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

     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  				continue
    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  					continue
    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  // integer comparisons if at least one of the sides of the comparison is zero.
    78  // Ths converts str == "" into len(str) == 0 and "" == "" into false.
    79  func OptimizeStringEqual(mod llvm.Module) {
    80  	stringEqual := mod.NamedFunction("runtime.stringEqual")
    81  	if stringEqual.IsNil() {
    82  		// nothing to optimize
    83  		return
    84  	}
    85  
    86  	builder := mod.Context().NewBuilder()
    87  	defer builder.Dispose()
    88  
    89  	for _, call := range getUses(stringEqual) {
    90  		str1len := call.Operand(1)
    91  		str2len := call.Operand(3)
    92  
    93  		zero := llvm.ConstInt(str1len.Type(), 0, false)
    94  		if str1len == zero || str2len == zero {
    95  			builder.SetInsertPointBefore(call)
    96  			icmp := builder.CreateICmp(llvm.IntEQ, str1len, str2len, "")
    97  			call.ReplaceAllUsesWith(icmp)
    98  			call.EraseFromParentAsInstruction()
    99  			continue
   100  		}
   101  	}
   102  }
   103  
   104  // OptimizeReflectImplements optimizes the following code:
   105  //
   106  //	implements := someType.Implements(someInterfaceType)
   107  //
   108  // where someType is an arbitrary reflect.Type and someInterfaceType is a
   109  // reflect.Type of interface kind, to the following code:
   110  //
   111  //	_, implements := someType.(interfaceType)
   112  //
   113  // if the interface type is known at compile time (that is, someInterfaceType is
   114  // a LLVM constant aggregate). This optimization is especially important for the
   115  // encoding/json package, which uses this method.
   116  //
   117  // As of this writing, the (reflect.Type).Interface method has not yet been
   118  // implemented so this optimization is critical for the encoding/json package.
   119  func OptimizeReflectImplements(mod llvm.Module) {
   120  	implementsSignature := mod.NamedGlobal("reflect/methods.Implements(reflect.Type) bool")
   121  	if implementsSignature.IsNil() {
   122  		return
   123  	}
   124  
   125  	builder := mod.Context().NewBuilder()
   126  	defer builder.Dispose()
   127  
   128  	// Look up the (reflect.Value).Implements() method.
   129  	var implementsFunc llvm.Value
   130  	for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
   131  		attr := fn.GetStringAttributeAtIndex(-1, "tinygo-invoke")
   132  		if attr.IsNil() {
   133  			continue
   134  		}
   135  		if attr.GetStringValue() == "reflect/methods.Implements(reflect.Type) bool" {
   136  			implementsFunc = fn
   137  			break
   138  		}
   139  	}
   140  	if implementsFunc.IsNil() {
   141  		// Doesn't exist in the program, so nothing to do.
   142  		return
   143  	}
   144  
   145  	for _, call := range getUses(implementsFunc) {
   146  		if call.IsACallInst().IsNil() {
   147  			continue
   148  		}
   149  		interfaceType := stripPointerCasts(call.Operand(2))
   150  		if interfaceType.IsAGlobalVariable().IsNil() {
   151  			// Interface is unknown at compile time. This can't be optimized.
   152  			continue
   153  		}
   154  
   155  		if strings.HasPrefix(interfaceType.Name(), "reflect/types.type:named:") {
   156  			// Get the underlying type.
   157  			interfaceType = stripPointerCasts(builder.CreateExtractValue(interfaceType.Initializer(), 3, ""))
   158  		}
   159  		if !strings.HasPrefix(interfaceType.Name(), "reflect/types.type:interface:") {
   160  			// This is an error. The Type passed to Implements should be of
   161  			// interface type. Ignore it here (don't report it), it will be
   162  			// reported at runtime.
   163  			continue
   164  		}
   165  		typeAssertFunction := mod.NamedFunction(strings.TrimPrefix(interfaceType.Name(), "reflect/types.type:") + ".$typeassert")
   166  		if typeAssertFunction.IsNil() {
   167  			continue
   168  		}
   169  
   170  		// Replace Implements call with the type assert call.
   171  		builder.SetInsertPointBefore(call)
   172  		implements := builder.CreateCall(typeAssertFunction.GlobalValueType(), typeAssertFunction, []llvm.Value{
   173  			call.Operand(0), // typecode to check
   174  		}, "")
   175  		call.ReplaceAllUsesWith(implements)
   176  		call.EraseFromParentAsInstruction()
   177  	}
   178  }