mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-09 13:33:39 +00:00
all: remove init interpretation during IR construction
The interp package does a much better job at interpretation, and is implemented as a pass on the IR which makes it much easier to compose. Also, the implementation works much better as it is based on LLVM IR instead of Go SSA.
This commit is contained in:
committed by
Ron Evans
parent
da345e8723
commit
92d9b780b5
+16
-342
@@ -30,19 +30,18 @@ func init() {
|
||||
|
||||
// Configure the compiler.
|
||||
type Config struct {
|
||||
Triple string // LLVM target triple, e.g. x86_64-unknown-linux-gnu (empty string means default)
|
||||
CPU string // LLVM CPU name, e.g. atmega328p (empty string means default)
|
||||
GOOS string //
|
||||
GOARCH string //
|
||||
GC string // garbage collection strategy
|
||||
CFlags []string // cflags to pass to cgo
|
||||
LDFlags []string // ldflags to pass to cgo
|
||||
DumpSSA bool // dump Go SSA, for compiler debugging
|
||||
Debug bool // add debug symbols for gdb
|
||||
RootDir string // GOROOT for TinyGo
|
||||
GOPATH string // GOPATH, like `go env GOPATH`
|
||||
BuildTags []string // build tags for TinyGo (empty means {Config.GOOS/Config.GOARCH})
|
||||
InitInterp bool // use new init interpretation, meaning the old one is disabled
|
||||
Triple string // LLVM target triple, e.g. x86_64-unknown-linux-gnu (empty string means default)
|
||||
CPU string // LLVM CPU name, e.g. atmega328p (empty string means default)
|
||||
GOOS string //
|
||||
GOARCH string //
|
||||
GC string // garbage collection strategy
|
||||
CFlags []string // cflags to pass to cgo
|
||||
LDFlags []string // ldflags to pass to cgo
|
||||
DumpSSA bool // dump Go SSA, for compiler debugging
|
||||
Debug bool // add debug symbols for gdb
|
||||
RootDir string // GOROOT for TinyGo
|
||||
GOPATH string // GOPATH, like `go env GOPATH`
|
||||
BuildTags []string // build tags for TinyGo (empty means {Config.GOOS/Config.GOARCH})
|
||||
}
|
||||
|
||||
type Compiler struct {
|
||||
@@ -250,8 +249,7 @@ func (c *Compiler) Compile(mainPath string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Declare all globals. These will get an initializer when parsing "package
|
||||
// initializer" functions.
|
||||
// Declare all globals.
|
||||
for _, g := range c.ir.Globals {
|
||||
typ := g.Type().(*types.Pointer).Elem()
|
||||
llvmType, err := c.getLLVMType(typ)
|
||||
@@ -282,54 +280,18 @@ func (c *Compiler) Compile(mainPath string) error {
|
||||
frames = append(frames, frame)
|
||||
}
|
||||
|
||||
// Find and interpret package initializers.
|
||||
// Add definitions to declarations.
|
||||
for _, frame := range frames {
|
||||
if frame.fn.Synthetic == "package initializer" {
|
||||
c.initFuncs = append(c.initFuncs, frame.fn.LLVMFn)
|
||||
// Try to interpret as much as possible of the init() function.
|
||||
// Whenever it hits an instruction that it doesn't understand, it
|
||||
// bails out and leaves the rest to the compiler (so initialization
|
||||
// continues at runtime).
|
||||
// This should only happen when it hits a function call or the end
|
||||
// of the block, ideally.
|
||||
if !c.InitInterp {
|
||||
err := c.ir.Interpret(frame.fn.Blocks[0], c.DumpSSA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = c.parseFunc(frame)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set values for globals (after package initializer has been interpreted).
|
||||
for _, g := range c.ir.Globals {
|
||||
if g.Initializer() == nil {
|
||||
continue
|
||||
}
|
||||
err := c.parseGlobalInitializer(g)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Add definitions to declarations.
|
||||
for _, frame := range frames {
|
||||
if frame.fn.CName() != "" {
|
||||
continue
|
||||
}
|
||||
if frame.fn.Blocks == nil {
|
||||
continue // external function
|
||||
}
|
||||
var err error
|
||||
if frame.fn.Synthetic == "package initializer" {
|
||||
continue // already done
|
||||
} else {
|
||||
err = c.parseFunc(frame)
|
||||
}
|
||||
err := c.parseFunc(frame)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -725,282 +687,6 @@ func (c *Compiler) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value, suffix,
|
||||
return difunc, nil
|
||||
}
|
||||
|
||||
// Create a new global hashmap bucket, for map initialization.
|
||||
func (c *Compiler) initMapNewBucket(prefix string, mapType *types.Map) (llvm.Value, uint64, uint64, error) {
|
||||
llvmKeyType, err := c.getLLVMType(mapType.Key().Underlying())
|
||||
if err != nil {
|
||||
return llvm.Value{}, 0, 0, err
|
||||
}
|
||||
llvmValueType, err := c.getLLVMType(mapType.Elem().Underlying())
|
||||
if err != nil {
|
||||
return llvm.Value{}, 0, 0, err
|
||||
}
|
||||
keySize := c.targetData.TypeAllocSize(llvmKeyType)
|
||||
valueSize := c.targetData.TypeAllocSize(llvmValueType)
|
||||
bucketType := c.ctx.StructType([]llvm.Type{
|
||||
llvm.ArrayType(c.ctx.Int8Type(), 8), // tophash
|
||||
c.i8ptrType, // next bucket
|
||||
llvm.ArrayType(llvmKeyType, 8), // key type
|
||||
llvm.ArrayType(llvmValueType, 8), // value type
|
||||
}, false)
|
||||
bucketValue, err := c.getZeroValue(bucketType)
|
||||
if err != nil {
|
||||
return llvm.Value{}, 0, 0, err
|
||||
}
|
||||
bucket := llvm.AddGlobal(c.mod, bucketType, prefix+"$hashmap$bucket")
|
||||
bucket.SetInitializer(bucketValue)
|
||||
bucket.SetLinkage(llvm.InternalLinkage)
|
||||
return bucket, keySize, valueSize, nil
|
||||
}
|
||||
|
||||
func (c *Compiler) parseGlobalInitializer(g *ir.Global) error {
|
||||
if g.IsExtern() {
|
||||
return nil
|
||||
}
|
||||
llvmValue, err := c.getInterpretedValue(g.LinkName(), g.Initializer())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.LLVMGlobal.SetInitializer(llvmValue)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Turn a computed Value type (ConstValue, ArrayValue, etc.) into a LLVM value.
|
||||
// This is used to set the initializer of globals after they have been
|
||||
// calculated by the package initializer interpreter.
|
||||
func (c *Compiler) getInterpretedValue(prefix string, value ir.Value) (llvm.Value, error) {
|
||||
switch value := value.(type) {
|
||||
case *ir.ArrayValue:
|
||||
vals := make([]llvm.Value, len(value.Elems))
|
||||
for i, elem := range value.Elems {
|
||||
val, err := c.getInterpretedValue(prefix+"$arrayval", elem)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
vals[i] = val
|
||||
}
|
||||
subTyp, err := c.getLLVMType(value.ElemType)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
return llvm.ConstArray(subTyp, vals), nil
|
||||
|
||||
case *ir.ConstValue:
|
||||
return c.parseConst(prefix, value.Expr)
|
||||
|
||||
case *ir.FunctionValue:
|
||||
if value.Elem == nil {
|
||||
llvmType, err := c.getLLVMType(value.Type)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
return c.getZeroValue(llvmType)
|
||||
}
|
||||
fn := c.ir.GetFunction(value.Elem)
|
||||
ptr := fn.LLVMFn
|
||||
// Create closure value: {context, function pointer}
|
||||
ptr = c.ctx.ConstStruct([]llvm.Value{llvm.ConstPointerNull(c.i8ptrType), ptr}, false)
|
||||
return ptr, nil
|
||||
|
||||
case *ir.GlobalValue:
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
ptr := llvm.ConstInBoundsGEP(value.Global.LLVMGlobal, []llvm.Value{zero})
|
||||
return ptr, nil
|
||||
|
||||
case *ir.MapValue:
|
||||
// Create initial bucket.
|
||||
firstBucketGlobal, keySize, valueSize, err := c.initMapNewBucket(prefix, value.Type)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
|
||||
// Insert each key/value pair in the hashmap.
|
||||
bucketGlobal := firstBucketGlobal
|
||||
for i, key := range value.Keys {
|
||||
llvmKey, err := c.getInterpretedValue(prefix, key)
|
||||
if err != nil {
|
||||
return llvm.Value{}, nil
|
||||
}
|
||||
llvmValue, err := c.getInterpretedValue(prefix, value.Values[i])
|
||||
if err != nil {
|
||||
return llvm.Value{}, nil
|
||||
}
|
||||
|
||||
constVal := key.(*ir.ConstValue).Expr
|
||||
var keyBuf []byte
|
||||
switch constVal.Type().Underlying().(*types.Basic).Kind() {
|
||||
case types.String, types.UntypedString:
|
||||
keyBuf = []byte(constant.StringVal(constVal.Value))
|
||||
case types.Int:
|
||||
keyBuf = make([]byte, c.targetData.TypeAllocSize(c.intType))
|
||||
n, _ := constant.Uint64Val(constVal.Value)
|
||||
for i := range keyBuf {
|
||||
keyBuf[i] = byte(n)
|
||||
n >>= 8
|
||||
}
|
||||
default:
|
||||
return llvm.Value{}, errors.New("todo: init: map key not implemented: " + constVal.Type().Underlying().String())
|
||||
}
|
||||
hash := hashmapHash(keyBuf)
|
||||
|
||||
if i%8 == 0 && i != 0 {
|
||||
// Bucket is full, create a new one.
|
||||
newBucketGlobal, _, _, err := c.initMapNewBucket(prefix, value.Type)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
newBucketPtr := llvm.ConstInBoundsGEP(newBucketGlobal, []llvm.Value{zero})
|
||||
newBucketPtrCast := llvm.ConstBitCast(newBucketPtr, c.i8ptrType)
|
||||
// insert pointer into old bucket
|
||||
bucket := bucketGlobal.Initializer()
|
||||
bucket = llvm.ConstInsertValue(bucket, newBucketPtrCast, []uint32{1})
|
||||
bucketGlobal.SetInitializer(bucket)
|
||||
// switch to next bucket
|
||||
bucketGlobal = newBucketGlobal
|
||||
}
|
||||
|
||||
tophashValue := llvm.ConstInt(c.ctx.Int8Type(), uint64(hashmapTopHash(hash)), false)
|
||||
bucket := bucketGlobal.Initializer()
|
||||
bucket = llvm.ConstInsertValue(bucket, tophashValue, []uint32{0, uint32(i % 8)})
|
||||
bucket = llvm.ConstInsertValue(bucket, llvmKey, []uint32{2, uint32(i % 8)})
|
||||
bucket = llvm.ConstInsertValue(bucket, llvmValue, []uint32{3, uint32(i % 8)})
|
||||
bucketGlobal.SetInitializer(bucket)
|
||||
}
|
||||
|
||||
// Create the hashmap itself.
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
bucketPtr := llvm.ConstInBoundsGEP(firstBucketGlobal, []llvm.Value{zero})
|
||||
hashmapType := c.mod.GetTypeByName("runtime.hashmap")
|
||||
hashmap := llvm.ConstNamedStruct(hashmapType, []llvm.Value{
|
||||
llvm.ConstPointerNull(llvm.PointerType(hashmapType, 0)), // next
|
||||
llvm.ConstBitCast(bucketPtr, c.i8ptrType), // buckets
|
||||
llvm.ConstInt(c.uintptrType, uint64(len(value.Keys)), false), // count
|
||||
llvm.ConstInt(c.ctx.Int8Type(), keySize, false), // keySize
|
||||
llvm.ConstInt(c.ctx.Int8Type(), valueSize, false), // valueSize
|
||||
llvm.ConstInt(c.ctx.Int8Type(), 0, false), // bucketBits
|
||||
})
|
||||
|
||||
// Create a pointer to this hashmap.
|
||||
hashmapPtr := llvm.AddGlobal(c.mod, hashmap.Type(), prefix+"$hashmap")
|
||||
hashmapPtr.SetInitializer(hashmap)
|
||||
hashmapPtr.SetLinkage(llvm.InternalLinkage)
|
||||
return llvm.ConstInBoundsGEP(hashmapPtr, []llvm.Value{zero}), nil
|
||||
|
||||
case *ir.PointerBitCastValue:
|
||||
elem, err := c.getInterpretedValue(prefix, value.Elem)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
llvmType, err := c.getLLVMType(value.Type)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
return llvm.ConstBitCast(elem, llvmType), nil
|
||||
|
||||
case *ir.PointerToUintptrValue:
|
||||
elem, err := c.getInterpretedValue(prefix, value.Elem)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
return llvm.ConstPtrToInt(elem, c.uintptrType), nil
|
||||
|
||||
case *ir.PointerValue:
|
||||
if value.Elem == nil {
|
||||
typ, err := c.getLLVMType(value.Type)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
return llvm.ConstPointerNull(typ), nil
|
||||
}
|
||||
elem, err := c.getInterpretedValue(prefix, *value.Elem)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
|
||||
obj := llvm.AddGlobal(c.mod, elem.Type(), prefix+"$ptrvalue")
|
||||
obj.SetInitializer(elem)
|
||||
obj.SetLinkage(llvm.InternalLinkage)
|
||||
elem = obj
|
||||
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
ptr := llvm.ConstInBoundsGEP(elem, []llvm.Value{zero})
|
||||
return ptr, nil
|
||||
|
||||
case *ir.SliceValue:
|
||||
var globalPtr llvm.Value
|
||||
var arrayLength uint64
|
||||
if value.Array == nil {
|
||||
arrayType, err := c.getLLVMType(value.Type.Elem())
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
globalPtr = llvm.ConstPointerNull(llvm.PointerType(arrayType, 0))
|
||||
} else {
|
||||
// make array
|
||||
array, err := c.getInterpretedValue(prefix, value.Array)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
// make global from array
|
||||
global := llvm.AddGlobal(c.mod, array.Type(), prefix+"$array")
|
||||
global.SetInitializer(array)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
|
||||
// get pointer to global
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
globalPtr = c.builder.CreateInBoundsGEP(global, []llvm.Value{zero, zero}, "")
|
||||
|
||||
arrayLength = uint64(len(value.Array.Elems))
|
||||
}
|
||||
|
||||
// make slice
|
||||
sliceTyp, err := c.getLLVMType(value.Type)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
llvmLen := llvm.ConstInt(c.uintptrType, arrayLength, false)
|
||||
slice := llvm.ConstNamedStruct(sliceTyp, []llvm.Value{
|
||||
globalPtr, // ptr
|
||||
llvmLen, // len
|
||||
llvmLen, // cap
|
||||
})
|
||||
return slice, nil
|
||||
|
||||
case *ir.StructValue:
|
||||
fields := make([]llvm.Value, len(value.Fields))
|
||||
for i, elem := range value.Fields {
|
||||
field, err := c.getInterpretedValue(prefix, elem)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
fields[i] = field
|
||||
}
|
||||
switch value.Type.(type) {
|
||||
case *types.Named:
|
||||
llvmType, err := c.getLLVMType(value.Type)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
return llvm.ConstNamedStruct(llvmType, fields), nil
|
||||
case *types.Struct:
|
||||
return c.ctx.ConstStruct(fields, false), nil
|
||||
default:
|
||||
return llvm.Value{}, errors.New("init: unknown struct type: " + value.Type.String())
|
||||
}
|
||||
|
||||
case *ir.ZeroBasicValue:
|
||||
llvmType, err := c.getLLVMType(value.Type)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
return c.getZeroValue(llvmType)
|
||||
|
||||
default:
|
||||
return llvm.Value{}, errors.New("init: unknown initializer type: " + fmt.Sprintf("%#v", value))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) parseFunc(frame *Frame) error {
|
||||
if c.DumpSSA {
|
||||
fmt.Printf("\nfunc %s:\n", frame.fn.Function)
|
||||
@@ -1185,10 +871,6 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error {
|
||||
switch instr := instr.(type) {
|
||||
case ssa.Value:
|
||||
value, err := c.parseExpr(frame, instr)
|
||||
if err == ir.ErrCGoWrapper {
|
||||
// Ignore CGo global variables which we don't use.
|
||||
return nil
|
||||
}
|
||||
frame.locals[instr] = value
|
||||
return err
|
||||
case *ssa.DebugRef:
|
||||
@@ -1299,10 +981,6 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error {
|
||||
return c.emitChanSend(frame, instr)
|
||||
case *ssa.Store:
|
||||
llvmAddr, err := c.parseExpr(frame, instr.Addr)
|
||||
if err == ir.ErrCGoWrapper {
|
||||
// Ignore CGo global variables which we don't use.
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1900,10 +1578,6 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
fn.LLVMFn,
|
||||
}, false), nil
|
||||
case *ssa.Global:
|
||||
if strings.HasPrefix(expr.Name(), "__cgofn__cgo_") || strings.HasPrefix(expr.Name(), "_cgo_") {
|
||||
// Ignore CGo global variables which we don't use.
|
||||
return llvm.Value{}, ir.ErrCGoWrapper
|
||||
}
|
||||
value := c.ir.GetGlobal(expr).LLVMGlobal
|
||||
if value.IsNil() {
|
||||
return llvm.Value{}, c.makeError(expr.Pos(), "global not found: "+c.ir.GetGlobal(expr).LinkName())
|
||||
@@ -2021,7 +1695,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
return c.parseMakeInterface(val, expr.X.Type(), "", expr.Pos())
|
||||
return c.parseMakeInterface(val, expr.X.Type(), expr.Pos())
|
||||
case *ssa.MakeMap:
|
||||
mapType := expr.Type().Underlying().(*types.Map)
|
||||
llvmKeyType, err := c.getLLVMType(mapType.Key().Underlying())
|
||||
|
||||
+8
-19
@@ -22,28 +22,17 @@ import (
|
||||
// value field.
|
||||
//
|
||||
// An interface value is a {typecode, value} tuple, or {i16, i8*} to be exact.
|
||||
func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, global string, pos token.Pos) (llvm.Value, error) {
|
||||
func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) (llvm.Value, error) {
|
||||
var itfValue llvm.Value
|
||||
size := c.targetData.TypeAllocSize(val.Type())
|
||||
if size > c.targetData.TypeAllocSize(c.i8ptrType) {
|
||||
if global != "" {
|
||||
// Allocate in a global variable.
|
||||
global := llvm.AddGlobal(c.mod, val.Type(), global+"$itfvalue")
|
||||
global.SetInitializer(val)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetGlobalConstant(true)
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
itfValueRaw := llvm.ConstInBoundsGEP(global, []llvm.Value{zero, zero})
|
||||
itfValue = llvm.ConstBitCast(itfValueRaw, c.i8ptrType)
|
||||
} else {
|
||||
// Allocate on the heap and put a pointer in the interface.
|
||||
// TODO: escape analysis.
|
||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||
alloc := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "makeinterface.alloc")
|
||||
itfValueCast := c.builder.CreateBitCast(alloc, llvm.PointerType(val.Type(), 0), "makeinterface.cast.value")
|
||||
c.builder.CreateStore(val, itfValueCast)
|
||||
itfValue = c.builder.CreateBitCast(itfValueCast, c.i8ptrType, "makeinterface.cast.i8ptr")
|
||||
}
|
||||
// Allocate on the heap and put a pointer in the interface.
|
||||
// TODO: escape analysis.
|
||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||
alloc := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "makeinterface.alloc")
|
||||
itfValueCast := c.builder.CreateBitCast(alloc, llvm.PointerType(val.Type(), 0), "makeinterface.cast.value")
|
||||
c.builder.CreateStore(val, itfValueCast)
|
||||
itfValue = c.builder.CreateBitCast(itfValueCast, c.i8ptrType, "makeinterface.cast.i8ptr")
|
||||
} else if size == 0 {
|
||||
itfValue = llvm.ConstPointerNull(c.i8ptrType)
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user