aboutsummaryrefslogtreecommitdiffhomepage
path: root/transform
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2019-09-21 18:05:23 +0200
committerRon Evans <[email protected]>2019-09-22 08:25:50 +0200
commit65beddafe8e3d37cd60eb24a3d87e9dc3b3150b3 (patch)
tree5b7a3c3b769b400fe21375059de36051c4b6b615 /transform
parentcea0d9f864f2e1af32b97bd5af078a16f487859d (diff)
downloadtinygo-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.go57
-rw-r--r--transform/stringtobytes_test.go15
-rw-r--r--transform/testdata/stringtobytes.ll32
-rw-r--r--transform/testdata/stringtobytes.out.ll24
-rw-r--r--transform/util.go21
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
+}