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

     1  package interp
     2  
     3  import (
     4  	"tinygo.org/x/go-llvm"
     5  )
     6  
     7  type sideEffectSeverity int
     8  
     9  const (
    10  	sideEffectInProgress sideEffectSeverity = iota // computing side effects is in progress (for recursive functions)
    11  	sideEffectNone                                 // no side effects at all (pure)
    12  	sideEffectLimited                              // has side effects, but the effects are known
    13  	sideEffectAll                                  // has unknown side effects
    14  )
    15  
    16  // sideEffectResult contains the scan results after scanning a function for side
    17  // effects (recursively).
    18  type sideEffectResult struct {
    19  	severity        sideEffectSeverity
    20  	mentionsGlobals map[llvm.Value]struct{}
    21  }
    22  
    23  // hasSideEffects scans this function and all descendants, recursively. It
    24  // returns whether this function has side effects and if it does, which globals
    25  // it mentions anywhere in this function or any called functions.
    26  func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
    27  	switch fn.Name() {
    28  	case "runtime.alloc":
    29  		// Cannot be scanned but can be interpreted.
    30  		return &sideEffectResult{severity: sideEffectNone}
    31  	case "runtime.nanotime":
    32  		// Fixed value at compile time.
    33  		return &sideEffectResult{severity: sideEffectNone}
    34  	case "runtime._panic":
    35  		return &sideEffectResult{severity: sideEffectLimited}
    36  	case "runtime.interfaceImplements":
    37  		return &sideEffectResult{severity: sideEffectNone}
    38  	}
    39  	if e.sideEffectFuncs == nil {
    40  		e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
    41  	}
    42  	if se, ok := e.sideEffectFuncs[fn]; ok {
    43  		return se
    44  	}
    45  	result := &sideEffectResult{
    46  		severity:        sideEffectInProgress,
    47  		mentionsGlobals: map[llvm.Value]struct{}{},
    48  	}
    49  	e.sideEffectFuncs[fn] = result
    50  	dirtyLocals := map[llvm.Value]struct{}{}
    51  	for bb := fn.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
    52  		for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
    53  			if inst.IsAInstruction().IsNil() {
    54  				panic("not an instruction")
    55  			}
    56  
    57  			// Check for any globals mentioned anywhere in the function. Assume
    58  			// any mentioned globals may be read from or written to when
    59  			// executed, thus must be marked dirty with a call.
    60  			for i := 0; i < inst.OperandsCount(); i++ {
    61  				operand := inst.Operand(i)
    62  				if !operand.IsAGlobalVariable().IsNil() {
    63  					result.mentionsGlobals[operand] = struct{}{}
    64  				}
    65  			}
    66  
    67  			switch inst.InstructionOpcode() {
    68  			case llvm.IndirectBr, llvm.Invoke:
    69  				// Not emitted by the compiler.
    70  				panic("unknown instructions")
    71  			case llvm.Call:
    72  				child := inst.CalledValue()
    73  				if !child.IsAInlineAsm().IsNil() {
    74  					// Inline assembly. This most likely has side effects.
    75  					// Assume they're only limited side effects, similar to
    76  					// external function calls.
    77  					result.updateSeverity(sideEffectLimited)
    78  					continue
    79  				}
    80  				if child.IsAFunction().IsNil() {
    81  					// Indirect call?
    82  					// In any case, we can't know anything here about what it
    83  					// affects exactly so mark this function as invoking all
    84  					// possible side effects.
    85  					result.updateSeverity(sideEffectAll)
    86  					continue
    87  				}
    88  				if child.IsDeclaration() {
    89  					// External function call. Assume only limited side effects
    90  					// (no affected globals, etc.).
    91  					if e.hasLocalSideEffects(dirtyLocals, inst) {
    92  						result.updateSeverity(sideEffectLimited)
    93  					}
    94  					continue
    95  				}
    96  				childSideEffects := e.hasSideEffects(child)
    97  				switch childSideEffects.severity {
    98  				case sideEffectInProgress, sideEffectNone:
    99  					// no side effects or recursive function - continue scanning
   100  				case sideEffectLimited:
   101  					// The return value may be problematic.
   102  					if e.hasLocalSideEffects(dirtyLocals, inst) {
   103  						result.updateSeverity(sideEffectLimited)
   104  					}
   105  				case sideEffectAll:
   106  					result.updateSeverity(sideEffectAll)
   107  				default:
   108  					panic("unreachable")
   109  				}
   110  			case llvm.Load, llvm.Store:
   111  				if inst.IsVolatile() {
   112  					result.updateSeverity(sideEffectLimited)
   113  				}
   114  			case llvm.IntToPtr:
   115  				// Pointer casts are not yet supported.
   116  				result.updateSeverity(sideEffectLimited)
   117  			default:
   118  				// Ignore most instructions.
   119  				// Check this list for completeness:
   120  				// https://godoc.org/github.com/llvm-mirror/llvm/bindings/go/llvm#Opcode
   121  			}
   122  		}
   123  	}
   124  
   125  	if result.severity == sideEffectInProgress {
   126  		// No side effect was reported for this function.
   127  		result.severity = sideEffectNone
   128  	}
   129  	return result
   130  }
   131  
   132  // hasLocalSideEffects checks whether the given instruction flows into a branch
   133  // or return instruction, in which case the whole function must be marked as
   134  // having side effects and be called at runtime.
   135  func (e *Eval) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llvm.Value) bool {
   136  	if _, ok := dirtyLocals[inst]; ok {
   137  		// It is already known that this local is dirty.
   138  		return true
   139  	}
   140  
   141  	for use := inst.FirstUse(); !use.IsNil(); use = use.NextUse() {
   142  		user := use.User()
   143  		if user.IsAInstruction().IsNil() {
   144  			panic("user not an instruction")
   145  		}
   146  		switch user.InstructionOpcode() {
   147  		case llvm.Br, llvm.Switch:
   148  			// A branch on a dirty value makes this function dirty: it cannot be
   149  			// interpreted at compile time so has to be run at runtime. It is
   150  			// marked as having side effects for this reason.
   151  			return true
   152  		case llvm.Ret:
   153  			// This function returns a dirty value so it is itself marked as
   154  			// dirty to make sure it is called at runtime.
   155  			return true
   156  		case llvm.Store:
   157  			ptr := user.Operand(1)
   158  			if !ptr.IsAGlobalVariable().IsNil() {
   159  				// Store to a global variable.
   160  				// Already handled in (*Eval).hasSideEffects.
   161  				continue
   162  			}
   163  			// But a store might also store to an alloca, in which case all uses
   164  			// of the alloca (possibly indirect through a GEP, bitcast, etc.)
   165  			// must be marked dirty.
   166  			panic("todo: store")
   167  		default:
   168  			// All instructions that take 0 or more operands (1 or more if it
   169  			// was a use) and produce a result.
   170  			// For a list:
   171  			// https://godoc.org/github.com/llvm-mirror/llvm/bindings/go/llvm#Opcode
   172  			dirtyLocals[user] = struct{}{}
   173  			if e.hasLocalSideEffects(dirtyLocals, user) {
   174  				return true
   175  			}
   176  		}
   177  	}
   178  
   179  	// No side effects found.
   180  	return false
   181  }
   182  
   183  // updateSeverity sets r.severity to the max of r.severity and severity,
   184  // conservatively assuming the worst severity.
   185  func (r *sideEffectResult) updateSeverity(severity sideEffectSeverity) {
   186  	if severity > r.severity {
   187  		r.severity = severity
   188  	}
   189  }
   190  
   191  // updateSeverity updates the severity with the severity of the child severity,
   192  // like in a function call. This means it also copies the mentioned globals.
   193  func (r *sideEffectResult) update(child *sideEffectResult) {
   194  	r.updateSeverity(child.severity)
   195  	for global := range child.mentionsGlobals {
   196  		r.mentionsGlobals[global] = struct{}{}
   197  	}
   198  }