diff --git a/compiler/compiler.go b/compiler/compiler.go index a934515c..ab8fe8c6 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -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()) diff --git a/compiler/interface.go b/compiler/interface.go index 9e2e8b43..3a2f64bf 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -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 { diff --git a/ir/interpreter.go b/ir/interpreter.go deleted file mode 100644 index ed81478a..00000000 --- a/ir/interpreter.go +++ /dev/null @@ -1,522 +0,0 @@ -package ir - -// This file provides functionality to interpret very basic Go SSA, for -// compile-time initialization of globals. - -import ( - "errors" - "fmt" - "go/constant" - "go/token" - "go/types" - "strings" - - "golang.org/x/tools/go/ssa" -) - -var ErrCGoWrapper = errors.New("tinygo internal: cgo wrapper") // a signal, not an error - -// Ignore these calls (replace with a zero return value) when encountered during -// interpretation. -var ignoreInitCalls = map[string]struct{}{ - "syscall.runtime_envs": struct{}{}, - "syscall/js.predefValue": struct{}{}, - "(syscall/js.Value).Get": struct{}{}, - "(syscall/js.Value).New": struct{}{}, - "(syscall/js.Value).Int": struct{}{}, - "os.init$1": struct{}{}, -} - -// Interpret instructions as far as possible, and drop those instructions from -// the basic block. -func (p *Program) Interpret(block *ssa.BasicBlock, dumpSSA bool) error { - if dumpSSA { - fmt.Printf("\ninterpret: %s\n", block.Parent().Pkg.Pkg.Path()) - } - for { - i, err := p.interpret(block.Instrs, nil, nil, nil, dumpSSA) - if err == ErrCGoWrapper { - // skip this instruction - block.Instrs = block.Instrs[i+1:] - continue - } - block.Instrs = block.Instrs[i:] - return err - } -} - -// Interpret instructions as far as possible, and return the index of the first -// unknown instruction. -func (p *Program) interpret(instrs []ssa.Instruction, paramKeys []*ssa.Parameter, paramValues []Value, results []Value, dumpSSA bool) (int, error) { - locals := map[ssa.Value]Value{} - for i, key := range paramKeys { - locals[key] = paramValues[i] - } - for i, instr := range instrs { - if _, ok := instr.(*ssa.DebugRef); ok { - continue - } - if dumpSSA { - if val, ok := instr.(ssa.Value); ok && val.Name() != "" { - fmt.Printf("\t%s: %s = %s\n", instr.Parent().RelString(nil), val.Name(), val.String()) - } else { - fmt.Printf("\t%s: %s\n", instr.Parent().RelString(nil), instr.String()) - } - } - switch instr := instr.(type) { - case *ssa.Alloc: - alloc, err := p.getZeroValue(instr.Type().Underlying().(*types.Pointer).Elem()) - if err != nil { - return i, err - } - locals[instr] = &PointerValue{nil, &alloc} - case *ssa.BinOp: - if typ, ok := instr.Type().(*types.Basic); ok && typ.Kind() == types.String { - // Concatenate two strings. - // This happens in the time package, for example. - x, err := p.getValue(instr.X, locals) - if err != nil { - return i, err - } - y, err := p.getValue(instr.Y, locals) - if err != nil { - return i, err - } - xstr := constant.StringVal(x.(*ConstValue).Expr.Value) - ystr := constant.StringVal(y.(*ConstValue).Expr.Value) - locals[instr] = &ConstValue{ssa.NewConst(constant.MakeString(xstr+ystr), types.Typ[types.String])} - } else { - return i, errors.New("init: unknown binop: " + instr.String()) - } - case *ssa.Call: - common := instr.Common() - callee := common.StaticCallee() - if callee == nil { - return i, nil // don't understand dynamic dispatch - } - if _, ok := ignoreInitCalls[callee.String()]; ok { - // These calls are not needed and can be ignored, for the time - // being. - results := make([]Value, callee.Signature.Results().Len()) - for i := range results { - var err error - results[i], err = p.getZeroValue(callee.Signature.Results().At(i).Type()) - if err != nil { - return i, err - } - } - if len(results) == 1 { - locals[instr] = results[0] - } else if len(results) > 1 { - locals[instr] = &StructValue{Fields: results} - } - continue - } - if callee.String() == "os.NewFile" { - // Emulate the creation of os.Stdin, os.Stdout and os.Stderr. - resultPtrType := callee.Signature.Results().At(0).Type().(*types.Pointer) - resultStructOuterType := resultPtrType.Elem().Underlying().(*types.Struct) - if resultStructOuterType.NumFields() != 1 { - panic("expected 1 field in os.File struct") - } - fileInnerPtrType := resultStructOuterType.Field(0).Type().(*types.Pointer) - fileInnerType := fileInnerPtrType.Elem().(*types.Named) - fileInnerStructType := fileInnerType.Underlying().(*types.Struct) - fileInner, err := p.getZeroValue(fileInnerType) // os.file - if err != nil { - return i, err - } - for fieldIndex := 0; fieldIndex < fileInnerStructType.NumFields(); fieldIndex++ { - field := fileInnerStructType.Field(fieldIndex) - if field.Name() == "name" { - // Set the 'name' field. - name, err := p.getValue(common.Args[1], locals) - if err != nil { - return i, err - } - fileInner.(*StructValue).Fields[fieldIndex] = name - } else if field.Type().String() == "internal/poll.FD" { - // Set the file descriptor field. - field := field.Type().Underlying().(*types.Struct) - for subfieldIndex := 0; subfieldIndex < field.NumFields(); subfieldIndex++ { - subfield := field.Field(subfieldIndex) - if subfield.Name() == "Sysfd" { - sysfd, err := p.getValue(common.Args[0], locals) - if err != nil { - return i, err - } - sysfd = &ConstValue{Expr: ssa.NewConst(sysfd.(*ConstValue).Expr.Value, subfield.Type())} - fileInner.(*StructValue).Fields[fieldIndex].(*StructValue).Fields[subfieldIndex] = sysfd - } - } - } - } - fileInnerPtr := &PointerValue{fileInnerPtrType, &fileInner} // *os.file - var fileOuter Value = &StructValue{Type: resultPtrType.Elem(), Fields: []Value{fileInnerPtr}} // os.File - result := &PointerValue{resultPtrType.Elem(), &fileOuter} // *os.File - locals[instr] = result - continue - } - if canInterpret(callee) { - params := make([]Value, len(common.Args)) - for i, arg := range common.Args { - val, err := p.getValue(arg, locals) - if err != nil { - return i, err - } - params[i] = val - } - results := make([]Value, callee.Signature.Results().Len()) - subi, err := p.interpret(callee.Blocks[0].Instrs, callee.Params, params, results, dumpSSA) - if err != nil { - return i, err - } - if subi != len(callee.Blocks[0].Instrs) { - return i, errors.New("init: could not interpret all instructions of subroutine") - } - if len(results) == 1 { - locals[instr] = results[0] - } else { - panic("unimplemented: not exactly 1 result") - } - continue - } - if callee.Object() == nil || callee.Object().Name() == "init" { - return i, nil // arrived at the init#num functions - } - return i, errors.New("todo: init call: " + callee.String()) - case *ssa.ChangeType: - x, err := p.getValue(instr.X, locals) - if err != nil { - return i, err - } - // The only case when we need to bitcast is when casting between named - // struct types, as those are actually different in LLVM. Let's just - // bitcast all struct types for ease of use. - if _, ok := instr.Type().Underlying().(*types.Struct); ok { - return i, errors.New("todo: init: " + instr.String()) - } - locals[instr] = x - case *ssa.Convert: - x, err := p.getValue(instr.X, locals) - if err != nil { - return i, err - } - typeFrom := instr.X.Type().Underlying() - switch typeTo := instr.Type().Underlying().(type) { - case *types.Basic: - if typeTo.Kind() == types.String { - return i, nil - } - - if _, ok := typeFrom.(*types.Pointer); ok && typeTo.Kind() == types.UnsafePointer { - locals[instr] = &PointerBitCastValue{typeTo, x} - } else if typeFrom, ok := typeFrom.(*types.Basic); ok { - if typeFrom.Kind() == types.UnsafePointer && typeTo.Kind() == types.Uintptr { - locals[instr] = &PointerToUintptrValue{x} - } else if typeFrom.Info()&types.IsInteger != 0 && typeTo.Info()&types.IsInteger != 0 { - locals[instr] = &ConstValue{Expr: ssa.NewConst(x.(*ConstValue).Expr.Value, typeTo)} - } else { - return i, nil - } - } else { - return i, nil - } - case *types.Pointer: - if typeFrom, ok := typeFrom.(*types.Basic); ok && typeFrom.Kind() == types.UnsafePointer { - locals[instr] = &PointerBitCastValue{typeTo, x} - } else { - panic("expected unsafe pointer conversion") - } - default: - return i, nil - } - case *ssa.DebugRef: - // ignore - case *ssa.Extract: - tuple, err := p.getValue(instr.Tuple, locals) - if err != nil { - return i, err - } - locals[instr] = tuple.(*StructValue).Fields[instr.Index] - case *ssa.FieldAddr: - x, err := p.getValue(instr.X, locals) - if err != nil { - return i, err - } - var structVal *StructValue - switch x := x.(type) { - case *GlobalValue: - structVal = x.Global.initializer.(*StructValue) - case *PointerValue: - structVal = (*x.Elem).(*StructValue) - default: - panic("expected a pointer") - } - locals[instr] = &PointerValue{nil, &structVal.Fields[instr.Field]} - case *ssa.IndexAddr: - x, err := p.getValue(instr.X, locals) - if err != nil { - return i, err - } - if cnst, ok := instr.Index.(*ssa.Const); ok { - index, _ := constant.Int64Val(cnst.Value) - switch xPtr := x.(type) { - case *GlobalValue: - x = xPtr.Global.initializer - case *PointerValue: - x = *xPtr.Elem - default: - panic("expected a pointer") - } - switch x := x.(type) { - case *ArrayValue: - locals[instr] = &PointerValue{nil, &x.Elems[index]} - default: - return i, errors.New("todo: init IndexAddr not on an array or struct") - } - } else { - return i, errors.New("todo: init IndexAddr index: " + instr.Index.String()) - } - case *ssa.MakeMap: - locals[instr] = &MapValue{instr.Type().Underlying().(*types.Map), nil, nil} - case *ssa.MapUpdate: - // Assume no duplicate keys exist. This is most likely true for - // autogenerated code, but may not be true when trying to interpret - // user code. - key, err := p.getValue(instr.Key, locals) - if err != nil { - return i, err - } - value, err := p.getValue(instr.Value, locals) - if err != nil { - return i, err - } - x := locals[instr.Map].(*MapValue) - x.Keys = append(x.Keys, key) - x.Values = append(x.Values, value) - case *ssa.Return: - for i, r := range instr.Results { - val, err := p.getValue(r, locals) - if err != nil { - return i, err - } - results[i] = val - } - case *ssa.Slice: - // Turn a just-allocated array into a slice. - if instr.Low != nil || instr.High != nil || instr.Max != nil { - return i, errors.New("init: slice expression with bounds") - } - source, err := p.getValue(instr.X, locals) - if err != nil { - return i, err - } - switch source := source.(type) { - case *PointerValue: // pointer to array - array := (*source.Elem).(*ArrayValue) - locals[instr] = &SliceValue{instr.Type().Underlying().(*types.Slice), array} - default: - return i, errors.New("init: unknown slice type") - } - case *ssa.Store: - if addr, ok := instr.Addr.(*ssa.Global); ok { - if strings.HasPrefix(instr.Addr.Name(), "__cgofn__cgo_") || strings.HasPrefix(instr.Addr.Name(), "_cgo_") { - // Ignore CGo global variables which we don't use. - continue - } - value, err := p.getValue(instr.Val, locals) - if err != nil { - return i, err - } - p.GetGlobal(addr).initializer = value - } else if addr, ok := locals[instr.Addr]; ok { - value, err := p.getValue(instr.Val, locals) - if err != nil { - return i, err - } - if addr, ok := addr.(*PointerValue); ok { - *(addr.Elem) = value - } else { - panic("store to non-pointer") - } - } else { - return i, errors.New("todo: init Store: " + instr.String()) - } - case *ssa.UnOp: - if instr.Op != token.MUL || instr.CommaOk { - return i, errors.New("init: unknown unop: " + instr.String()) - } - valPtr, err := p.getValue(instr.X, locals) - if err != nil { - return i, err - } - switch valPtr := valPtr.(type) { - case *GlobalValue: - locals[instr] = valPtr.Global.initializer - case *PointerValue: - locals[instr] = *valPtr.Elem - default: - panic("expected a pointer") - } - default: - return i, nil - } - } - return len(instrs), nil -} - -// Check whether this function can be interpreted at compile time. For that, it -// needs to only contain relatively simple instructions (for example, no control -// flow). -func canInterpret(callee *ssa.Function) bool { - if len(callee.Blocks) != 1 || callee.Signature.Results().Len() != 1 { - // No control flow supported so only one basic block. - // Only exactly one return value supported right now so check that as - // well. - return false - } - for _, instr := range callee.Blocks[0].Instrs { - switch instr.(type) { - // Ignore all functions fully supported by Program.interpret() - // above. - case *ssa.Alloc: - case *ssa.ChangeType: - case *ssa.DebugRef: - case *ssa.Extract: - case *ssa.FieldAddr: - case *ssa.IndexAddr: - case *ssa.MakeMap: - case *ssa.MapUpdate: - case *ssa.Return: - case *ssa.Slice: - case *ssa.Store: - case *ssa.UnOp: - default: - return false - } - } - return true -} - -func (p *Program) getValue(value ssa.Value, locals map[ssa.Value]Value) (Value, error) { - switch value := value.(type) { - case *ssa.Const: - return &ConstValue{value}, nil - case *ssa.Function: - return &FunctionValue{value.Type(), value}, nil - case *ssa.Global: - if strings.HasPrefix(value.Name(), "__cgofn__cgo_") || strings.HasPrefix(value.Name(), "_cgo_") { - // Ignore CGo global variables which we don't use. - return nil, ErrCGoWrapper - } - g := p.GetGlobal(value) - if g.initializer == nil { - value, err := p.getZeroValue(value.Type().Underlying().(*types.Pointer).Elem()) - if err != nil { - return nil, err - } - g.initializer = value - } - return &GlobalValue{g}, nil - default: - if local, ok := locals[value]; ok { - return local, nil - } else { - return nil, errors.New("todo: init: unknown value: " + value.String()) - } - } -} - -func (p *Program) getZeroValue(t types.Type) (Value, error) { - switch typ := t.Underlying().(type) { - case *types.Array: - elems := make([]Value, typ.Len()) - for i := range elems { - elem, err := p.getZeroValue(typ.Elem()) - if err != nil { - return nil, err - } - elems[i] = elem - } - return &ArrayValue{typ.Elem(), elems}, nil - case *types.Basic: - return &ZeroBasicValue{typ}, nil - case *types.Signature: - return &FunctionValue{typ, nil}, nil - case *types.Map: - return &MapValue{typ, nil, nil}, nil - case *types.Pointer: - return &PointerValue{typ, nil}, nil - case *types.Struct: - elems := make([]Value, typ.NumFields()) - for i := range elems { - elem, err := p.getZeroValue(typ.Field(i).Type()) - if err != nil { - return nil, err - } - elems[i] = elem - } - return &StructValue{t, elems}, nil - case *types.Slice: - return &SliceValue{typ, nil}, nil - default: - return nil, errors.New("todo: init: unknown global type: " + typ.String()) - } -} - -// Boxed value for interpreter. -type Value interface { -} - -type ConstValue struct { - Expr *ssa.Const -} - -type ZeroBasicValue struct { - Type *types.Basic -} - -type PointerValue struct { - Type types.Type - Elem *Value -} - -type FunctionValue struct { - Type types.Type - Elem *ssa.Function -} - -type PointerBitCastValue struct { - Type types.Type - Elem Value -} - -type PointerToUintptrValue struct { - Elem Value -} - -type GlobalValue struct { - Global *Global -} - -type ArrayValue struct { - ElemType types.Type - Elems []Value -} - -type StructValue struct { - Type types.Type // types.Struct or types.Named - Fields []Value -} - -type SliceValue struct { - Type *types.Slice - Array *ArrayValue -} - -type MapValue struct { - Type *types.Map - Keys []Value - Values []Value -} diff --git a/ir/ir.go b/ir/ir.go index 9f663424..76758fbc 100644 --- a/ir/ir.go +++ b/ir/ir.go @@ -43,11 +43,10 @@ type Function struct { // Global variable, possibly constant. type Global struct { *ssa.Global - program *Program - LLVMGlobal llvm.Value - linkName string // go:extern - extern bool // go:extern - initializer Value + program *Program + LLVMGlobal llvm.Value + linkName string // go:extern + extern bool // go:extern } // Type with a name and possibly methods. @@ -416,10 +415,6 @@ func (g *Global) CName() string { return "" } -func (g *Global) Initializer() Value { - return g.initializer -} - // Return true if this named type is annotated with the //go:volatile pragma, // for volatile loads and stores. func (p *Program) IsVolatile(t types.Type) bool { diff --git a/main.go b/main.go index 64e8b913..9e8f1d9a 100644 --- a/main.go +++ b/main.go @@ -47,7 +47,6 @@ type BuildConfig struct { dumpSSA bool debug bool printSizes string - initInterp bool cFlags []string ldFlags []string wasmAbi string @@ -64,19 +63,18 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act spec.LDFlags = append(spec.LDFlags, config.ldFlags...) compilerConfig := compiler.Config{ - Triple: spec.Triple, - CPU: spec.CPU, - GOOS: spec.GOOS, - GOARCH: spec.GOARCH, - GC: config.gc, - CFlags: spec.CFlags, - LDFlags: spec.LDFlags, - Debug: config.debug, - DumpSSA: config.dumpSSA, - RootDir: sourceDir(), - GOPATH: getGopath(), - BuildTags: spec.BuildTags, - InitInterp: config.initInterp, + Triple: spec.Triple, + CPU: spec.CPU, + GOOS: spec.GOOS, + GOARCH: spec.GOARCH, + GC: config.gc, + CFlags: spec.CFlags, + LDFlags: spec.LDFlags, + Debug: config.debug, + DumpSSA: config.dumpSSA, + RootDir: sourceDir(), + GOPATH: getGopath(), + BuildTags: spec.BuildTags, } c, err := compiler.NewCompiler(pkgName, compilerConfig) if err != nil { @@ -96,14 +94,12 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act return errors.New("verification error after IR construction") } - if config.initInterp { - err = interp.Run(c.Module(), c.TargetData(), config.dumpSSA) - if err != nil { - return err - } - if err := c.Verify(); err != nil { - return errors.New("verification error after interpreting runtime.initAll") - } + err = interp.Run(c.Module(), c.TargetData(), config.dumpSSA) + if err != nil { + return err + } + if err := c.Verify(); err != nil { + return errors.New("verification error after interpreting runtime.initAll") } c.ApplyFunctionSections() // -ffunction-sections @@ -501,7 +497,6 @@ func main() { printSize := flag.String("size", "", "print sizes (none, short, full)") nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation") ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug") - initInterp := flag.Bool("initinterp", true, "enable/disable partial evaluator of generated IR") port := flag.String("port", "/dev/ttyACM0", "flash port") cFlags := flag.String("cflags", "", "additional cflags for compiler") ldFlags := flag.String("ldflags", "", "additional ldflags for linker") @@ -522,7 +517,6 @@ func main() { dumpSSA: *dumpSSA, debug: !*nodebug, printSizes: *printSize, - initInterp: *initInterp, wasmAbi: *wasmAbi, } diff --git a/main_test.go b/main_test.go index f1e164d8..6530f060 100644 --- a/main_test.go +++ b/main_test.go @@ -103,7 +103,6 @@ func runTest(path, tmpdir string, target string, t *testing.T) { dumpSSA: false, debug: false, printSizes: "", - initInterp: true, } binary := filepath.Join(tmpdir, "test") err = Build("./"+path, binary, target, config)