
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.
294 строки
7,7 КиБ
Go
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
|
|
}
|