Skip to content

Commit

Permalink
transform: optimize string comparisons against ""
Browse files Browse the repository at this point in the history
This optimizes a common pattern like:

    if s != "" {
        ...
    }

to:

    if len(s) != 0 {
        ...
    }

This avoids a runtime call and thus produces slightly better code.
  • Loading branch information
aykevl authored and deadprogram committed Mar 18, 2021
1 parent 13db2c1 commit f9865a0
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 0 deletions.
1 change: 1 addition & 0 deletions transform/optimizer.go
Expand Up @@ -97,6 +97,7 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
// Run TinyGo-specific interprocedural optimizations.
OptimizeAllocs(mod)
OptimizeStringToBytes(mod)
OptimizeStringEqual(mod)

} else {
// Must be run at any optimization level.
Expand Down
30 changes: 30 additions & 0 deletions transform/stringtobytes.go → transform/rtcalls.go
@@ -1,5 +1,7 @@
package transform

// This file implements several small optimizations of runtime calls.

import (
"tinygo.org/x/go-llvm"
)
Expand Down Expand Up @@ -55,3 +57,31 @@ func OptimizeStringToBytes(mod llvm.Module) {
}
}
}

// 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
}
}
}
8 changes: 8 additions & 0 deletions transform/stringtobytes_test.go → transform/rtcalls_test.go
Expand Up @@ -13,3 +13,11 @@ func TestOptimizeStringToBytes(t *testing.T) {
OptimizeStringToBytes(mod)
})
}

func TestOptimizeStringEqual(t *testing.T) {
t.Parallel()
testTransform(t, "testdata/stringequal", func(mod llvm.Module) {
// Run optimization pass.
OptimizeStringEqual(mod)
})
}
19 changes: 19 additions & 0 deletions transform/testdata/stringequal.ll
@@ -0,0 +1,19 @@
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"

@zeroString = constant [0 x i8] zeroinitializer

declare i1 @runtime.stringEqual(i8*, i32, i8*, i32, i8*, i8*)

define i1 @main.stringCompareEqualConstantZero(i8* %s1.data, i32 %s1.len, i8* %context, i8* %parentHandle) {
entry:
%0 = call i1 @runtime.stringEqual(i8* %s1.data, i32 %s1.len, i8* getelementptr inbounds ([0 x i8], [0 x i8]* @zeroString, i32 0, i32 0), i32 0, i8* undef, i8* null)
ret i1 %0
}

define i1 @main.stringCompareUnequalConstantZero(i8* %s1.data, i32 %s1.len, i8* %context, i8* %parentHandle) {
entry:
%0 = call i1 @runtime.stringEqual(i8* %s1.data, i32 %s1.len, i8* getelementptr inbounds ([0 x i8], [0 x i8]* @zeroString, i32 0, i32 0), i32 0, i8* undef, i8* null)
%1 = xor i1 %0, true
ret i1 %1
}
19 changes: 19 additions & 0 deletions transform/testdata/stringequal.out.ll
@@ -0,0 +1,19 @@
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"

@zeroString = constant [0 x i8] zeroinitializer

declare i1 @runtime.stringEqual(i8*, i32, i8*, i32, i8*, i8*)

define i1 @main.stringCompareEqualConstantZero(i8* %s1.data, i32 %s1.len, i8* %context, i8* %parentHandle) {
entry:
%0 = icmp eq i32 %s1.len, 0
ret i1 %0
}

define i1 @main.stringCompareUnequalConstantZero(i8* %s1.data, i32 %s1.len, i8* %context, i8* %parentHandle) {
entry:
%0 = icmp eq i32 %s1.len, 0
%1 = xor i1 %0, true
ret i1 %1
}

0 comments on commit f9865a0

Please sign in to comment.