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