diff --git a/transform/optimizer.go b/transform/optimizer.go index 150a9a77cb..cf6dd60969 100644 --- a/transform/optimizer.go +++ b/transform/optimizer.go @@ -65,6 +65,7 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error { // Run TinyGo-specific optimization passes. OptimizeStringToBytes(mod) + OptimizeStringFromBytes(mod) maxStackSize := config.MaxStackAlloc() OptimizeAllocs(mod, nil, maxStackSize, nil) err = LowerInterfaces(mod, config) @@ -90,6 +91,8 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error { // The go coverage tool expects this header before any blocks. fmt.Fprintln(os.Stderr, "mode: set") } + OptimizeStringToBytes(mod) + OptimizeStringFromBytes(mod) OptimizeAllocs(mod, config.Options.PrintAllocs, maxStackSize, func(pos token.Position, reason string) { var line string @@ -103,10 +106,11 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error { } }, ) - OptimizeStringToBytes(mod) OptimizeStringEqual(mod) } else { + OptimizeStringFromBytes(mod) + // Must be run at any optimization level. err := LowerInterfaces(mod, config) if err != nil { diff --git a/transform/rtcalls.go b/transform/rtcalls.go index 49b138bcc9..b8424c76c3 100644 --- a/transform/rtcalls.go +++ b/transform/rtcalls.go @@ -3,9 +3,7 @@ package transform // This file implements several small optimizations of runtime and reflect // calls. -import ( - "tinygo.org/x/go-llvm" -) +import "tinygo.org/x/go-llvm" // OptimizeStringToBytes transforms runtime.stringToBytes(...) calls into const // []byte slices whenever possible. This optimizes the following pattern: @@ -71,6 +69,169 @@ func OptimizeStringToBytes(mod llvm.Module) { } } +// OptimizeStringFromBytes transforms temporary strings created from []byte +// slices into direct uses of the slice data when no instruction between the +// conversion and use can mutate the slice. +func OptimizeStringFromBytes(mod llvm.Module) { + stringFromBytes := mod.NamedFunction("runtime.stringFromBytes") + if stringFromBytes.IsNil() { + // nothing to optimize + return + } + + // String comparisons only read their operands, so they are safe between a + // conversion and another supported use of that conversion. + safeCalls := map[llvm.Value]struct{}{} + for _, name := range []string{"runtime.stringEqual", "runtime.stringLess"} { + compare := mod.NamedFunction(name) + if compare.IsNil() { + continue + } + for _, call := range getUses(compare) { + safeCalls[call] = struct{}{} + } + } + + // Rewrite each supported use independently. Length extracts are delayed so + // comparison operands can still be matched as pointer/length pairs. + for _, call := range getUses(stringFromBytes) { + var lengthExtracts []llvm.Value + for _, extract := range getUses(call) { + if extract.IsAExtractValueInst().IsNil() { + continue + } + indices := extract.Indices() + if len(indices) != 1 { + continue + } + switch indices[0] { + case 0: + for _, use := range getUses(extract) { + if _, ok := safeCalls[use]; !ok { + continue + } + if !isSafeStringFromBytesUse(call, stringFromBytes, use, safeCalls) { + continue + } + replaceStringFromBytesCompareUse(use, extract, call, stringFromBytes) + } + case 1: + lengthExtracts = append(lengthExtracts, extract) + } + } + + for _, extract := range lengthExtracts { + extract.ReplaceAllUsesWith(call.Operand(1)) + extract.EraseFromParentAsInstruction() + } + removeDeadStringFromBytes(call) + } +} + +func replaceStringFromBytesCompareUse(compare, ptrExtract, call, stringFromBytes llvm.Value) { + for _, pair := range [][2]int{{0, 1}, {2, 3}} { + if compare.Operand(pair[0]) != ptrExtract { + continue + } + lenExtract, ok := getStringFromBytesExtract(compare.Operand(pair[1]), stringFromBytes, 1) + if !ok || lenExtract.Operand(0) != call { + continue + } + compare.SetOperand(pair[0], call.Operand(0)) + compare.SetOperand(pair[1], call.Operand(1)) + } +} + +func getStringFromBytesExtract(value, stringFromBytes llvm.Value, index uint64) (llvm.Value, bool) { + if value.IsAExtractValueInst().IsNil() { + return llvm.Value{}, false + } + indices := value.Indices() + if len(indices) != 1 || indices[0] != uint32(index) { + return llvm.Value{}, false + } + call := value.Operand(0) + if call.IsACallInst().IsNil() { + return llvm.Value{}, false + } + called := call.CalledValue() + if called.IsNil() || called != stringFromBytes { + return llvm.Value{}, false + } + return value, true +} + +func isStringFromBytesCall(value, stringFromBytes llvm.Value) bool { + if value.IsACallInst().IsNil() { + return false + } + called := value.CalledValue() + return !called.IsNil() && called == stringFromBytes +} + +// isSafeStringFromBytesUse reports whether replacing the copied string with the +// source slice preserves the bytes observed by use. +func isSafeStringFromBytesUse(call, stringFromBytes, use llvm.Value, allowedCalls map[llvm.Value]struct{}) bool { + if call.InstructionParent() != use.InstructionParent() { + return false + } + for inst := llvm.NextInstruction(call); !inst.IsNil(); inst = llvm.NextInstruction(inst) { + if inst == use { + return true + } + if !isSafeStringFromBytesInterveningInstruction(inst, stringFromBytes, allowedCalls) { + return false + } + } + return false +} + +func isSafeStringFromBytesInterveningInstruction(inst, stringFromBytes llvm.Value, allowedCalls map[llvm.Value]struct{}) bool { + if _, ok := allowedCalls[inst]; ok { + return true + } + switch { + case !inst.IsAExtractValueInst().IsNil(): + return true + case isTrackPointerCall(inst): + return true + case isStringFromBytesCall(inst, stringFromBytes): + return true + default: + return false + } +} + +func removeDeadStringFromBytes(call llvm.Value) { + for _, use := range getUses(call) { + if use.IsAExtractValueInst().IsNil() { + return + } + for _, extractUse := range getUses(use) { + if !isTrackPointerCall(extractUse) { + return + } + } + } + for _, use := range getUses(call) { + for _, extractUse := range getUses(use) { + extractUse.EraseFromParentAsInstruction() + } + use.EraseFromParentAsInstruction() + } + if !hasUses(call) { + call.EraseFromParentAsInstruction() + } +} + +func isTrackPointerCall(value llvm.Value) bool { + if value.IsACallInst().IsNil() { + return false + } + called := value.CalledValue() + return !called.IsNil() && called.Name() == "runtime.trackPointer" +} + // 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. diff --git a/transform/rtcalls_test.go b/transform/rtcalls_test.go index 2ae1603155..5d2b6f5f68 100644 --- a/transform/rtcalls_test.go +++ b/transform/rtcalls_test.go @@ -22,3 +22,24 @@ func TestOptimizeStringEqual(t *testing.T) { transform.OptimizeStringEqual(mod) }) } + +func TestOptimizeStringFromBytesStringEqual(t *testing.T) { + t.Parallel() + testTransform(t, "testdata/stringfrombytes-stringequal", func(mod llvm.Module) { + transform.OptimizeStringFromBytes(mod) + }) +} + +func TestOptimizeStringFromBytesStringLess(t *testing.T) { + t.Parallel() + testTransform(t, "testdata/stringfrombytes-stringless", func(mod llvm.Module) { + transform.OptimizeStringFromBytes(mod) + }) +} + +func TestOptimizeStringFromBytesLen(t *testing.T) { + t.Parallel() + testTransform(t, "testdata/stringfrombytes-len", func(mod llvm.Module) { + transform.OptimizeStringFromBytes(mod) + }) +} diff --git a/transform/testdata/stringfrombytes-len.ll b/transform/testdata/stringfrombytes-len.ll new file mode 100644 index 0000000000..8a7c0ec378 --- /dev/null +++ b/transform/testdata/stringfrombytes-len.ll @@ -0,0 +1,31 @@ +target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64" +target triple = "armv7m-none-eabi" + +%runtime._string = type { ptr, i32 } + +declare %runtime._string @runtime.stringFromBytes(ptr, i32, i32, ptr) + +declare void @runtime.trackPointer(ptr, ptr, ptr) + +declare void @useString(ptr, i32) + +define i32 @main.stringFromBytesLen(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %context) { +entry: + %stackalloc = alloca i8, align 1 + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) + %1 = extractvalue %runtime._string %0, 0 + call void @runtime.trackPointer(ptr %1, ptr %stackalloc, ptr undef) + %2 = extractvalue %runtime._string %0, 1 + %len = extractvalue %runtime._string %0, 1 + ret i32 %len +} + +define i32 @main.keepStringConversion(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %context) { +entry: + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) + %1 = extractvalue %runtime._string %0, 0 + %2 = extractvalue %runtime._string %0, 1 + call void @useString(ptr %1, i32 %2) + %len = extractvalue %runtime._string %0, 1 + ret i32 %len +} diff --git a/transform/testdata/stringfrombytes-len.out.ll b/transform/testdata/stringfrombytes-len.out.ll new file mode 100644 index 0000000000..e144720eaf --- /dev/null +++ b/transform/testdata/stringfrombytes-len.out.ll @@ -0,0 +1,24 @@ +target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64" +target triple = "armv7m-none-eabi" + +%runtime._string = type { ptr, i32 } + +declare %runtime._string @runtime.stringFromBytes(ptr, i32, i32, ptr) + +declare void @runtime.trackPointer(ptr, ptr, ptr) + +declare void @useString(ptr, i32) + +define i32 @main.stringFromBytesLen(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %context) { +entry: + %stackalloc = alloca i8, align 1 + ret i32 %a.len +} + +define i32 @main.keepStringConversion(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %context) { +entry: + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) + %1 = extractvalue %runtime._string %0, 0 + call void @useString(ptr %1, i32 %a.len) + ret i32 %a.len +} diff --git a/transform/testdata/stringfrombytes-stringequal.ll b/transform/testdata/stringfrombytes-stringequal.ll new file mode 100644 index 0000000000..3fc259de00 --- /dev/null +++ b/transform/testdata/stringfrombytes-stringequal.ll @@ -0,0 +1,107 @@ +target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64" +target triple = "armv7m-none-eabi" + +%runtime._string = type { ptr, i32 } + +declare %runtime._string @runtime.stringFromBytes(ptr, i32, i32, ptr) + +declare i1 @runtime.stringEqual(ptr, i32, ptr, i32, ptr) + +declare void @runtime.trackPointer(ptr, ptr, ptr) + +declare void @useString(ptr, i32) + +declare void @llvm.lifetime.end.p0(ptr captures(none)) + +define i1 @main.bytesEqual(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %b.data, i32 %b.len, i32 %b.cap, ptr %context) { +entry: + %stackalloc = alloca i8, align 1 + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) + %1 = extractvalue %runtime._string %0, 0 + call void @runtime.trackPointer(ptr %1, ptr %stackalloc, ptr undef) + %2 = extractvalue %runtime._string %0, 1 + %3 = call %runtime._string @runtime.stringFromBytes(ptr %b.data, i32 %b.len, i32 %b.cap, ptr undef) + %4 = extractvalue %runtime._string %3, 0 + call void @runtime.trackPointer(ptr %4, ptr %stackalloc, ptr undef) + %5 = extractvalue %runtime._string %3, 1 + %6 = call i1 @runtime.stringEqual(ptr %1, i32 %2, ptr %4, i32 %5, ptr undef) + ret i1 %6 +} + +define i1 @main.stringAndBytesEqual(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %s.data, i32 %s.len, ptr %context) { +entry: + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) + %1 = extractvalue %runtime._string %0, 0 + %2 = extractvalue %runtime._string %0, 1 + %3 = call i1 @runtime.stringEqual(ptr %1, i32 %2, ptr %s.data, i32 %s.len, ptr undef) + ret i1 %3 +} + +define i1 @main.keepStringConversion(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %b.data, i32 %b.len, i32 %b.cap, ptr %context) { +entry: + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) + %1 = extractvalue %runtime._string %0, 0 + %2 = extractvalue %runtime._string %0, 1 + call void @useString(ptr %1, i32 %2) + %3 = call %runtime._string @runtime.stringFromBytes(ptr %b.data, i32 %b.len, i32 %b.cap, ptr undef) + %4 = extractvalue %runtime._string %3, 0 + %5 = extractvalue %runtime._string %3, 1 + %6 = call i1 @runtime.stringEqual(ptr %1, i32 %2, ptr %4, i32 %5, ptr undef) + ret i1 %6 +} + +define i32 @main.equalAndLen(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %s.data, i32 %s.len, ptr %context) { +entry: + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) + %1 = extractvalue %runtime._string %0, 0 + %2 = extractvalue %runtime._string %0, 1 + %equal = call i1 @runtime.stringEqual(ptr %1, i32 %2, ptr %s.data, i32 %s.len, ptr undef) + %len = extractvalue %runtime._string %0, 1 + %equal.ext = zext i1 %equal to i32 + %result = add i32 %len, %equal.ext + ret i32 %result +} + +define i1 @main.equalBeforeOtherUse(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %s.data, i32 %s.len, ptr %context) { +entry: + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) + %1 = extractvalue %runtime._string %0, 0 + %2 = extractvalue %runtime._string %0, 1 + %equal = call i1 @runtime.stringEqual(ptr %1, i32 %2, ptr %s.data, i32 %s.len, ptr undef) + call void @useString(ptr %1, i32 %2) + ret i1 %equal +} + +define i1 @main.twoComparisons(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %s.data, i32 %s.len, ptr %context) { +entry: + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) + %1 = extractvalue %runtime._string %0, 0 + %2 = extractvalue %runtime._string %0, 1 + %equal1 = call i1 @runtime.stringEqual(ptr %1, i32 %2, ptr %s.data, i32 %s.len, ptr undef) + %equal2 = call i1 @runtime.stringEqual(ptr %1, i32 %2, ptr %s.data, i32 %s.len, ptr undef) + %result = and i1 %equal1, %equal2 + ret i1 %result +} + +define i1 @main.keepComparisonAfterMutation(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %s.data, i32 %s.len, ptr %context) { +entry: + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) + %1 = extractvalue %runtime._string %0, 0 + %2 = extractvalue %runtime._string %0, 1 + %equal1 = call i1 @runtime.stringEqual(ptr %1, i32 %2, ptr %s.data, i32 %s.len, ptr undef) + store i8 1, ptr %a.data, align 1 + %equal2 = call i1 @runtime.stringEqual(ptr %1, i32 %2, ptr %s.data, i32 %s.len, ptr undef) + %result = and i1 %equal1, %equal2 + ret i1 %result +} + +define i1 @main.keepAfterLifetimeEnd(ptr %s.data, i32 %s.len, ptr %context) { +entry: + %a.data = alloca [4 x i8], align 1 + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 4, i32 4, ptr undef) + %1 = extractvalue %runtime._string %0, 0 + %2 = extractvalue %runtime._string %0, 1 + call void @llvm.lifetime.end.p0(ptr %a.data) + %equal = call i1 @runtime.stringEqual(ptr %1, i32 %2, ptr %s.data, i32 %s.len, ptr undef) + ret i1 %equal +} diff --git a/transform/testdata/stringfrombytes-stringequal.out.ll b/transform/testdata/stringfrombytes-stringequal.out.ll new file mode 100644 index 0000000000..ec426da76c --- /dev/null +++ b/transform/testdata/stringfrombytes-stringequal.out.ll @@ -0,0 +1,85 @@ +target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64" +target triple = "armv7m-none-eabi" + +%runtime._string = type { ptr, i32 } + +declare %runtime._string @runtime.stringFromBytes(ptr, i32, i32, ptr) + +declare i1 @runtime.stringEqual(ptr, i32, ptr, i32, ptr) + +declare void @runtime.trackPointer(ptr, ptr, ptr) + +declare void @useString(ptr, i32) + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +declare void @llvm.lifetime.end.p0(ptr captures(none)) #0 + +define i1 @main.bytesEqual(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %b.data, i32 %b.len, i32 %b.cap, ptr %context) { +entry: + %stackalloc = alloca i8, align 1 + %0 = call i1 @runtime.stringEqual(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr undef) + ret i1 %0 +} + +define i1 @main.stringAndBytesEqual(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %s.data, i32 %s.len, ptr %context) { +entry: + %0 = call i1 @runtime.stringEqual(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef) + ret i1 %0 +} + +define i1 @main.keepStringConversion(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %b.data, i32 %b.len, i32 %b.cap, ptr %context) { +entry: + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) + %1 = extractvalue %runtime._string %0, 0 + call void @useString(ptr %1, i32 %a.len) + %2 = call i1 @runtime.stringEqual(ptr %1, i32 %a.len, ptr %b.data, i32 %b.len, ptr undef) + ret i1 %2 +} + +define i32 @main.equalAndLen(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %s.data, i32 %s.len, ptr %context) { +entry: + %equal = call i1 @runtime.stringEqual(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef) + %equal.ext = zext i1 %equal to i32 + %result = add i32 %a.len, %equal.ext + ret i32 %result +} + +define i1 @main.equalBeforeOtherUse(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %s.data, i32 %s.len, ptr %context) { +entry: + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) + %1 = extractvalue %runtime._string %0, 0 + %equal = call i1 @runtime.stringEqual(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef) + call void @useString(ptr %1, i32 %a.len) + ret i1 %equal +} + +define i1 @main.twoComparisons(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %s.data, i32 %s.len, ptr %context) { +entry: + %equal1 = call i1 @runtime.stringEqual(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef) + %equal2 = call i1 @runtime.stringEqual(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef) + %result = and i1 %equal1, %equal2 + ret i1 %result +} + +define i1 @main.keepComparisonAfterMutation(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %s.data, i32 %s.len, ptr %context) { +entry: + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) + %1 = extractvalue %runtime._string %0, 0 + %equal1 = call i1 @runtime.stringEqual(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef) + store i8 1, ptr %a.data, align 1 + %equal2 = call i1 @runtime.stringEqual(ptr %1, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef) + %result = and i1 %equal1, %equal2 + ret i1 %result +} + +define i1 @main.keepAfterLifetimeEnd(ptr %s.data, i32 %s.len, ptr %context) { +entry: + %a.data = alloca [4 x i8], align 1 + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 4, i32 4, ptr undef) + %1 = extractvalue %runtime._string %0, 0 + call void @llvm.lifetime.end.p0(ptr %a.data) + %equal = call i1 @runtime.stringEqual(ptr %1, i32 4, ptr %s.data, i32 %s.len, ptr undef) + ret i1 %equal +} + +attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } diff --git a/transform/testdata/stringfrombytes-stringless.ll b/transform/testdata/stringfrombytes-stringless.ll new file mode 100644 index 0000000000..d771a4448e --- /dev/null +++ b/transform/testdata/stringfrombytes-stringless.ll @@ -0,0 +1,58 @@ +target datalayout = "e-m:e-p:32:32-i64:64-v128:64-a:0:32-n32-S64" +target triple = "armv7m-none-eabi" + +%runtime._string = type { ptr, i32 } + +declare %runtime._string @runtime.stringFromBytes(ptr, i32, i32, ptr) + +declare i1 @runtime.stringLess(ptr, i32, ptr, i32, ptr) + +declare void @runtime.trackPointer(ptr, ptr, ptr) + +declare void @useString(ptr, i32) + +define i1 @main.bytesLess(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %b.data, i32 %b.len, i32 %b.cap, ptr %context) { +entry: + %stackalloc = alloca i8, align 1 + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) + %1 = extractvalue %runtime._string %0, 0 + call void @runtime.trackPointer(ptr %1, ptr %stackalloc, ptr undef) + %2 = extractvalue %runtime._string %0, 1 + %3 = call %runtime._string @runtime.stringFromBytes(ptr %b.data, i32 %b.len, i32 %b.cap, ptr undef) + %4 = extractvalue %runtime._string %3, 0 + call void @runtime.trackPointer(ptr %4, ptr %stackalloc, ptr undef) + %5 = extractvalue %runtime._string %3, 1 + %6 = call i1 @runtime.stringLess(ptr %1, i32 %2, ptr %4, i32 %5, ptr undef) + ret i1 %6 +} + +define i1 @main.bytesLessString(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %s.data, i32 %s.len, ptr %context) { +entry: + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) + %1 = extractvalue %runtime._string %0, 0 + %2 = extractvalue %runtime._string %0, 1 + %3 = call i1 @runtime.stringLess(ptr %1, i32 %2, ptr %s.data, i32 %s.len, ptr undef) + ret i1 %3 +} + +define i1 @main.stringLessBytes(ptr %s.data, i32 %s.len, ptr %a.data, i32 %a.len, i32 %a.cap, ptr %context) { +entry: + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) + %1 = extractvalue %runtime._string %0, 0 + %2 = extractvalue %runtime._string %0, 1 + %3 = call i1 @runtime.stringLess(ptr %s.data, i32 %s.len, ptr %1, i32 %2, ptr undef) + ret i1 %3 +} + +define i1 @main.keepStringConversion(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %b.data, i32 %b.len, i32 %b.cap, ptr %context) { +entry: + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) + %1 = extractvalue %runtime._string %0, 0 + %2 = extractvalue %runtime._string %0, 1 + call void @useString(ptr %1, i32 %2) + %3 = call %runtime._string @runtime.stringFromBytes(ptr %b.data, i32 %b.len, i32 %b.cap, ptr undef) + %4 = extractvalue %runtime._string %3, 0 + %5 = extractvalue %runtime._string %3, 1 + %6 = call i1 @runtime.stringLess(ptr %1, i32 %2, ptr %4, i32 %5, ptr undef) + ret i1 %6 +} diff --git a/transform/testdata/stringfrombytes-stringless.out.ll b/transform/testdata/stringfrombytes-stringless.out.ll new file mode 100644 index 0000000000..4e3e0f7832 --- /dev/null +++ b/transform/testdata/stringfrombytes-stringless.out.ll @@ -0,0 +1,40 @@ +target datalayout = "e-m:e-p:32:32-i64:64-v128:64-a:0:32-n32-S64" +target triple = "armv7m-none-eabi" + +%runtime._string = type { ptr, i32 } + +declare %runtime._string @runtime.stringFromBytes(ptr, i32, i32, ptr) + +declare i1 @runtime.stringLess(ptr, i32, ptr, i32, ptr) + +declare void @runtime.trackPointer(ptr, ptr, ptr) + +declare void @useString(ptr, i32) + +define i1 @main.bytesLess(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %b.data, i32 %b.len, i32 %b.cap, ptr %context) { +entry: + %stackalloc = alloca i8, align 1 + %0 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr undef) + ret i1 %0 +} + +define i1 @main.bytesLessString(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %s.data, i32 %s.len, ptr %context) { +entry: + %0 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef) + ret i1 %0 +} + +define i1 @main.stringLessBytes(ptr %s.data, i32 %s.len, ptr %a.data, i32 %a.len, i32 %a.cap, ptr %context) { +entry: + %0 = call i1 @runtime.stringLess(ptr %s.data, i32 %s.len, ptr %a.data, i32 %a.len, ptr undef) + ret i1 %0 +} + +define i1 @main.keepStringConversion(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %b.data, i32 %b.len, i32 %b.cap, ptr %context) { +entry: + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) + %1 = extractvalue %runtime._string %0, 0 + call void @useString(ptr %1, i32 %a.len) + %2 = call i1 @runtime.stringLess(ptr %1, i32 %a.len, ptr %b.data, i32 %b.len, ptr undef) + ret i1 %2 +}