github.com/aykevl/tinygo@v0.5.0/interp/interp.go (about)

     1  // Package interp interprets Go package initializers as much as possible. This
     2  // avoid running them at runtime, improving code size and making other
     3  // optimizations possible.
     4  package interp
     5  
     6  // This file provides the overarching Eval object with associated (utility)
     7  // methods.
     8  
     9  import (
    10  	"errors"
    11  	"strings"
    12  
    13  	"tinygo.org/x/go-llvm"
    14  )
    15  
    16  type Eval struct {
    17  	Mod             llvm.Module
    18  	TargetData      llvm.TargetData
    19  	Debug           bool
    20  	builder         llvm.Builder
    21  	dibuilder       *llvm.DIBuilder
    22  	dirtyGlobals    map[llvm.Value]struct{}
    23  	sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results
    24  }
    25  
    26  // Run evaluates the function with the given name and then eliminates all
    27  // callers.
    28  func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
    29  	if debug {
    30  		println("\ncompile-time evaluation:")
    31  	}
    32  
    33  	name := "runtime.initAll"
    34  	e := &Eval{
    35  		Mod:          mod,
    36  		TargetData:   targetData,
    37  		Debug:        debug,
    38  		dirtyGlobals: map[llvm.Value]struct{}{},
    39  	}
    40  	e.builder = mod.Context().NewBuilder()
    41  	e.dibuilder = llvm.NewDIBuilder(mod)
    42  
    43  	initAll := mod.NamedFunction(name)
    44  	bb := initAll.EntryBasicBlock()
    45  	// Create a dummy alloca in the entry block that we can set the insert point
    46  	// to. This is necessary because otherwise we might be removing the
    47  	// instruction (init call) that we are removing after successful
    48  	// interpretation.
    49  	e.builder.SetInsertPointBefore(bb.FirstInstruction())
    50  	dummy := e.builder.CreateAlloca(e.Mod.Context().Int8Type(), "dummy")
    51  	e.builder.SetInsertPointBefore(dummy)
    52  	e.builder.SetInstDebugLocation(bb.FirstInstruction())
    53  	var initCalls []llvm.Value
    54  	for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
    55  		if inst == dummy {
    56  			continue
    57  		}
    58  		if !inst.IsAReturnInst().IsNil() {
    59  			break // ret void
    60  		}
    61  		if inst.IsACallInst().IsNil() || inst.CalledValue().IsAFunction().IsNil() {
    62  			return errors.New("expected all instructions in " + name + " to be direct calls")
    63  		}
    64  		initCalls = append(initCalls, inst)
    65  	}
    66  
    67  	// Do this in a separate step to avoid corrupting the iterator above.
    68  	undefPtr := llvm.Undef(llvm.PointerType(mod.Context().Int8Type(), 0))
    69  	for _, call := range initCalls {
    70  		initName := call.CalledValue().Name()
    71  		if !strings.HasSuffix(initName, ".init") {
    72  			return errors.New("expected all instructions in " + name + " to be *.init() calls")
    73  		}
    74  		pkgName := initName[:len(initName)-5]
    75  		fn := call.CalledValue()
    76  		call.EraseFromParentAsInstruction()
    77  		_, err := e.Function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, pkgName)
    78  		if err == ErrUnreachable {
    79  			break
    80  		}
    81  		if err != nil {
    82  			return err
    83  		}
    84  	}
    85  
    86  	return nil
    87  }
    88  
    89  func (e *Eval) Function(fn llvm.Value, params []Value, pkgName string) (Value, error) {
    90  	return e.function(fn, params, pkgName, "")
    91  }
    92  
    93  func (e *Eval) function(fn llvm.Value, params []Value, pkgName, indent string) (Value, error) {
    94  	fr := frame{
    95  		Eval:    e,
    96  		fn:      fn,
    97  		pkgName: pkgName,
    98  		locals:  make(map[llvm.Value]Value),
    99  	}
   100  	for i, param := range fn.Params() {
   101  		fr.locals[param] = params[i]
   102  	}
   103  
   104  	bb := fn.EntryBasicBlock()
   105  	var lastBB llvm.BasicBlock
   106  	for {
   107  		retval, outgoing, err := fr.evalBasicBlock(bb, lastBB, indent)
   108  		if outgoing == nil {
   109  			// returned something (a value or void, or an error)
   110  			return retval, err
   111  		}
   112  		if len(outgoing) > 1 {
   113  			panic("unimplemented: multiple outgoing blocks")
   114  		}
   115  		next := outgoing[0]
   116  		if next.IsABasicBlock().IsNil() {
   117  			panic("did not switch to a basic block")
   118  		}
   119  		lastBB = bb
   120  		bb = next.AsBasicBlock()
   121  	}
   122  }
   123  
   124  // getValue determines what kind of LLVM value it gets and returns the
   125  // appropriate Value type.
   126  func (e *Eval) getValue(v llvm.Value) Value {
   127  	return &LocalValue{e, v}
   128  }
   129  
   130  // markDirty marks the passed-in LLVM value dirty, recursively. For example,
   131  // when it encounters a constant GEP on a global, it marks the global dirty.
   132  func (e *Eval) markDirty(v llvm.Value) {
   133  	if !v.IsAGlobalVariable().IsNil() {
   134  		if v.IsGlobalConstant() {
   135  			return
   136  		}
   137  		if _, ok := e.dirtyGlobals[v]; !ok {
   138  			e.dirtyGlobals[v] = struct{}{}
   139  			e.sideEffectFuncs = nil // re-calculate all side effects
   140  		}
   141  	} else if v.IsConstant() {
   142  		if v.OperandsCount() >= 2 && !v.Operand(0).IsAGlobalVariable().IsNil() {
   143  			// looks like a constant getelementptr of a global.
   144  			// TODO: find a way to make sure it really is: v.Opcode() returns 0.
   145  			e.markDirty(v.Operand(0))
   146  			return
   147  		}
   148  		return // nothing to mark
   149  	} else if !v.IsAGetElementPtrInst().IsNil() {
   150  		panic("interp: todo: GEP")
   151  	} else {
   152  		// Not constant and not a global or GEP so doesn't have to be marked
   153  		// non-constant.
   154  	}
   155  }