diff options
author | Ayke van Laethem <[email protected]> | 2019-09-21 18:05:23 +0200 |
---|---|---|
committer | Ron Evans <[email protected]> | 2019-09-22 08:25:50 +0200 |
commit | 65beddafe8e3d37cd60eb24a3d87e9dc3b3150b3 (patch) | |
tree | 5b7a3c3b769b400fe21375059de36051c4b6b615 /transform | |
parent | cea0d9f864f2e1af32b97bd5af078a16f487859d (diff) | |
download | tinygo-65beddafe8e3d37cd60eb24a3d87e9dc3b3150b3.tar.gz tinygo-65beddafe8e3d37cd60eb24a3d87e9dc3b3150b3.zip |
compiler: move OptimizeStringToBytes to transform package
Unfortunately, while doing this I found that it doesn't actually apply
in any real-world programs (tested with `make smoketest`), apparently
because nil pointer checking messes with the functionattrs pass. I hope
to fix that after moving to LLVM 9, which has an optimization that makes
nil pointer checking easier to implement.
Diffstat (limited to 'transform')
-rw-r--r-- | transform/stringtobytes.go | 57 | ||||
-rw-r--r-- | transform/stringtobytes_test.go | 15 | ||||
-rw-r--r-- | transform/testdata/stringtobytes.ll | 32 | ||||
-rw-r--r-- | transform/testdata/stringtobytes.out.ll | 24 | ||||
-rw-r--r-- | transform/util.go | 21 |
5 files changed, 149 insertions, 0 deletions
diff --git a/transform/stringtobytes.go b/transform/stringtobytes.go new file mode 100644 index 000000000..b3cfe1158 --- /dev/null +++ b/transform/stringtobytes.go @@ -0,0 +1,57 @@ +package transform + +import ( + "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. + + convertedAllUses := true + for _, use := range getUses(call) { + if use.IsAExtractValueInst().IsNil() { + // Expected an extractvalue, but this is something else. + convertedAllUses = false + continue + } + switch use.Type().TypeKind() { + case llvm.IntegerTypeKind: + // A length (len or cap). Propagate the length value. + use.ReplaceAllUsesWith(strlen) + use.EraseFromParentAsInstruction() + case llvm.PointerTypeKind: + // The string pointer itself. + if !isReadOnly(use) { + convertedAllUses = false + continue + } + use.ReplaceAllUsesWith(strptr) + use.EraseFromParentAsInstruction() + default: + // should not happen + panic("unknown return type of runtime.stringToBytes: " + use.Type().String()) + } + } + if convertedAllUses { + // Call to runtime.stringToBytes can be eliminated: both the input + // and the output is constant. + call.EraseFromParentAsInstruction() + } + } +} diff --git a/transform/stringtobytes_test.go b/transform/stringtobytes_test.go new file mode 100644 index 000000000..91553840d --- /dev/null +++ b/transform/stringtobytes_test.go @@ -0,0 +1,15 @@ +package transform + +import ( + "testing" + + "tinygo.org/x/go-llvm" +) + +func TestOptimizeStringToBytes(t *testing.T) { + t.Parallel() + testTransform(t, "testdata/stringtobytes", func(mod llvm.Module) { + // Run optimization pass. + OptimizeStringToBytes(mod) + }) +} diff --git a/transform/testdata/stringtobytes.ll b/transform/testdata/stringtobytes.ll new file mode 100644 index 000000000..f3cec8234 --- /dev/null +++ b/transform/testdata/stringtobytes.ll @@ -0,0 +1,32 @@ +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64--linux" + +@str = constant [6 x i8] c"foobar" + +declare { i8*, i64, i64 } @runtime.stringToBytes(i8*, i64) + +declare void @printSlice(i8* nocapture readonly, i64, i64) + +declare void @writeToSlice(i8* nocapture, i64, i64) + +; Test that runtime.stringToBytes can be fully optimized away. +define void @testReadOnly() { +entry: + %0 = call fastcc { i8*, i64, i64 } @runtime.stringToBytes(i8* getelementptr inbounds ([6 x i8], [6 x i8]* @str, i32 0, i32 0), i64 6) + %1 = extractvalue { i8*, i64, i64 } %0, 0 + %2 = extractvalue { i8*, i64, i64 } %0, 1 + %3 = extractvalue { i8*, i64, i64 } %0, 2 + call fastcc void @printSlice(i8* %1, i64 %2, i64 %3) + ret void +} + +; Test that even though the slice is written to, some values can be propagated. +define void @testReadWrite() { +entry: + %0 = call fastcc { i8*, i64, i64 } @runtime.stringToBytes(i8* getelementptr inbounds ([6 x i8], [6 x i8]* @str, i32 0, i32 0), i64 6) + %1 = extractvalue { i8*, i64, i64 } %0, 0 + %2 = extractvalue { i8*, i64, i64 } %0, 1 + %3 = extractvalue { i8*, i64, i64 } %0, 2 + call fastcc void @writeToSlice(i8* %1, i64 %2, i64 %3) + ret void +} diff --git a/transform/testdata/stringtobytes.out.ll b/transform/testdata/stringtobytes.out.ll new file mode 100644 index 000000000..49b065818 --- /dev/null +++ b/transform/testdata/stringtobytes.out.ll @@ -0,0 +1,24 @@ +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64--linux" + +@str = constant [6 x i8] c"foobar" + +declare { i8*, i64, i64 } @runtime.stringToBytes(i8*, i64) + +declare void @printSlice(i8* nocapture readonly, i64, i64) + +declare void @writeToSlice(i8* nocapture, i64, i64) + +define void @testReadOnly() { +entry: + call fastcc void @printSlice(i8* getelementptr inbounds ([6 x i8], [6 x i8]* @str, i32 0, i32 0), i64 6, i64 6) + ret void +} + +define void @testReadWrite() { +entry: + %0 = call fastcc { i8*, i64, i64 } @runtime.stringToBytes(i8* getelementptr inbounds ([6 x i8], [6 x i8]* @str, i32 0, i32 0), i64 6) + %1 = extractvalue { i8*, i64, i64 } %0, 0 + call fastcc void @writeToSlice(i8* %1, i64 6, i64 6) + ret void +} diff --git a/transform/util.go b/transform/util.go index a27bf9ec8..9923669d1 100644 --- a/transform/util.go +++ b/transform/util.go @@ -32,3 +32,24 @@ func hasFlag(call, param llvm.Value, kind string) bool { } return true } + +// isReadOnly returns true if the given value (which must be of pointer type) is +// never stored to, and false if this cannot be proven. +func isReadOnly(value llvm.Value) bool { + uses := getUses(value) + for _, use := range uses { + if !use.IsAGetElementPtrInst().IsNil() { + if !isReadOnly(use) { + return false + } + } else if !use.IsACallInst().IsNil() { + if !hasFlag(use, value, "readonly") { + return false + } + } else { + // Unknown instruction, might not be readonly. + return false + } + } + return true +} |