tinygo/interpreter.go
Ayke van Laethem c25b448758
Rewrite init() interpretation to a real interpreter
Instead of mostly heuristics, actually execute the init() instruction in
an interpreter to calculate initializers for globals. This is far more
flexible and extensible, and gives the option of extending the
interpreter to other code and make it a partial evaluator.
2018-08-25 02:07:01 +02:00

294 строки
7,7 КиБ
Go

package main
// This file provides functionality to interpret very basic Go SSA, for
// compile-time initialization of globals.
import (
"errors"
"go/constant"
"go/token"
"go/types"
"strings"
"golang.org/x/tools/go/ssa"
)
// Interpret instructions as far as possible, and drop those instructions from
// the basic block.
func (p *Program) Interpret(block *ssa.BasicBlock) error {
for {
i, err := p.interpret(block.Instrs)
if err == cgoWrapperError {
// 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) (int, error) {
locals := map[ssa.Value]Value{}
for i, instr := range instrs {
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{&alloc}
case *ssa.Convert:
x, err := p.getValue(instr.X, locals)
if err != nil {
return i, err
}
switch typ := instr.Type().Underlying().(type) {
case *types.Basic:
if _, ok := instr.X.Type().Underlying().(*types.Pointer); ok && typ.Kind() == types.UnsafePointer {
locals[instr] = &PointerBitCastValue{typ, x}
} else if xtyp, ok := instr.X.Type().Underlying().(*types.Basic); ok && xtyp.Kind() == types.UnsafePointer && typ.Kind() == types.Uintptr {
locals[instr] = &PointerToUintptrValue{x}
} else {
return i, errors.New("todo: init: unknown basic convert: " + instr.String())
}
case *types.Pointer:
if xtyp, ok := instr.X.Type().Underlying().(*types.Basic); ok && xtyp.Kind() == types.UnsafePointer {
locals[instr] = &PointerBitCastValue{typ, x}
} else {
panic("expected unsafe pointer conversion")
}
default:
return i, errors.New("todo: init: unknown convert: " + instr.String())
}
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{&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{&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.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")
}
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.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())
}
default:
return i, nil
}
}
return len(instrs), nil // normally unreachable
}
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.Global:
if strings.HasPrefix(value.Name(), "__cgofn__cgo_") || strings.HasPrefix(value.Name(), "_cgo_") {
// Ignore CGo global variables which we don't use.
return nil, cgoWrapperError
}
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
//return &PointerValue{&g.initializer}, 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.Pointer:
return &PointerValue{nil}, nil
case *types.Struct:
elems := make([]Value, typ.NumFields())
for i := range elems {
elem, err := p.getZeroValue(typ.Field(i).Type().Underlying())
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 {
Elem *Value
}
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
}