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.
Этот коммит содержится в:
Ayke van Laethem 2019-02-09 13:31:51 +01:00 коммит произвёл Ron Evans
родитель da345e8723
коммит 92d9b780b5
6 изменённых файлов: 46 добавлений и 917 удалений

Просмотреть файл

@ -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())

Просмотреть файл

@ -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 {

Просмотреть файл

@ -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
}

Просмотреть файл

@ -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 {

42
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,
}

Просмотреть файл

@ -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)