Browse Source

compiler,transform: move interface lowering to transform package

pull/730/head
Ayke van Laethem 5 years ago
committed by Ayke
parent
commit
e20af665fa
  1. 39
      compiler/llvm.go
  2. 12
      compiler/llvmutil/wordpack.go
  3. 4
      compiler/optimizer.go
  4. 43
      transform/interface-lowering.go
  5. 10
      transform/interface-lowering_test.go
  6. 50
      transform/llvm.go
  7. 24
      transform/reflect.go
  8. 80
      transform/testdata/interface.ll
  9. 102
      transform/testdata/interface.out.ll

39
compiler/llvm.go

@ -1,8 +1,6 @@
package compiler
import (
"reflect"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"tinygo.org/x/go-llvm"
)
@ -130,43 +128,14 @@ func (c *Compiler) splitBasicBlock(afterInst llvm.Value, insertAfter llvm.BasicB
// contents, and returns the global.
// Note that it is left with the default linkage etc., you should set
// linkage/constant/etc properties yourself.
func (c *Compiler) makeGlobalArray(bufItf interface{}, name string, elementType llvm.Type) llvm.Value {
buf := reflect.ValueOf(bufItf)
globalType := llvm.ArrayType(elementType, buf.Len())
func (c *Compiler) makeGlobalArray(buf []byte, name string, elementType llvm.Type) llvm.Value {
globalType := llvm.ArrayType(elementType, len(buf))
global := llvm.AddGlobal(c.mod, globalType, name)
value := llvm.Undef(globalType)
for i := 0; i < buf.Len(); i++ {
ch := buf.Index(i).Uint()
for i := 0; i < len(buf); i++ {
ch := uint64(buf[i])
value = llvm.ConstInsertValue(value, llvm.ConstInt(elementType, ch, false), []uint32{uint32(i)})
}
global.SetInitializer(value)
return global
}
// getGlobalBytes returns the slice contained in the array of the provided
// global. It can recover the bytes originally created using makeGlobalArray, if
// makeGlobalArray was given a byte slice.
func getGlobalBytes(global llvm.Value) []byte {
value := global.Initializer()
buf := make([]byte, value.Type().ArrayLength())
for i := range buf {
buf[i] = byte(llvm.ConstExtractValue(value, []uint32{uint32(i)}).ZExtValue())
}
return buf
}
// replaceGlobalByteWithArray replaces a global integer type in the module with
// an integer array, using a GEP to make the types match. It is a convenience
// function used for creating reflection sidetables, for example.
func (c *Compiler) replaceGlobalIntWithArray(name string, buf interface{}) llvm.Value {
oldGlobal := c.mod.NamedGlobal(name)
global := c.makeGlobalArray(buf, name+".tmp", oldGlobal.Type().ElementType())
gep := llvm.ConstGEP(global, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
})
oldGlobal.ReplaceAllUsesWith(gep)
oldGlobal.EraseFromParentAsGlobal()
global.SetName(name)
return global
}

12
compiler/llvmutil/wordpack.go

@ -45,10 +45,18 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.
// Packed data is bigger than a pointer, so allocate it on the heap.
sizeValue := llvm.ConstInt(uintptrType, size, false)
alloc := mod.NamedFunction("runtime.alloc")
packedHeapAlloc = builder.CreateCall(alloc, []llvm.Value{sizeValue}, "")
packedHeapAlloc = builder.CreateCall(alloc, []llvm.Value{
sizeValue,
llvm.Undef(i8ptrType), // unused context parameter
llvm.ConstPointerNull(i8ptrType), // coroutine handle
}, "")
if config.NeedsStackObjects() {
trackPointer := mod.NamedFunction("runtime.trackPointer")
builder.CreateCall(trackPointer, []llvm.Value{packedHeapAlloc}, "")
builder.CreateCall(trackPointer, []llvm.Value{
packedHeapAlloc,
llvm.Undef(i8ptrType), // unused context parameter
llvm.ConstPointerNull(i8ptrType), // coroutine handle
}, "")
}
packedAlloc = builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
}

4
compiler/optimizer.go

@ -55,7 +55,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
transform.OptimizeMaps(c.mod)
transform.OptimizeStringToBytes(c.mod)
transform.OptimizeAllocs(c.mod)
c.LowerInterfaces()
transform.LowerInterfaces(c.mod)
c.LowerFuncValues()
// After interfaces are lowered, there are many more opportunities for
@ -89,7 +89,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
}
} else {
// Must be run at any optimization level.
c.LowerInterfaces()
transform.LowerInterfaces(c.mod)
c.LowerFuncValues()
err := c.LowerGoroutines()
if err != nil {

43
compiler/interface-lowering.go → transform/interface-lowering.go

@ -1,4 +1,4 @@
package compiler
package transform
// This file provides function to lower interface intrinsics to their final LLVM
// form, optimizing them in the process.
@ -38,6 +38,7 @@ import (
"sort"
"strings"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"tinygo.org/x/go-llvm"
)
@ -133,22 +134,28 @@ func (itf *interfaceInfo) id() string {
// pass has been implemented as an object type because of its complexity, but
// should be seen as a regular function call (see LowerInterfaces).
type lowerInterfacesPass struct {
*Compiler
types map[string]*typeInfo
signatures map[string]*signatureInfo
interfaces map[string]*interfaceInfo
mod llvm.Module
builder llvm.Builder
ctx llvm.Context
uintptrType llvm.Type
types map[string]*typeInfo
signatures map[string]*signatureInfo
interfaces map[string]*interfaceInfo
}
// Lower all interface functions. They are emitted by the compiler as
// higher-level intrinsics that need some lowering before LLVM can work on them.
// This is done so that a few cleanup passes can run before assigning the final
// type codes.
func (c *Compiler) LowerInterfaces() {
// LowerInterfaces lowers all intermediate interface calls and globals that are
// emitted by the compiler as higher-level intrinsics. They need some lowering
// before LLVM can work on them. This is done so that a few cleanup passes can
// run before assigning the final type codes.
func LowerInterfaces(mod llvm.Module) {
p := &lowerInterfacesPass{
Compiler: c,
types: make(map[string]*typeInfo),
signatures: make(map[string]*signatureInfo),
interfaces: make(map[string]*interfaceInfo),
mod: mod,
builder: mod.Context().NewBuilder(),
ctx: mod.Context(),
uintptrType: mod.Context().IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8),
types: make(map[string]*typeInfo),
signatures: make(map[string]*signatureInfo),
interfaces: make(map[string]*interfaceInfo),
}
p.run()
}
@ -156,8 +163,8 @@ func (c *Compiler) LowerInterfaces() {
// run runs the pass itself.
func (p *lowerInterfacesPass) run() {
// Collect all type codes.
typecodeIDPtr := llvm.PointerType(p.getLLVMRuntimeType("typecodeID"), 0)
typeInInterfacePtr := llvm.PointerType(p.getLLVMRuntimeType("typeInInterface"), 0)
typecodeIDPtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typecodeID"), 0)
typeInInterfacePtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typeInInterface"), 0)
var typesInInterfaces []llvm.Value
for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
switch global.Type() {
@ -368,7 +375,7 @@ func (p *lowerInterfacesPass) run() {
}
// Assign a type code for each type.
p.assignTypeCodes(typeSlice)
assignTypeCodes(p.mod, typeSlice)
// Replace each use of a runtime.typeInInterface with the constant type
// code.
@ -519,7 +526,7 @@ func (p *lowerInterfacesPass) replaceInvokeWithCall(use llvm.Value, typ *typeInf
}
}
p.builder.SetInsertPointBefore(call)
receiverParams := p.emitPointerUnpack(operands[0], receiverParamTypes)
receiverParams := llvmutil.EmitPointerUnpack(p.builder, p.mod, operands[0], receiverParamTypes)
result := p.builder.CreateCall(function, append(receiverParams, operands[1:]...), "")
if result.Type().TypeKind() != llvm.VoidTypeKind {
call.ReplaceAllUsesWith(result)

10
transform/interface-lowering_test.go

@ -0,0 +1,10 @@
package transform
import (
"testing"
)
func TestInterfaceLowering(t *testing.T) {
t.Parallel()
testTransform(t, "testdata/interface", LowerInterfaces)
}

50
transform/llvm.go

@ -1,12 +1,17 @@
package transform
import (
"reflect"
"tinygo.org/x/go-llvm"
)
// Return a list of values (actually, instructions) where this value is used as
// an operand.
func getUses(value llvm.Value) []llvm.Value {
if value.IsNil() {
return nil
}
var uses []llvm.Value
use := value.FirstUse()
for !use.IsNil() {
@ -15,3 +20,48 @@ func getUses(value llvm.Value) []llvm.Value {
}
return uses
}
// makeGlobalArray creates a new LLVM global with the given name and integers as
// contents, and returns the global.
// Note that it is left with the default linkage etc., you should set
// linkage/constant/etc properties yourself.
func makeGlobalArray(mod llvm.Module, bufItf interface{}, name string, elementType llvm.Type) llvm.Value {
buf := reflect.ValueOf(bufItf)
globalType := llvm.ArrayType(elementType, buf.Len())
global := llvm.AddGlobal(mod, globalType, name)
value := llvm.Undef(globalType)
for i := 0; i < buf.Len(); i++ {
ch := buf.Index(i).Uint()
value = llvm.ConstInsertValue(value, llvm.ConstInt(elementType, ch, false), []uint32{uint32(i)})
}
global.SetInitializer(value)
return global
}
// getGlobalBytes returns the slice contained in the array of the provided
// global. It can recover the bytes originally created using makeGlobalArray, if
// makeGlobalArray was given a byte slice.
func getGlobalBytes(global llvm.Value) []byte {
value := global.Initializer()
buf := make([]byte, value.Type().ArrayLength())
for i := range buf {
buf[i] = byte(llvm.ConstExtractValue(value, []uint32{uint32(i)}).ZExtValue())
}
return buf
}
// replaceGlobalByteWithArray replaces a global integer type in the module with
// an integer array, using a GEP to make the types match. It is a convenience
// function used for creating reflection sidetables, for example.
func replaceGlobalIntWithArray(mod llvm.Module, name string, buf interface{}) llvm.Value {
oldGlobal := mod.NamedGlobal(name)
global := makeGlobalArray(mod, buf, name+".tmp", oldGlobal.Type().ElementType())
gep := llvm.ConstGEP(global, []llvm.Value{
llvm.ConstInt(mod.Context().Int32Type(), 0, false),
llvm.ConstInt(mod.Context().Int32Type(), 0, false),
})
oldGlobal.ReplaceAllUsesWith(gep)
oldGlobal.EraseFromParentAsGlobal()
global.SetName(name)
return global
}

24
compiler/reflect.go → transform/reflect.go

@ -1,4 +1,4 @@
package compiler
package transform
// This file has some compiler support for run-time reflection using the reflect
// package. In particular, it encodes type information in type codes in such a
@ -125,27 +125,27 @@ type typeCodeAssignmentState struct {
// assignTypeCodes is used to assign a type code to each type in the program
// that is ever stored in an interface. It tries to use the smallest possible
// numbers to make the code that works with interfaces as small as possible.
func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
func assignTypeCodes(mod llvm.Module, typeSlice typeInfoSlice) {
// if reflect were not used, we could skip generating the sidetable
// this does not help in practice, and is difficult to do correctly
// Assign typecodes the way the reflect package expects.
state := typeCodeAssignmentState{
fallbackIndex: 1,
uintptrLen: c.uintptrType.IntTypeWidth(),
uintptrLen: llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8,
namedBasicTypes: make(map[string]int),
namedNonBasicTypes: make(map[string]int),
arrayTypes: make(map[string]int),
structTypes: make(map[string]int),
structNames: make(map[string]int),
needsNamedNonBasicTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.namedNonBasicTypesSidetable"))) != 0,
needsStructTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structTypesSidetable"))) != 0,
needsStructNamesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structNamesSidetable"))) != 0,
needsArrayTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.arrayTypesSidetable"))) != 0,
needsNamedNonBasicTypesSidetable: len(getUses(mod.NamedGlobal("reflect.namedNonBasicTypesSidetable"))) != 0,
needsStructTypesSidetable: len(getUses(mod.NamedGlobal("reflect.structTypesSidetable"))) != 0,
needsStructNamesSidetable: len(getUses(mod.NamedGlobal("reflect.structNamesSidetable"))) != 0,
needsArrayTypesSidetable: len(getUses(mod.NamedGlobal("reflect.arrayTypesSidetable"))) != 0,
}
for _, t := range typeSlice {
num := state.getTypeCodeNum(t.typecode)
if num.BitLen() > c.uintptrType.IntTypeWidth() || !num.IsUint64() {
if num.BitLen() > state.uintptrLen || !num.IsUint64() {
// TODO: support this in some way, using a side table for example.
// That's less efficient but better than not working at all.
// Particularly important on systems with 16-bit pointers (e.g.
@ -157,22 +157,22 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
// Only create this sidetable when it is necessary.
if state.needsNamedNonBasicTypesSidetable {
global := c.replaceGlobalIntWithArray("reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable)
global := replaceGlobalIntWithArray(mod, "reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
}
if state.needsArrayTypesSidetable {
global := c.replaceGlobalIntWithArray("reflect.arrayTypesSidetable", state.arrayTypesSidetable)
global := replaceGlobalIntWithArray(mod, "reflect.arrayTypesSidetable", state.arrayTypesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
}
if state.needsStructTypesSidetable {
global := c.replaceGlobalIntWithArray("reflect.structTypesSidetable", state.structTypesSidetable)
global := replaceGlobalIntWithArray(mod, "reflect.structTypesSidetable", state.structTypesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
}
if state.needsStructNamesSidetable {
global := c.replaceGlobalIntWithArray("reflect.structNamesSidetable", state.structNamesSidetable)
global := replaceGlobalIntWithArray(mod, "reflect.structNamesSidetable", state.structNamesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
}

80
transform/testdata/interface.ll

@ -0,0 +1,80 @@
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"
%runtime.typecodeID = type { %runtime.typecodeID*, i32 }
%runtime.typeInInterface = type { %runtime.typecodeID*, %runtime.interfaceMethodInfo* }
%runtime.interfaceMethodInfo = type { i8*, i32 }
@"reflect/types.type:basic:uint8" = external constant %runtime.typecodeID
@"reflect/types.type:basic:int" = external constant %runtime.typecodeID
@"typeInInterface:reflect/types.type:basic:uint8" = private constant %runtime.typeInInterface { %runtime.typecodeID* @"reflect/types.type:basic:uint8", %runtime.interfaceMethodInfo* null }
@"typeInInterface:reflect/types.type:basic:int" = private constant %runtime.typeInInterface { %runtime.typecodeID* @"reflect/types.type:basic:int", %runtime.interfaceMethodInfo* null }
@"func NeverImplementedMethod()" = external constant i8
@"Unmatched$interface" = private constant [1 x i8*] [i8* @"func NeverImplementedMethod()"]
@"func Double() int" = external constant i8
@"Doubler$interface" = private constant [1 x i8*] [i8* @"func Double() int"]
@"Number$methodset" = private constant [1 x %runtime.interfaceMethodInfo] [%runtime.interfaceMethodInfo { i8* @"func Double() int", i32 ptrtoint (i32 (i8*)* @"(Number).Double$invoke" to i32) }]
@"reflect/types.type:named:Number" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0 }
@"typeInInterface:reflect/types.type:named:Number" = private constant %runtime.typeInInterface { %runtime.typecodeID* @"reflect/types.type:named:Number", %runtime.interfaceMethodInfo* getelementptr inbounds ([1 x %runtime.interfaceMethodInfo], [1 x %runtime.interfaceMethodInfo]* @"Number$methodset", i32 0, i32 0) }
declare i1 @runtime.interfaceImplements(i32, i8**)
declare i1 @runtime.typeAssert(i32, %runtime.typecodeID*)
declare i32 @runtime.interfaceMethod(i32, i8**, i8*)
declare void @runtime.printuint8(i8)
declare void @runtime.printint32(i32)
declare void @runtime.printptr(i32)
declare void @runtime.printnl()
define void @printInterfaces() {
call void @printInterface(i32 ptrtoint (%runtime.typeInInterface* @"typeInInterface:reflect/types.type:basic:int" to i32), i8* inttoptr (i32 5 to i8*))
call void @printInterface(i32 ptrtoint (%runtime.typeInInterface* @"typeInInterface:reflect/types.type:basic:uint8" to i32), i8* inttoptr (i8 120 to i8*))
call void @printInterface(i32 ptrtoint (%runtime.typeInInterface* @"typeInInterface:reflect/types.type:named:Number" to i32), i8* inttoptr (i32 3 to i8*))
ret void
}
define void @printInterface(i32 %typecode, i8* %value) {
%isUnmatched = call i1 @runtime.interfaceImplements(i32 %typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"Unmatched$interface", i32 0, i32 0))
br i1 %isUnmatched, label %typeswitch.Unmatched, label %typeswitch.notUnmatched
typeswitch.Unmatched:
%unmatched = ptrtoint i8* %value to i32
call void @runtime.printptr(i32 %unmatched)
call void @runtime.printnl()
ret void
typeswitch.notUnmatched:
%isDoubler = call i1 @runtime.interfaceImplements(i32 %typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"Doubler$interface", i32 0, i32 0))
br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler
typeswitch.Doubler:
%doubler.func = call i32 @runtime.interfaceMethod(i32 %typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"Doubler$interface", i32 0, i32 0), i8* nonnull @"func Double() int")
%doubler.func.cast = inttoptr i32 %doubler.func to i32 (i8*)*
%doubler.result = call i32 %doubler.func.cast(i8* %value)
call void @runtime.printint32(i32 %doubler.result)
ret void
typeswitch.notDoubler:
%isByte = call i1 @runtime.typeAssert(i32 %typecode, %runtime.typecodeID* nonnull @"reflect/types.type:basic:uint8")
br i1 %isByte, label %typeswitch.byte, label %typeswitch.notByte
typeswitch.byte:
%byte = ptrtoint i8* %value to i8
call void @runtime.printuint8(i8 %byte)
call void @runtime.printnl()
ret void
typeswitch.notByte:
ret void
}
define i32 @"(Number).Double"(i32 %receiver) {
%ret = mul i32 %receiver, 2
ret i32 %ret
}
define i32 @"(Number).Double$invoke"(i8* %receiverPtr) {
%receiver = ptrtoint i8* %receiverPtr to i32
%ret = call i32 @"(Number).Double"(i32 %receiver)
ret i32 %ret
}

102
transform/testdata/interface.out.ll

@ -0,0 +1,102 @@
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"
%runtime.typecodeID = type { %runtime.typecodeID*, i32 }
@"reflect/types.type:basic:uint8" = external constant %runtime.typecodeID
@"reflect/types.type:basic:int" = external constant %runtime.typecodeID
@"func NeverImplementedMethod()" = external constant i8
@"Unmatched$interface" = private constant [1 x i8*] [i8* @"func NeverImplementedMethod()"]
@"func Double() int" = external constant i8
@"Doubler$interface" = private constant [1 x i8*] [i8* @"func Double() int"]
@"reflect/types.type:named:Number" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0 }
declare i1 @runtime.interfaceImplements(i32, i8**)
declare i1 @runtime.typeAssert(i32, %runtime.typecodeID*)
declare i32 @runtime.interfaceMethod(i32, i8**, i8*)
declare void @runtime.printuint8(i8)
declare void @runtime.printint32(i32)
declare void @runtime.printptr(i32)
declare void @runtime.printnl()
define void @printInterfaces() {
call void @printInterface(i32 4, i8* inttoptr (i32 5 to i8*))
call void @printInterface(i32 16, i8* inttoptr (i8 120 to i8*))
call void @printInterface(i32 68, i8* inttoptr (i32 3 to i8*))
ret void
}
define void @printInterface(i32 %typecode, i8* %value) {
%typeassert.ok1 = call i1 @"Unmatched$typeassert"(i32 %typecode)
br i1 %typeassert.ok1, label %typeswitch.Unmatched, label %typeswitch.notUnmatched
typeswitch.Unmatched:
%unmatched = ptrtoint i8* %value to i32
call void @runtime.printptr(i32 %unmatched)
call void @runtime.printnl()
ret void
typeswitch.notUnmatched:
%typeassert.ok = call i1 @"Doubler$typeassert"(i32 %typecode)
br i1 %typeassert.ok, label %typeswitch.Doubler, label %typeswitch.notDoubler
typeswitch.Doubler:
%doubler.result = call i32 @"(Number).Double$invoke"(i8* %value)
call void @runtime.printint32(i32 %doubler.result)
ret void
typeswitch.notDoubler:
%typeassert.ok2 = icmp eq i32 16, %typecode
br i1 %typeassert.ok2, label %typeswitch.byte, label %typeswitch.notByte
typeswitch.byte:
%byte = ptrtoint i8* %value to i8
call void @runtime.printuint8(i8 %byte)
call void @runtime.printnl()
ret void
typeswitch.notByte:
ret void
}
define i32 @"(Number).Double"(i32 %receiver) {
%ret = mul i32 %receiver, 2
ret i32 %ret
}
define i32 @"(Number).Double$invoke"(i8* %receiverPtr) {
%receiver = ptrtoint i8* %receiverPtr to i32
%ret = call i32 @"(Number).Double"(i32 %receiver)
ret i32 %ret
}
define internal i1 @"Doubler$typeassert"(i32 %actualType) unnamed_addr {
entry:
switch i32 %actualType, label %else [
i32 68, label %then
]
then:
ret i1 true
else:
ret i1 false
}
define internal i1 @"Unmatched$typeassert"(i32 %actualType) unnamed_addr {
entry:
switch i32 %actualType, label %else [
]
then:
ret i1 true
else:
ret i1 false
}
Loading…
Cancel
Save