You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

178 lines
5.5 KiB

package transform
// This file implements several small optimizations of runtime and reflect
// calls.
import (
"strings"
"tinygo.org/x/go-llvm"
)
// OptimizeStringToBytes transforms runtime.stringToBytes(...) calls into const
// []byte slices whenever possible. This optimizes the following pattern:
//
// w.Write([]byte("foo"))
//
// where Write does not store to the slice.
func OptimizeStringToBytes(mod llvm.Module) {
stringToBytes := mod.NamedFunction("runtime.stringToBytes")
if stringToBytes.IsNil() {
// nothing to optimize
return
}
for _, call := range getUses(stringToBytes) {
strptr := call.Operand(0)
strlen := call.Operand(1)
// strptr is always constant because strings are always constant.
var pointerUses []llvm.Value
canConvertPointer := true
for _, use := range getUses(call) {
if use.IsAExtractValueInst().IsNil() {
// Expected an extractvalue, but this is something else.
canConvertPointer = false
break
}
switch use.Type().TypeKind() {
case llvm.IntegerTypeKind:
// A length (len or cap). Propagate the length value.
// This can always be done because the byte slice is always the
// same length as the original string.
use.ReplaceAllUsesWith(strlen)
use.EraseFromParentAsInstruction()
case llvm.PointerTypeKind:
// The string pointer itself.
if !isReadOnly(use) {
// There is a store to the byte slice. This means that none
// of the pointer uses can't be propagated.
canConvertPointer = false
break
}
// It may be that the pointer value can be propagated, if all of
// the pointer uses are readonly.
pointerUses = append(pointerUses, use)
default:
// should not happen
panic("unknown return type of runtime.stringToBytes: " + use.Type().String())
}
}
if canConvertPointer {
// All pointer uses are readonly, so they can be converted.
for _, use := range pointerUses {
use.ReplaceAllUsesWith(strptr)
use.EraseFromParentAsInstruction()
}
// Call to runtime.stringToBytes can be eliminated: both the input
// and the output is constant.
call.EraseFromParentAsInstruction()
}
}
}
// OptimizeStringEqual transforms runtime.stringEqual(...) calls into simple
// integer comparisons if at least one of the sides of the comparison is zero.
// Ths converts str == "" into len(str) == 0 and "" == "" into false.
func OptimizeStringEqual(mod llvm.Module) {
stringEqual := mod.NamedFunction("runtime.stringEqual")
if stringEqual.IsNil() {
// nothing to optimize
return
}
builder := mod.Context().NewBuilder()
defer builder.Dispose()
for _, call := range getUses(stringEqual) {
str1len := call.Operand(1)
str2len := call.Operand(3)
zero := llvm.ConstInt(str1len.Type(), 0, false)
if str1len == zero || str2len == zero {
builder.SetInsertPointBefore(call)
icmp := builder.CreateICmp(llvm.IntEQ, str1len, str2len, "")
call.ReplaceAllUsesWith(icmp)
call.EraseFromParentAsInstruction()
continue
}
}
}
// OptimizeReflectImplements optimizes the following code:
//
// implements := someType.Implements(someInterfaceType)
//
// where someType is an arbitrary reflect.Type and someInterfaceType is a
// reflect.Type of interface kind, to the following code:
//
// _, implements := someType.(interfaceType)
//
// if the interface type is known at compile time (that is, someInterfaceType is
// a LLVM constant aggregate). This optimization is especially important for the
// encoding/json package, which uses this method.
//
// As of this writing, the (reflect.Type).Interface method has not yet been
// implemented so this optimization is critical for the encoding/json package.
func OptimizeReflectImplements(mod llvm.Module) {
implementsSignature := mod.NamedGlobal("reflect/methods.Implements(reflect.Type) bool")
if implementsSignature.IsNil() {
return
}
builder := mod.Context().NewBuilder()
defer builder.Dispose()
// Look up the (reflect.Value).Implements() method.
var implementsFunc llvm.Value
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
attr := fn.GetStringAttributeAtIndex(-1, "tinygo-invoke")
if attr.IsNil() {
continue
}
if attr.GetStringValue() == "reflect/methods.Implements(reflect.Type) bool" {
implementsFunc = fn
break
}
}
if implementsFunc.IsNil() {
// Doesn't exist in the program, so nothing to do.
return
}
for _, call := range getUses(implementsFunc) {
if call.IsACallInst().IsNil() {
continue
}
interfaceType := stripPointerCasts(call.Operand(2))
if interfaceType.IsAGlobalVariable().IsNil() {
// Interface is unknown at compile time. This can't be optimized.
continue
}
if strings.HasPrefix(interfaceType.Name(), "reflect/types.type:named:") {
// Get the underlying type.
interfaceType = stripPointerCasts(builder.CreateExtractValue(interfaceType.Initializer(), 3, ""))
}
if !strings.HasPrefix(interfaceType.Name(), "reflect/types.type:interface:") {
// This is an error. The Type passed to Implements should be of
// interface type. Ignore it here (don't report it), it will be
// reported at runtime.
continue
}
typeAssertFunction := mod.NamedFunction(strings.TrimPrefix(interfaceType.Name(), "reflect/types.type:") + ".$typeassert")
if typeAssertFunction.IsNil() {
continue
}
// Replace Implements call with the type assert call.
builder.SetInsertPointBefore(call)
implements := builder.CreateCall(typeAssertFunction.GlobalValueType(), typeAssertFunction, []llvm.Value{
call.Operand(0), // typecode to check
}, "")
call.ReplaceAllUsesWith(implements)
call.EraseFromParentAsInstruction()
}
}