rtcalls.go raw

   1  package transform
   2  
   3  // This file implements several small optimizations of runtime and reflect
   4  // calls.
   5  
   6  import (
   7  	"strings"
   8  
   9  	"tinygo.org/x/go-llvm"
  10  )
  11  
  12  // OptimizeStringToBytes transforms runtime.stringToBytes(...) calls into const
  13  // []byte slices whenever possible. This optimizes the following pattern:
  14  //
  15  //	w.Write([]byte("foo"))
  16  //
  17  // where Write does not store to the slice.
  18  func OptimizeStringToBytes(mod llvm.Module) {
  19  	stringToBytes := mod.NamedFunction("runtime.stringToBytes")
  20  	if stringToBytes.IsNil() {
  21  		// nothing to optimize
  22  		return
  23  	}
  24  
  25  	for _, call := range getUses(stringToBytes) {
  26  		strptr := call.Operand(0)
  27  		strlen := call.Operand(1)
  28  
  29  		// strptr is always constant because strings are always constant.
  30  
  31  		var pointerUses []llvm.Value
  32  		canConvertPointer := true
  33  		for _, use := range getUses(call) {
  34  			if use.IsAExtractValueInst().IsNil() {
  35  				// Expected an extractvalue, but this is something else.
  36  				canConvertPointer = false
  37  				break
  38  			}
  39  			switch use.Type().TypeKind() {
  40  			case llvm.IntegerTypeKind:
  41  				// A length (len or cap). Propagate the length value.
  42  				// This can always be done because the byte slice is always the
  43  				// same length as the original string.
  44  				use.ReplaceAllUsesWith(strlen)
  45  				use.EraseFromParentAsInstruction()
  46  			case llvm.PointerTypeKind:
  47  				// The string pointer itself.
  48  				if !isReadOnly(use) {
  49  					// There is a store to the byte slice. This means that none
  50  					// of the pointer uses can't be propagated.
  51  					canConvertPointer = false
  52  					break
  53  				}
  54  				// It may be that the pointer value can be propagated, if all of
  55  				// the pointer uses are readonly.
  56  				pointerUses = append(pointerUses, use)
  57  			default:
  58  				// should not happen
  59  				panic("unknown return type of runtime.stringToBytes: " + use.Type().String())
  60  			}
  61  		}
  62  		if canConvertPointer {
  63  			// All pointer uses are readonly, so they can be converted.
  64  			for _, use := range pointerUses {
  65  				use.ReplaceAllUsesWith(strptr)
  66  				use.EraseFromParentAsInstruction()
  67  			}
  68  
  69  			// Call to runtime.stringToBytes can be eliminated: both the input
  70  			// and the output is constant.
  71  			call.EraseFromParentAsInstruction()
  72  		}
  73  	}
  74  }
  75  
  76  // OptimizeStringEqual transforms runtime.stringEqual(...) calls into simple
  77  // length comparisons when one operand is the empty string.
  78  // Converts str == "" into len(str) == 0.
  79  func OptimizeStringEqual(mod llvm.Module) {
  80  	stringEqual := mod.NamedFunction("runtime.stringEqual")
  81  	if stringEqual.IsNil() {
  82  		return
  83  	}
  84  
  85  	builder := mod.Context().NewBuilder()
  86  	defer builder.Dispose()
  87  
  88  	for _, call := range getUses(stringEqual) {
  89  		// Moxie string is {ptr, len, cap}. Call args are flattened:
  90  		//   Operand(0)=x.ptr, (1)=x.len, (2)=x.cap,
  91  		//   (3)=y.ptr, (4)=y.len, (5)=y.cap,
  92  		//   (6)=context, (7)=callee
  93  		str1len := call.Operand(1)
  94  		str2len := call.Operand(4)
  95  
  96  		str1zero := !str1len.IsAConstantInt().IsNil() && str1len.ZExtValue() == 0
  97  		str2zero := !str2len.IsAConstantInt().IsNil() && str2len.ZExtValue() == 0
  98  		if str1zero || str2zero {
  99  			builder.SetInsertPointBefore(call)
 100  			icmp := builder.CreateICmp(llvm.IntEQ, str1len, str2len, "")
 101  			call.ReplaceAllUsesWith(icmp)
 102  			call.EraseFromParentAsInstruction()
 103  		}
 104  	}
 105  }
 106  
 107  // OptimizeReflectImplements optimizes the following code:
 108  //
 109  //	implements := someType.Implements(someInterfaceType)
 110  //
 111  // where someType is an arbitrary reflect.Type and someInterfaceType is a
 112  // reflect.Type of interface kind, to the following code:
 113  //
 114  //	_, implements := someType.(interfaceType)
 115  //
 116  // if the interface type is known at compile time (that is, someInterfaceType is
 117  // a LLVM constant aggregate). This optimization is especially important for the
 118  // encoding/json package, which uses this method.
 119  //
 120  // As of this writing, the (reflect.Type).Interface method has not yet been
 121  // implemented so this optimization is critical for the encoding/json package.
 122  func OptimizeReflectImplements(mod llvm.Module) {
 123  	implementsSignature1 := mod.NamedGlobal("reflect/methods.Implements(reflect.Type) bool")
 124  	implementsSignature2 := mod.NamedGlobal("reflect/methods.Implements(internal/reflectlite.Type) bool")
 125  	if implementsSignature1.IsNil() && implementsSignature2.IsNil() {
 126  		return
 127  	}
 128  
 129  	builder := mod.Context().NewBuilder()
 130  	defer builder.Dispose()
 131  
 132  	// Look up the (reflect.Value).Implements() method.
 133  	var implementsFunc llvm.Value
 134  	for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
 135  		attr := fn.GetStringAttributeAtIndex(-1, "moxie-invoke")
 136  		if attr.IsNil() {
 137  			continue
 138  		}
 139  		val := attr.GetStringValue()
 140  		if val == "reflect/methods.Implements(reflect.Type) bool" || val == "reflect/methods.Implements(internal/reflectlite.Type) bool" {
 141  			implementsFunc = fn
 142  			break
 143  		}
 144  	}
 145  	if implementsFunc.IsNil() {
 146  		// Doesn't exist in the program, so nothing to do.
 147  		return
 148  	}
 149  
 150  	for _, call := range getUses(implementsFunc) {
 151  		if call.IsACallInst().IsNil() {
 152  			continue
 153  		}
 154  		interfaceType := stripPointerCasts(call.Operand(2))
 155  		if interfaceType.IsAGlobalVariable().IsNil() {
 156  			// Interface is unknown at compile time. This can't be optimized.
 157  			continue
 158  		}
 159  
 160  		if strings.HasPrefix(interfaceType.Name(), "reflect/types.type:named:") {
 161  			// Get the underlying type.
 162  			interfaceType = stripPointerCasts(builder.CreateExtractValue(interfaceType.Initializer(), 3, ""))
 163  		}
 164  		if !strings.HasPrefix(interfaceType.Name(), "reflect/types.type:interface:") {
 165  			// This is an error. The Type passed to Implements should be of
 166  			// interface type. Ignore it here (don't report it), it will be
 167  			// reported at runtime.
 168  			continue
 169  		}
 170  		typeAssertFunction := mod.NamedFunction(strings.TrimPrefix(interfaceType.Name(), "reflect/types.type:") + ".$typeassert")
 171  		if typeAssertFunction.IsNil() {
 172  			continue
 173  		}
 174  
 175  		// Replace Implements call with the type assert call.
 176  		builder.SetInsertPointBefore(call)
 177  		implements := builder.CreateCall(typeAssertFunction.GlobalValueType(), typeAssertFunction, []llvm.Value{
 178  			call.Operand(0), // typecode to check
 179  		}, "")
 180  		call.ReplaceAllUsesWith(implements)
 181  		call.EraseFromParentAsInstruction()
 182  	}
 183  }
 184