github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/interp/interp.go (about)

     1  // Package interp is a partial evaluator of code run at package init time. See
     2  // the README in this package for details.
     3  package interp
     4  
     5  import (
     6  	"fmt"
     7  	"os"
     8  	"strings"
     9  	"time"
    10  
    11  	"tinygo.org/x/go-llvm"
    12  )
    13  
    14  // Enable extra checks, which should be disabled by default.
    15  // This may help track down bugs by adding a few more sanity checks.
    16  const checks = true
    17  
    18  // runner contains all state related to one interp run.
    19  type runner struct {
    20  	mod           llvm.Module
    21  	targetData    llvm.TargetData
    22  	builder       llvm.Builder
    23  	pointerSize   uint32                   // cached pointer size from the TargetData
    24  	dataPtrType   llvm.Type                // often used type so created in advance
    25  	uintptrType   llvm.Type                // equivalent to uintptr in Go
    26  	maxAlign      int                      // maximum alignment of an object, alignment of runtime.alloc() result
    27  	debug         bool                     // log debug messages
    28  	pkgName       string                   // package name of the currently executing package
    29  	functionCache map[llvm.Value]*function // cache of compiled functions
    30  	objects       []object                 // slice of objects in memory
    31  	globals       map[llvm.Value]int       // map from global to index in objects slice
    32  	start         time.Time
    33  	timeout       time.Duration
    34  	callsExecuted uint64
    35  }
    36  
    37  func newRunner(mod llvm.Module, timeout time.Duration, debug bool) *runner {
    38  	r := runner{
    39  		mod:           mod,
    40  		targetData:    llvm.NewTargetData(mod.DataLayout()),
    41  		debug:         debug,
    42  		functionCache: make(map[llvm.Value]*function),
    43  		objects:       []object{{}},
    44  		globals:       make(map[llvm.Value]int),
    45  		start:         time.Now(),
    46  		timeout:       timeout,
    47  	}
    48  	r.pointerSize = uint32(r.targetData.PointerSize())
    49  	r.dataPtrType = llvm.PointerType(mod.Context().Int8Type(), 0)
    50  	r.uintptrType = mod.Context().IntType(r.targetData.PointerSize() * 8)
    51  	r.maxAlign = r.targetData.PrefTypeAlignment(r.dataPtrType) // assume pointers are maximally aligned (this is not always the case)
    52  	return &r
    53  }
    54  
    55  // Dispose deallocates all alloated LLVM resources.
    56  func (r *runner) dispose() {
    57  	r.targetData.Dispose()
    58  	r.targetData = llvm.TargetData{}
    59  }
    60  
    61  // Run evaluates runtime.initAll function as much as possible at compile time.
    62  // Set debug to true if it should print output while running.
    63  func Run(mod llvm.Module, timeout time.Duration, debug bool) error {
    64  	r := newRunner(mod, timeout, debug)
    65  	defer r.dispose()
    66  
    67  	initAll := mod.NamedFunction("runtime.initAll")
    68  	bb := initAll.EntryBasicBlock()
    69  
    70  	// Create a builder, to insert instructions that could not be evaluated at
    71  	// compile time.
    72  	r.builder = mod.Context().NewBuilder()
    73  	defer r.builder.Dispose()
    74  
    75  	// Create a dummy alloca in the entry block that we can set the insert point
    76  	// to. This is necessary because otherwise we might be removing the
    77  	// instruction (init call) that we are removing after successful
    78  	// interpretation.
    79  	r.builder.SetInsertPointBefore(bb.FirstInstruction())
    80  	dummy := r.builder.CreateAlloca(r.mod.Context().Int8Type(), "dummy")
    81  	r.builder.SetInsertPointBefore(dummy)
    82  	defer dummy.EraseFromParentAsInstruction()
    83  
    84  	// Get a list if init calls. A runtime.initAll might look something like this:
    85  	// func initAll() {
    86  	//     unsafe.init()
    87  	//     machine.init()
    88  	//     runtime.init()
    89  	// }
    90  	// This function gets a list of these call instructions.
    91  	var initCalls []llvm.Value
    92  	for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
    93  		if inst == dummy {
    94  			continue
    95  		}
    96  		if !inst.IsAReturnInst().IsNil() {
    97  			break // ret void
    98  		}
    99  		if inst.IsACallInst().IsNil() || inst.CalledValue().IsAFunction().IsNil() {
   100  			return errorAt(inst, "interp: expected all instructions in "+initAll.Name()+" to be direct calls")
   101  		}
   102  		initCalls = append(initCalls, inst)
   103  	}
   104  
   105  	// Run initializers for each package. Once the package initializer is
   106  	// finished, the call to the package initializer can be removed.
   107  	for _, call := range initCalls {
   108  		initName := call.CalledValue().Name()
   109  		if !strings.HasSuffix(initName, ".init") {
   110  			return errorAt(call, "interp: expected all instructions in "+initAll.Name()+" to be *.init() calls")
   111  		}
   112  		r.pkgName = initName[:len(initName)-len(".init")]
   113  		fn := call.CalledValue()
   114  		if r.debug {
   115  			fmt.Fprintln(os.Stderr, "call:", fn.Name())
   116  		}
   117  		_, mem, callErr := r.run(r.getFunction(fn), nil, nil, "    ")
   118  		call.EraseFromParentAsInstruction()
   119  		if callErr != nil {
   120  			if isRecoverableError(callErr.Err) {
   121  				if r.debug {
   122  					fmt.Fprintln(os.Stderr, "not interpreting", r.pkgName, "because of error:", callErr.Error())
   123  				}
   124  				// Remove instructions that were created as part of interpreting
   125  				// the package.
   126  				mem.revert()
   127  				// Create a call to the package initializer (which was
   128  				// previously deleted).
   129  				i8undef := llvm.Undef(r.dataPtrType)
   130  				r.builder.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{i8undef}, "")
   131  				// Make sure that any globals touched by the package
   132  				// initializer, won't be accessed by later package initializers.
   133  				err := r.markExternalLoad(fn)
   134  				if err != nil {
   135  					return fmt.Errorf("failed to interpret package %s: %w", r.pkgName, err)
   136  				}
   137  				continue
   138  			}
   139  			return callErr
   140  		}
   141  		for index, obj := range mem.objects {
   142  			r.objects[index] = obj
   143  		}
   144  	}
   145  	r.pkgName = ""
   146  
   147  	// Update all global variables in the LLVM module.
   148  	mem := memoryView{r: r}
   149  	for i, obj := range r.objects {
   150  		if obj.llvmGlobal.IsNil() {
   151  			continue
   152  		}
   153  		if obj.buffer == nil {
   154  			continue
   155  		}
   156  		if obj.constant {
   157  			continue // constant buffers can't have been modified
   158  		}
   159  		initializer, err := obj.buffer.toLLVMValue(obj.llvmGlobal.GlobalValueType(), &mem)
   160  		if err == errInvalidPtrToIntSize {
   161  			// This can happen when a previous interp run did not have the
   162  			// correct LLVM type for a global and made something up. In that
   163  			// case, some fields could be written out as a series of (null)
   164  			// bytes even though they actually contain a pointer value.
   165  			// As a fallback, use asRawValue to get something of the correct
   166  			// memory layout.
   167  			initializer, err := obj.buffer.asRawValue(r).rawLLVMValue(&mem)
   168  			if err != nil {
   169  				return err
   170  			}
   171  			initializerType := initializer.Type()
   172  			newGlobal := llvm.AddGlobal(mod, initializerType, obj.llvmGlobal.Name()+".tmp")
   173  			newGlobal.SetInitializer(initializer)
   174  			newGlobal.SetLinkage(obj.llvmGlobal.Linkage())
   175  			newGlobal.SetAlignment(obj.llvmGlobal.Alignment())
   176  			// TODO: copy debug info, unnamed_addr, ...
   177  			obj.llvmGlobal.ReplaceAllUsesWith(newGlobal)
   178  			name := obj.llvmGlobal.Name()
   179  			obj.llvmGlobal.EraseFromParentAsGlobal()
   180  			newGlobal.SetName(name)
   181  
   182  			// Update interp-internal references.
   183  			delete(r.globals, obj.llvmGlobal)
   184  			obj.llvmGlobal = newGlobal
   185  			r.globals[newGlobal] = i
   186  			r.objects[i] = obj
   187  			continue
   188  		}
   189  		if err != nil {
   190  			return err
   191  		}
   192  		if checks && initializer.Type() != obj.llvmGlobal.GlobalValueType() {
   193  			panic("initializer type mismatch")
   194  		}
   195  		obj.llvmGlobal.SetInitializer(initializer)
   196  	}
   197  
   198  	return nil
   199  }
   200  
   201  // RunFunc evaluates a single package initializer at compile time.
   202  // Set debug to true if it should print output while running.
   203  func RunFunc(fn llvm.Value, timeout time.Duration, debug bool) error {
   204  	// Create and initialize *runner object.
   205  	mod := fn.GlobalParent()
   206  	r := newRunner(mod, timeout, debug)
   207  	defer r.dispose()
   208  	initName := fn.Name()
   209  	if !strings.HasSuffix(initName, ".init") {
   210  		return errorAt(fn, "interp: unexpected function name (expected *.init)")
   211  	}
   212  	r.pkgName = initName[:len(initName)-len(".init")]
   213  
   214  	// Create new function with the interp result.
   215  	newFn := llvm.AddFunction(mod, fn.Name()+".tmp", fn.GlobalValueType())
   216  	newFn.SetLinkage(fn.Linkage())
   217  	newFn.SetVisibility(fn.Visibility())
   218  	entry := mod.Context().AddBasicBlock(newFn, "entry")
   219  
   220  	// Create a builder, to insert instructions that could not be evaluated at
   221  	// compile time.
   222  	r.builder = mod.Context().NewBuilder()
   223  	defer r.builder.Dispose()
   224  	r.builder.SetInsertPointAtEnd(entry)
   225  
   226  	// Copy debug information.
   227  	subprogram := fn.Subprogram()
   228  	if !subprogram.IsNil() {
   229  		newFn.SetSubprogram(subprogram)
   230  		r.builder.SetCurrentDebugLocation(subprogram.SubprogramLine(), 0, subprogram, llvm.Metadata{})
   231  	}
   232  
   233  	// Run the initializer, filling the .init.tmp function.
   234  	if r.debug {
   235  		fmt.Fprintln(os.Stderr, "interp:", fn.Name())
   236  	}
   237  	_, pkgMem, callErr := r.run(r.getFunction(fn), nil, nil, "    ")
   238  	if callErr != nil {
   239  		if isRecoverableError(callErr.Err) {
   240  			// Could not finish, but could recover from it.
   241  			if r.debug {
   242  				fmt.Fprintln(os.Stderr, "not interpreting", r.pkgName, "because of error:", callErr.Error())
   243  			}
   244  			newFn.EraseFromParentAsFunction()
   245  			return nil
   246  		}
   247  		return callErr
   248  	}
   249  	for index, obj := range pkgMem.objects {
   250  		r.objects[index] = obj
   251  	}
   252  
   253  	// Update globals with values determined while running the initializer above.
   254  	mem := memoryView{r: r}
   255  	for _, obj := range r.objects {
   256  		if obj.llvmGlobal.IsNil() {
   257  			continue
   258  		}
   259  		if obj.buffer == nil {
   260  			continue
   261  		}
   262  		if obj.constant {
   263  			continue // constant, so can't have been modified
   264  		}
   265  		initializer, err := obj.buffer.toLLVMValue(obj.llvmGlobal.GlobalValueType(), &mem)
   266  		if err != nil {
   267  			return err
   268  		}
   269  		if checks && initializer.Type() != obj.llvmGlobal.GlobalValueType() {
   270  			panic("initializer type mismatch")
   271  		}
   272  		obj.llvmGlobal.SetInitializer(initializer)
   273  	}
   274  
   275  	// Finalize: remove the old init function and replace it with the new
   276  	// (.init.tmp) function.
   277  	r.builder.CreateRetVoid()
   278  	fnName := fn.Name()
   279  	fn.ReplaceAllUsesWith(newFn)
   280  	fn.EraseFromParentAsFunction()
   281  	newFn.SetName(fnName)
   282  
   283  	return nil
   284  }
   285  
   286  // getFunction returns the compiled version of the given LLVM function. It
   287  // compiles the function if necessary and caches the result.
   288  func (r *runner) getFunction(llvmFn llvm.Value) *function {
   289  	if fn, ok := r.functionCache[llvmFn]; ok {
   290  		return fn
   291  	}
   292  	fn := r.compileFunction(llvmFn)
   293  	r.functionCache[llvmFn] = fn
   294  	return fn
   295  }
   296  
   297  // markExternalLoad marks the given llvmValue as being loaded externally. This
   298  // is primarily used to mark package initializers that could not be run at
   299  // compile time. As an example, a package initialize might store to a global
   300  // variable. Another package initializer might read from the same global
   301  // variable. By marking this function as being run at runtime, that load
   302  // instruction will need to be run at runtime instead of at compile time.
   303  func (r *runner) markExternalLoad(llvmValue llvm.Value) error {
   304  	mem := memoryView{r: r}
   305  	err := mem.markExternalLoad(llvmValue)
   306  	if err != nil {
   307  		return err
   308  	}
   309  	for index, obj := range mem.objects {
   310  		if obj.marked > r.objects[index].marked {
   311  			r.objects[index].marked = obj.marked
   312  		}
   313  	}
   314  	return nil
   315  }