
This interpreter currently complements the Go SSA level interpreter. It may stay complementary or may be the only interpreter in the future. This interpreter is experimental and not yet finished (there are known bugs!) so it is disabled by default. It can be enabled by passing the -initinterp flag. The goal is to be able to run all initializations at compile time except for the ones having side effects. This mostly works except perhaps for a few edge cases. In the future, this interpeter may be used to actually run regular Go code, perhaps in a shell.
145 строки
4,1 КиБ
Go
145 строки
4,1 КиБ
Go
// Package interp interprets Go package initializers as much as possible. This
|
|
// avoid running them at runtime, improving code size and making other
|
|
// optimizations possible.
|
|
package interp
|
|
|
|
// This file provides the overarching Eval object with associated (utility)
|
|
// methods.
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
|
|
"github.com/aykevl/go-llvm"
|
|
)
|
|
|
|
type Eval struct {
|
|
Mod llvm.Module
|
|
TargetData llvm.TargetData
|
|
Debug bool
|
|
builder llvm.Builder
|
|
dibuilder *llvm.DIBuilder
|
|
dirtyGlobals map[llvm.Value]struct{}
|
|
sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results
|
|
}
|
|
|
|
// Run evaluates the function with the given name and then eliminates all
|
|
// callers.
|
|
func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
|
|
if debug {
|
|
println("\ncompile-time evaluation:")
|
|
}
|
|
|
|
name := "runtime.initAll"
|
|
e := &Eval{
|
|
Mod: mod,
|
|
TargetData: targetData,
|
|
Debug: debug,
|
|
dirtyGlobals: map[llvm.Value]struct{}{},
|
|
}
|
|
e.builder = mod.Context().NewBuilder()
|
|
e.dibuilder = llvm.NewDIBuilder(mod)
|
|
|
|
initAll := mod.NamedFunction(name)
|
|
bb := initAll.EntryBasicBlock()
|
|
e.builder.SetInsertPointBefore(bb.LastInstruction())
|
|
e.builder.SetInstDebugLocation(bb.FirstInstruction())
|
|
var initCalls []llvm.Value
|
|
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
|
if !inst.IsAReturnInst().IsNil() {
|
|
break // ret void
|
|
}
|
|
if inst.IsACallInst().IsNil() || inst.CalledValue().IsAFunction().IsNil() {
|
|
return errors.New("expected all instructions in " + name + " to be direct calls")
|
|
}
|
|
initCalls = append(initCalls, inst)
|
|
}
|
|
|
|
// Do this in a separate step to avoid corrupting the iterator above.
|
|
for _, call := range initCalls {
|
|
initName := call.CalledValue().Name()
|
|
if !strings.HasSuffix(initName, ".init") {
|
|
return errors.New("expected all instructions in " + name + " to be *.init() calls")
|
|
}
|
|
pkgName := initName[:len(initName)-5]
|
|
_, err := e.Function(call.CalledValue(), nil, pkgName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
call.EraseFromParentAsInstruction()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (e *Eval) Function(fn llvm.Value, params []Value, pkgName string) (Value, error) {
|
|
return e.function(fn, params, pkgName, "")
|
|
}
|
|
|
|
func (e *Eval) function(fn llvm.Value, params []Value, pkgName, indent string) (Value, error) {
|
|
fr := frame{
|
|
Eval: e,
|
|
fn: fn,
|
|
pkgName: pkgName,
|
|
locals: make(map[llvm.Value]Value),
|
|
}
|
|
for i, param := range fn.Params() {
|
|
fr.locals[param] = params[i]
|
|
}
|
|
|
|
bb := fn.EntryBasicBlock()
|
|
var lastBB llvm.BasicBlock
|
|
for {
|
|
retval, outgoing, err := fr.evalBasicBlock(bb, lastBB, indent)
|
|
if outgoing == nil {
|
|
// returned something (a value or void, or an error)
|
|
return retval, err
|
|
}
|
|
if len(outgoing) > 1 {
|
|
panic("unimplemented: multiple outgoing blocks")
|
|
}
|
|
next := outgoing[0]
|
|
if next.IsABasicBlock().IsNil() {
|
|
panic("did not switch to a basic block")
|
|
}
|
|
lastBB = bb
|
|
bb = next.AsBasicBlock()
|
|
}
|
|
}
|
|
|
|
// getValue determines what kind of LLVM value it gets and returns the
|
|
// appropriate Value type.
|
|
func (e *Eval) getValue(v llvm.Value) Value {
|
|
if !v.IsAGlobalVariable().IsNil() {
|
|
return &GlobalValue{e, v}
|
|
} else {
|
|
return &LocalValue{e, v}
|
|
}
|
|
}
|
|
|
|
// markDirty marks the passed-in LLVM value dirty, recursively. For example,
|
|
// when it encounters a constant GEP on a global, it marks the global dirty.
|
|
func (e *Eval) markDirty(v llvm.Value) {
|
|
if !v.IsAGlobalVariable().IsNil() {
|
|
if v.IsGlobalConstant() {
|
|
return
|
|
}
|
|
if _, ok := e.dirtyGlobals[v]; !ok {
|
|
e.dirtyGlobals[v] = struct{}{}
|
|
e.sideEffectFuncs = nil // re-calculate all side effects
|
|
}
|
|
} else if v.IsConstant() {
|
|
if v.OperandsCount() >= 2 && !v.Operand(0).IsAGlobalVariable().IsNil() {
|
|
// looks like a constant getelementptr of a global.
|
|
// TODO: find a way to make sure it really is: v.Opcode() returns 0.
|
|
e.markDirty(v.Operand(0))
|
|
return
|
|
}
|
|
return // nothing to mark
|
|
} else if !v.IsAGetElementPtrInst().IsNil() {
|
|
panic("interp: todo: GEP")
|
|
} else {
|
|
// Not constant and not a global or GEP so doesn't have to be marked
|
|
// non-constant.
|
|
}
|
|
}
|