tinygo/interp/interp.go
Ayke van Laethem 602c264749
all: rewrite goroutine lowering
Before this commit, goroutine support was spread through the compiler.
This commit changes this support, so that the compiler itself only
generates simple intrinsics and leaves the real support to a compiler
pass that runs as one of the TinyGo-specific optimization passes.

The biggest change, that was done together with the rewrite, was support
for goroutines in WebAssembly for JavaScript. The challenge in
JavaScript is that in general no blocking operations are allowed, which
means that programs that call time.Sleep() but do not start goroutines
also have to be scheduled by the scheduler.
2019-01-21 22:09:33 +01:00

149 строки
4,2 КиБ
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.
undefPtr := llvm.Undef(llvm.PointerType(mod.Context().Int8Type(), 0))
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(), []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, pkgName)
if err == ErrUnreachable {
break
}
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.
}
}