diff --git a/compiler/compiler.go b/compiler/compiler.go index 80c60caff0..e4f71ed464 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -3061,10 +3061,7 @@ func isByteSliceToStringComparison(expr *ssa.Convert) bool { return false } comparison := adjacentComparison(expr, isByteSliceToStringConvert) - return comparison != nil && - isComparisonOp(comparison.Op) && - isByteSliceToStringConvert(comparison.X) && - isByteSliceToStringConvert(comparison.Y) + return comparison != nil && isComparisonOp(comparison.Op) } func isComparisonOp(op token.Token) bool { diff --git a/compiler/testdata/string.go b/compiler/testdata/string.go index c057398d71..d598f0490a 100644 --- a/compiler/testdata/string.go +++ b/compiler/testdata/string.go @@ -212,6 +212,118 @@ func namedByteSliceStringGreaterEqual(a, b namedBytes) bool { return namedString(a) >= namedString(b) } +func mixedByteString(a []byte, s string) [6]bool { + return [6]bool{string(a) == s, string(a) != s, string(a) < s, + string(a) <= s, string(a) > s, string(a) >= s} +} + +func mixedStringByte(s string, a []byte) [6]bool { + return [6]bool{s == string(a), s != string(a), s < string(a), + s <= string(a), s > string(a), s >= string(a)} +} + +func mixedByteLiteral(a []byte) [6]bool { + return [6]bool{string(a) == "abc", string(a) != "abc", string(a) < "abc", + string(a) <= "abc", string(a) > "abc", string(a) >= "abc"} +} + +func mixedLiteralByte(a []byte) [6]bool { + return [6]bool{"abc" == string(a), "abc" != string(a), "abc" < string(a), + "abc" <= string(a), "abc" > string(a), "abc" >= string(a)} +} + +func mixedNamedByteString(a namedBytes, s namedString) [6]bool { + return [6]bool{namedString(a) == s, namedString(a) != s, namedString(a) < s, + namedString(a) <= s, namedString(a) > s, namedString(a) >= s} +} + +func mixedNamedStringByte(s namedString, a namedBytes) [6]bool { + return [6]bool{s == namedString(a), s != namedString(a), s < namedString(a), + s <= namedString(a), s > namedString(a), s >= namedString(a)} +} + +func mixedLocalByteString(a []byte, s string) bool { + t := string(a) + return t == s +} + +func mixedLocalStringByte(s string, a []byte) bool { + t := string(a) + return s < t +} + +func mixedSliceByteString(a []byte, s string) bool { + return string(a[:2]) == s +} + +func mixedPrecomputedStringSlice(a []byte, s string) bool { + t := s[:2] + return string(a) == t +} + +func mixedByteStringReuse(a []byte, s string) (bool, bool) { + t := string(a) + return t == s, t < s +} + +func mixedByteStringEscape(a []byte, s string) (bool, string) { + t := string(a) + return t == s, t +} + +func mixedByteStringStore(a []byte, s string, dst *string) bool { + t := string(a) + equal := t == s + *dst = t + return equal +} + +func mixedByteStringBox(a []byte, s string) (bool, any) { + t := string(a) + return t == s, any(t) +} + +func mixedByteStringMutation(a, b []byte, s string) bool { + t := string(a) + b[0] = 'z' + return t == s +} + +func mixedByteStringCall(a, b []byte, s string) bool { + t := string(a) + mutateBytes(b) + return s < t +} + +func mixedByteStringAcrossBlock(a, b []byte, s string, mutate bool) bool { + t := string(a) + if mutate { + b[0] = 'z' + } + return t == s +} + +func mixedByteStringLoad(a []byte, s *string) bool { + return string(a) == *s +} + +func mixedStringLoadByte(s *string, a []byte) bool { + return *s == string(a) +} + +func mixedByteStringSlice(a []byte, s string) bool { + return string(a) == s[:2] +} + +func mixedByteStringChangeType(a []byte, s namedString) bool { + t := string(a) + return namedString(t) == s +} + +func mixedByteStringInterface(a []byte, s any) bool { + return string(a) == s +} + //go:noinline func mutateBytes(s []byte) []byte { s[0]++ diff --git a/compiler/testdata/string.ll b/compiler/testdata/string.ll index 155f4be5c9..d3903f411e 100644 --- a/compiler/testdata/string.ll +++ b/compiler/testdata/string.ll @@ -11,6 +11,18 @@ target triple = "wasm32-unknown-wasi" @"reflect/types.type:pointer:basic:string" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:string" }, align 4 @"main$string.1" = internal unnamed_addr constant [3 x i8] c"abc", align 1 @"main$string.2" = internal unnamed_addr constant [3 x i8] c"abc", align 1 +@"main$string.3" = internal unnamed_addr constant [3 x i8] c"abc", align 1 +@"main$string.4" = internal unnamed_addr constant [3 x i8] c"abc", align 1 +@"main$string.5" = internal unnamed_addr constant [3 x i8] c"abc", align 1 +@"main$string.6" = internal unnamed_addr constant [3 x i8] c"abc", align 1 +@"main$string.7" = internal unnamed_addr constant [3 x i8] c"abc", align 1 +@"main$string.8" = internal unnamed_addr constant [3 x i8] c"abc", align 1 +@"main$string.9" = internal unnamed_addr constant [3 x i8] c"abc", align 1 +@"main$string.10" = internal unnamed_addr constant [3 x i8] c"abc", align 1 +@"main$string.11" = internal unnamed_addr constant [3 x i8] c"abc", align 1 +@"main$string.12" = internal unnamed_addr constant [3 x i8] c"abc", align 1 +@"main$string.13" = internal unnamed_addr constant [3 x i8] c"abc", align 1 +@"main$string.14" = internal unnamed_addr constant [3 x i8] c"abc", align 1 declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #0 @@ -348,13 +360,9 @@ unwind.return: ; preds = %slice.throw define hidden i1 @main.byteSliceStringCompareLiteral(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) #4 - %1 = extractvalue %runtime._string %0, 0 - call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #4 - %2 = extractvalue %runtime._string %0, 0 - %3 = extractvalue %runtime._string %0, 1 - %4 = call i1 @runtime.stringEqual(ptr %2, i32 %3, ptr nonnull @"main$string.1", i32 3, ptr undef) #4 - ret i1 %4 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %0 = call i1 @runtime.stringEqual(ptr %a.data, i32 %a.len, ptr nonnull @"main$string.1", i32 3, ptr undef) #4 + ret i1 %0 } ; Function Attrs: nounwind @@ -503,13 +511,9 @@ entry: define hidden i1 @main.byteSliceStringLessLiteral(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) #4 - %1 = extractvalue %runtime._string %0, 0 - call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #4 - %2 = extractvalue %runtime._string %0, 0 - %3 = extractvalue %runtime._string %0, 1 - %4 = call i1 @runtime.stringLess(ptr %2, i32 %3, ptr nonnull @"main$string.2", i32 3, ptr undef) #4 - ret i1 %4 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %0 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr nonnull @"main$string.2", i32 3, ptr undef) #4 + ret i1 %0 } ; Function Attrs: nounwind @@ -706,6 +710,640 @@ entry: ret i1 %1 } +; Function Attrs: nounwind +define hidden [6 x i1] @main.mixedByteString(ptr %a.data, i32 %a.len, i32 %a.cap, ptr readonly %s.data, i32 %s.len, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + %complit = call align 1 dereferenceable(6) ptr @runtime.alloc(i32 6, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4 + call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #4 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %0 = call i1 @runtime.stringEqual(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef) #4 + %1 = getelementptr inbounds nuw i8, ptr %complit, i32 1 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %2 = call i1 @runtime.stringEqual(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef) #4 + %3 = xor i1 %2, true + %4 = getelementptr inbounds nuw i8, ptr %complit, i32 2 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %5 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef) #4 + %6 = getelementptr inbounds nuw i8, ptr %complit, i32 3 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %7 = call i1 @runtime.stringLess(ptr %s.data, i32 %s.len, ptr %a.data, i32 %a.len, ptr undef) #4 + %8 = xor i1 %7, true + %9 = getelementptr inbounds nuw i8, ptr %complit, i32 4 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %10 = call i1 @runtime.stringLess(ptr %s.data, i32 %s.len, ptr %a.data, i32 %a.len, ptr undef) #4 + %11 = getelementptr inbounds nuw i8, ptr %complit, i32 5 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %12 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef) #4 + %13 = xor i1 %12, true + store i1 %0, ptr %complit, align 1 + store i1 %3, ptr %1, align 1 + store i1 %5, ptr %4, align 1 + store i1 %8, ptr %6, align 1 + store i1 %10, ptr %9, align 1 + store i1 %13, ptr %11, align 1 + %14 = insertvalue [6 x i1] poison, i1 %0, 0 + %.elt1 = getelementptr inbounds nuw i8, ptr %complit, i32 1 + %.unpack2 = load i1, ptr %.elt1, align 1 + %15 = insertvalue [6 x i1] %14, i1 %.unpack2, 1 + %.elt3 = getelementptr inbounds nuw i8, ptr %complit, i32 2 + %.unpack4 = load i1, ptr %.elt3, align 1 + %16 = insertvalue [6 x i1] %15, i1 %.unpack4, 2 + %.elt5 = getelementptr inbounds nuw i8, ptr %complit, i32 3 + %.unpack6 = load i1, ptr %.elt5, align 1 + %17 = insertvalue [6 x i1] %16, i1 %.unpack6, 3 + %.elt7 = getelementptr inbounds nuw i8, ptr %complit, i32 4 + %.unpack8 = load i1, ptr %.elt7, align 1 + %18 = insertvalue [6 x i1] %17, i1 %.unpack8, 4 + %.elt9 = getelementptr inbounds nuw i8, ptr %complit, i32 5 + %.unpack10 = load i1, ptr %.elt9, align 1 + %19 = insertvalue [6 x i1] %18, i1 %.unpack10, 5 + ret [6 x i1] %19 +} + +; Function Attrs: nounwind +define hidden [6 x i1] @main.mixedStringByte(ptr readonly %s.data, i32 %s.len, ptr %a.data, i32 %a.len, i32 %a.cap, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + %complit = call align 1 dereferenceable(6) ptr @runtime.alloc(i32 6, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4 + call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #4 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %0 = call i1 @runtime.stringEqual(ptr %s.data, i32 %s.len, ptr %a.data, i32 %a.len, ptr undef) #4 + %1 = getelementptr inbounds nuw i8, ptr %complit, i32 1 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %2 = call i1 @runtime.stringEqual(ptr %s.data, i32 %s.len, ptr %a.data, i32 %a.len, ptr undef) #4 + %3 = xor i1 %2, true + %4 = getelementptr inbounds nuw i8, ptr %complit, i32 2 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %5 = call i1 @runtime.stringLess(ptr %s.data, i32 %s.len, ptr %a.data, i32 %a.len, ptr undef) #4 + %6 = getelementptr inbounds nuw i8, ptr %complit, i32 3 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %7 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef) #4 + %8 = xor i1 %7, true + %9 = getelementptr inbounds nuw i8, ptr %complit, i32 4 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %10 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef) #4 + %11 = getelementptr inbounds nuw i8, ptr %complit, i32 5 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %12 = call i1 @runtime.stringLess(ptr %s.data, i32 %s.len, ptr %a.data, i32 %a.len, ptr undef) #4 + %13 = xor i1 %12, true + store i1 %0, ptr %complit, align 1 + store i1 %3, ptr %1, align 1 + store i1 %5, ptr %4, align 1 + store i1 %8, ptr %6, align 1 + store i1 %10, ptr %9, align 1 + store i1 %13, ptr %11, align 1 + %14 = insertvalue [6 x i1] poison, i1 %0, 0 + %.elt1 = getelementptr inbounds nuw i8, ptr %complit, i32 1 + %.unpack2 = load i1, ptr %.elt1, align 1 + %15 = insertvalue [6 x i1] %14, i1 %.unpack2, 1 + %.elt3 = getelementptr inbounds nuw i8, ptr %complit, i32 2 + %.unpack4 = load i1, ptr %.elt3, align 1 + %16 = insertvalue [6 x i1] %15, i1 %.unpack4, 2 + %.elt5 = getelementptr inbounds nuw i8, ptr %complit, i32 3 + %.unpack6 = load i1, ptr %.elt5, align 1 + %17 = insertvalue [6 x i1] %16, i1 %.unpack6, 3 + %.elt7 = getelementptr inbounds nuw i8, ptr %complit, i32 4 + %.unpack8 = load i1, ptr %.elt7, align 1 + %18 = insertvalue [6 x i1] %17, i1 %.unpack8, 4 + %.elt9 = getelementptr inbounds nuw i8, ptr %complit, i32 5 + %.unpack10 = load i1, ptr %.elt9, align 1 + %19 = insertvalue [6 x i1] %18, i1 %.unpack10, 5 + ret [6 x i1] %19 +} + +; Function Attrs: nounwind +define hidden [6 x i1] @main.mixedByteLiteral(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + %complit = call align 1 dereferenceable(6) ptr @runtime.alloc(i32 6, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4 + call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #4 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %0 = call i1 @runtime.stringEqual(ptr %a.data, i32 %a.len, ptr nonnull @"main$string.3", i32 3, ptr undef) #4 + %1 = getelementptr inbounds nuw i8, ptr %complit, i32 1 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %2 = call i1 @runtime.stringEqual(ptr %a.data, i32 %a.len, ptr nonnull @"main$string.4", i32 3, ptr undef) #4 + %3 = xor i1 %2, true + %4 = getelementptr inbounds nuw i8, ptr %complit, i32 2 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %5 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr nonnull @"main$string.5", i32 3, ptr undef) #4 + %6 = getelementptr inbounds nuw i8, ptr %complit, i32 3 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %7 = call i1 @runtime.stringLess(ptr nonnull @"main$string.6", i32 3, ptr %a.data, i32 %a.len, ptr undef) #4 + %8 = xor i1 %7, true + %9 = getelementptr inbounds nuw i8, ptr %complit, i32 4 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %10 = call i1 @runtime.stringLess(ptr nonnull @"main$string.7", i32 3, ptr %a.data, i32 %a.len, ptr undef) #4 + %11 = getelementptr inbounds nuw i8, ptr %complit, i32 5 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %12 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr nonnull @"main$string.8", i32 3, ptr undef) #4 + %13 = xor i1 %12, true + store i1 %0, ptr %complit, align 1 + store i1 %3, ptr %1, align 1 + store i1 %5, ptr %4, align 1 + store i1 %8, ptr %6, align 1 + store i1 %10, ptr %9, align 1 + store i1 %13, ptr %11, align 1 + %14 = insertvalue [6 x i1] poison, i1 %0, 0 + %.elt1 = getelementptr inbounds nuw i8, ptr %complit, i32 1 + %.unpack2 = load i1, ptr %.elt1, align 1 + %15 = insertvalue [6 x i1] %14, i1 %.unpack2, 1 + %.elt3 = getelementptr inbounds nuw i8, ptr %complit, i32 2 + %.unpack4 = load i1, ptr %.elt3, align 1 + %16 = insertvalue [6 x i1] %15, i1 %.unpack4, 2 + %.elt5 = getelementptr inbounds nuw i8, ptr %complit, i32 3 + %.unpack6 = load i1, ptr %.elt5, align 1 + %17 = insertvalue [6 x i1] %16, i1 %.unpack6, 3 + %.elt7 = getelementptr inbounds nuw i8, ptr %complit, i32 4 + %.unpack8 = load i1, ptr %.elt7, align 1 + %18 = insertvalue [6 x i1] %17, i1 %.unpack8, 4 + %.elt9 = getelementptr inbounds nuw i8, ptr %complit, i32 5 + %.unpack10 = load i1, ptr %.elt9, align 1 + %19 = insertvalue [6 x i1] %18, i1 %.unpack10, 5 + ret [6 x i1] %19 +} + +; Function Attrs: nounwind +define hidden [6 x i1] @main.mixedLiteralByte(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + %complit = call align 1 dereferenceable(6) ptr @runtime.alloc(i32 6, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4 + call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #4 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %0 = call i1 @runtime.stringEqual(ptr nonnull @"main$string.9", i32 3, ptr %a.data, i32 %a.len, ptr undef) #4 + %1 = getelementptr inbounds nuw i8, ptr %complit, i32 1 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %2 = call i1 @runtime.stringEqual(ptr nonnull @"main$string.10", i32 3, ptr %a.data, i32 %a.len, ptr undef) #4 + %3 = xor i1 %2, true + %4 = getelementptr inbounds nuw i8, ptr %complit, i32 2 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %5 = call i1 @runtime.stringLess(ptr nonnull @"main$string.11", i32 3, ptr %a.data, i32 %a.len, ptr undef) #4 + %6 = getelementptr inbounds nuw i8, ptr %complit, i32 3 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %7 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr nonnull @"main$string.12", i32 3, ptr undef) #4 + %8 = xor i1 %7, true + %9 = getelementptr inbounds nuw i8, ptr %complit, i32 4 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %10 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr nonnull @"main$string.13", i32 3, ptr undef) #4 + %11 = getelementptr inbounds nuw i8, ptr %complit, i32 5 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %12 = call i1 @runtime.stringLess(ptr nonnull @"main$string.14", i32 3, ptr %a.data, i32 %a.len, ptr undef) #4 + %13 = xor i1 %12, true + store i1 %0, ptr %complit, align 1 + store i1 %3, ptr %1, align 1 + store i1 %5, ptr %4, align 1 + store i1 %8, ptr %6, align 1 + store i1 %10, ptr %9, align 1 + store i1 %13, ptr %11, align 1 + %14 = insertvalue [6 x i1] poison, i1 %0, 0 + %.elt1 = getelementptr inbounds nuw i8, ptr %complit, i32 1 + %.unpack2 = load i1, ptr %.elt1, align 1 + %15 = insertvalue [6 x i1] %14, i1 %.unpack2, 1 + %.elt3 = getelementptr inbounds nuw i8, ptr %complit, i32 2 + %.unpack4 = load i1, ptr %.elt3, align 1 + %16 = insertvalue [6 x i1] %15, i1 %.unpack4, 2 + %.elt5 = getelementptr inbounds nuw i8, ptr %complit, i32 3 + %.unpack6 = load i1, ptr %.elt5, align 1 + %17 = insertvalue [6 x i1] %16, i1 %.unpack6, 3 + %.elt7 = getelementptr inbounds nuw i8, ptr %complit, i32 4 + %.unpack8 = load i1, ptr %.elt7, align 1 + %18 = insertvalue [6 x i1] %17, i1 %.unpack8, 4 + %.elt9 = getelementptr inbounds nuw i8, ptr %complit, i32 5 + %.unpack10 = load i1, ptr %.elt9, align 1 + %19 = insertvalue [6 x i1] %18, i1 %.unpack10, 5 + ret [6 x i1] %19 +} + +; Function Attrs: nounwind +define hidden [6 x i1] @main.mixedNamedByteString(ptr %a.data, i32 %a.len, i32 %a.cap, ptr readonly %s.data, i32 %s.len, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + %complit = call align 1 dereferenceable(6) ptr @runtime.alloc(i32 6, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4 + call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #4 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %0 = call i1 @runtime.stringEqual(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef) #4 + %1 = getelementptr inbounds nuw i8, ptr %complit, i32 1 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %2 = call i1 @runtime.stringEqual(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef) #4 + %3 = xor i1 %2, true + %4 = getelementptr inbounds nuw i8, ptr %complit, i32 2 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %5 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef) #4 + %6 = getelementptr inbounds nuw i8, ptr %complit, i32 3 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %7 = call i1 @runtime.stringLess(ptr %s.data, i32 %s.len, ptr %a.data, i32 %a.len, ptr undef) #4 + %8 = xor i1 %7, true + %9 = getelementptr inbounds nuw i8, ptr %complit, i32 4 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %10 = call i1 @runtime.stringLess(ptr %s.data, i32 %s.len, ptr %a.data, i32 %a.len, ptr undef) #4 + %11 = getelementptr inbounds nuw i8, ptr %complit, i32 5 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %12 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef) #4 + %13 = xor i1 %12, true + store i1 %0, ptr %complit, align 1 + store i1 %3, ptr %1, align 1 + store i1 %5, ptr %4, align 1 + store i1 %8, ptr %6, align 1 + store i1 %10, ptr %9, align 1 + store i1 %13, ptr %11, align 1 + %14 = insertvalue [6 x i1] poison, i1 %0, 0 + %.elt1 = getelementptr inbounds nuw i8, ptr %complit, i32 1 + %.unpack2 = load i1, ptr %.elt1, align 1 + %15 = insertvalue [6 x i1] %14, i1 %.unpack2, 1 + %.elt3 = getelementptr inbounds nuw i8, ptr %complit, i32 2 + %.unpack4 = load i1, ptr %.elt3, align 1 + %16 = insertvalue [6 x i1] %15, i1 %.unpack4, 2 + %.elt5 = getelementptr inbounds nuw i8, ptr %complit, i32 3 + %.unpack6 = load i1, ptr %.elt5, align 1 + %17 = insertvalue [6 x i1] %16, i1 %.unpack6, 3 + %.elt7 = getelementptr inbounds nuw i8, ptr %complit, i32 4 + %.unpack8 = load i1, ptr %.elt7, align 1 + %18 = insertvalue [6 x i1] %17, i1 %.unpack8, 4 + %.elt9 = getelementptr inbounds nuw i8, ptr %complit, i32 5 + %.unpack10 = load i1, ptr %.elt9, align 1 + %19 = insertvalue [6 x i1] %18, i1 %.unpack10, 5 + ret [6 x i1] %19 +} + +; Function Attrs: nounwind +define hidden [6 x i1] @main.mixedNamedStringByte(ptr readonly %s.data, i32 %s.len, ptr %a.data, i32 %a.len, i32 %a.cap, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + %complit = call align 1 dereferenceable(6) ptr @runtime.alloc(i32 6, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4 + call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #4 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %0 = call i1 @runtime.stringEqual(ptr %s.data, i32 %s.len, ptr %a.data, i32 %a.len, ptr undef) #4 + %1 = getelementptr inbounds nuw i8, ptr %complit, i32 1 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %2 = call i1 @runtime.stringEqual(ptr %s.data, i32 %s.len, ptr %a.data, i32 %a.len, ptr undef) #4 + %3 = xor i1 %2, true + %4 = getelementptr inbounds nuw i8, ptr %complit, i32 2 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %5 = call i1 @runtime.stringLess(ptr %s.data, i32 %s.len, ptr %a.data, i32 %a.len, ptr undef) #4 + %6 = getelementptr inbounds nuw i8, ptr %complit, i32 3 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %7 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef) #4 + %8 = xor i1 %7, true + %9 = getelementptr inbounds nuw i8, ptr %complit, i32 4 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %10 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef) #4 + %11 = getelementptr inbounds nuw i8, ptr %complit, i32 5 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %12 = call i1 @runtime.stringLess(ptr %s.data, i32 %s.len, ptr %a.data, i32 %a.len, ptr undef) #4 + %13 = xor i1 %12, true + store i1 %0, ptr %complit, align 1 + store i1 %3, ptr %1, align 1 + store i1 %5, ptr %4, align 1 + store i1 %8, ptr %6, align 1 + store i1 %10, ptr %9, align 1 + store i1 %13, ptr %11, align 1 + %14 = insertvalue [6 x i1] poison, i1 %0, 0 + %.elt1 = getelementptr inbounds nuw i8, ptr %complit, i32 1 + %.unpack2 = load i1, ptr %.elt1, align 1 + %15 = insertvalue [6 x i1] %14, i1 %.unpack2, 1 + %.elt3 = getelementptr inbounds nuw i8, ptr %complit, i32 2 + %.unpack4 = load i1, ptr %.elt3, align 1 + %16 = insertvalue [6 x i1] %15, i1 %.unpack4, 2 + %.elt5 = getelementptr inbounds nuw i8, ptr %complit, i32 3 + %.unpack6 = load i1, ptr %.elt5, align 1 + %17 = insertvalue [6 x i1] %16, i1 %.unpack6, 3 + %.elt7 = getelementptr inbounds nuw i8, ptr %complit, i32 4 + %.unpack8 = load i1, ptr %.elt7, align 1 + %18 = insertvalue [6 x i1] %17, i1 %.unpack8, 4 + %.elt9 = getelementptr inbounds nuw i8, ptr %complit, i32 5 + %.unpack10 = load i1, ptr %.elt9, align 1 + %19 = insertvalue [6 x i1] %18, i1 %.unpack10, 5 + ret [6 x i1] %19 +} + +; Function Attrs: nounwind +define hidden i1 @main.mixedLocalByteString(ptr %a.data, i32 %a.len, i32 %a.cap, ptr readonly %s.data, i32 %s.len, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %0 = call i1 @runtime.stringEqual(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef) #4 + ret i1 %0 +} + +; Function Attrs: nounwind +define hidden i1 @main.mixedLocalStringByte(ptr readonly %s.data, i32 %s.len, ptr %a.data, i32 %a.len, i32 %a.cap, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %0 = call i1 @runtime.stringLess(ptr %s.data, i32 %s.len, ptr %a.data, i32 %a.len, ptr undef) #4 + ret i1 %0 +} + +; Function Attrs: nounwind +define hidden i1 @main.mixedSliceByteString(ptr %a.data, i32 %a.len, i32 %a.cap, ptr readonly %s.data, i32 %s.len, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + %slice.highmax = icmp ult i32 %a.cap, 2 + br i1 %slice.highmax, label %slice.throw, label %slice.next + +slice.next: ; preds = %entry + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %0 = call i1 @runtime.stringEqual(ptr %a.data, i32 2, ptr %s.data, i32 %s.len, ptr undef) #4 + ret i1 %0 + +slice.throw: ; preds = %entry + call void @runtime.slicePanic(ptr undef) #4 + br label %unwind.return + +unwind.return: ; preds = %slice.throw + ret i1 undef +} + +; Function Attrs: nounwind +define hidden i1 @main.mixedPrecomputedStringSlice(ptr %a.data, i32 %a.len, i32 %a.cap, ptr readonly %s.data, i32 %s.len, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + %slice.maxcap = icmp ult i32 %s.len, 2 + br i1 %slice.maxcap, label %slice.throw, label %slice.next + +slice.next: ; preds = %entry + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %0 = call i1 @runtime.stringEqual(ptr %a.data, i32 %a.len, ptr %s.data, i32 2, ptr undef) #4 + ret i1 %0 + +slice.throw: ; preds = %entry + call void @runtime.slicePanic(ptr undef) #4 + br label %unwind.return + +unwind.return: ; preds = %slice.throw + ret i1 undef +} + +; Function Attrs: nounwind +define hidden { i1, i1 } @main.mixedByteStringReuse(ptr %a.data, i32 %a.len, i32 %a.cap, ptr readonly %s.data, i32 %s.len, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) #4 + %1 = extractvalue %runtime._string %0, 0 + call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #4 + %2 = extractvalue %runtime._string %0, 0 + %3 = extractvalue %runtime._string %0, 1 + %4 = call i1 @runtime.stringEqual(ptr %2, i32 %3, ptr %s.data, i32 %s.len, ptr undef) #4 + %5 = extractvalue %runtime._string %0, 0 + %6 = extractvalue %runtime._string %0, 1 + %7 = call i1 @runtime.stringLess(ptr %5, i32 %6, ptr %s.data, i32 %s.len, ptr undef) #4 + %8 = insertvalue { i1, i1 } zeroinitializer, i1 %4, 0 + %9 = insertvalue { i1, i1 } %8, i1 %7, 1 + ret { i1, i1 } %9 +} + +; Function Attrs: nounwind +define hidden { i1, %runtime._string } @main.mixedByteStringEscape(ptr %a.data, i32 %a.len, i32 %a.cap, ptr readonly %s.data, i32 %s.len, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) #4 + %1 = extractvalue %runtime._string %0, 0 + call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #4 + %2 = extractvalue %runtime._string %0, 0 + %3 = extractvalue %runtime._string %0, 1 + %4 = call i1 @runtime.stringEqual(ptr %2, i32 %3, ptr %s.data, i32 %s.len, ptr undef) #4 + %5 = insertvalue { i1, %runtime._string } zeroinitializer, i1 %4, 0 + %6 = insertvalue { i1, %runtime._string } %5, %runtime._string %0, 1 + ret { i1, %runtime._string } %6 +} + +; Function Attrs: nounwind +define hidden i1 @main.mixedByteStringStore(ptr %a.data, i32 %a.len, i32 %a.cap, ptr readonly %s.data, i32 %s.len, ptr dereferenceable_or_null(8) %dst, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) #4 + %1 = extractvalue %runtime._string %0, 0 + call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #4 + %2 = extractvalue %runtime._string %0, 0 + %3 = extractvalue %runtime._string %0, 1 + %4 = call i1 @runtime.stringEqual(ptr %2, i32 %3, ptr %s.data, i32 %s.len, ptr undef) #4 + %5 = icmp eq ptr %dst, null + br i1 %5, label %store.throw, label %store.next + +store.next: ; preds = %entry + %.elt = extractvalue %runtime._string %0, 0 + store ptr %.elt, ptr %dst, align 4 + %dst.repack1 = getelementptr inbounds nuw i8, ptr %dst, i32 4 + %.elt2 = extractvalue %runtime._string %0, 1 + store i32 %.elt2, ptr %dst.repack1, align 4 + ret i1 %4 + +store.throw: ; preds = %entry + call void @runtime.nilPanic(ptr undef) #4 + br label %unwind.return + +unwind.return: ; preds = %store.throw + ret i1 undef +} + +; Function Attrs: nounwind +define hidden { i1, %runtime._interface } @main.mixedByteStringBox(ptr %a.data, i32 %a.len, i32 %a.cap, ptr readonly %s.data, i32 %s.len, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) #4 + %1 = extractvalue %runtime._string %0, 0 + call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #4 + %2 = extractvalue %runtime._string %0, 0 + %3 = extractvalue %runtime._string %0, 1 + %4 = call i1 @runtime.stringEqual(ptr %2, i32 %3, ptr %s.data, i32 %s.len, ptr undef) #4 + %5 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 69 to ptr), ptr undef) #4 + call void @runtime.trackPointer(ptr nonnull %5, ptr nonnull %stackalloc, ptr undef) #4 + %.elt = extractvalue %runtime._string %0, 0 + store ptr %.elt, ptr %5, align 4 + %.repack1 = getelementptr inbounds nuw i8, ptr %5, i32 4 + %.elt2 = extractvalue %runtime._string %0, 1 + store i32 %.elt2, ptr %.repack1, align 4 + %6 = insertvalue %runtime._interface { ptr @"reflect/types.type:basic:string", ptr undef }, ptr %5, 1 + call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:basic:string", ptr nonnull %stackalloc, ptr undef) #4 + call void @runtime.trackPointer(ptr nonnull %5, ptr nonnull %stackalloc, ptr undef) #4 + %7 = insertvalue { i1, %runtime._interface } zeroinitializer, i1 %4, 0 + %8 = insertvalue { i1, %runtime._interface } %7, %runtime._interface %6, 1 + ret { i1, %runtime._interface } %8 +} + +; Function Attrs: nounwind +define hidden i1 @main.mixedByteStringMutation(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %b.data, i32 %b.len, i32 %b.cap, ptr readonly %s.data, i32 %s.len, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) #4 + %1 = extractvalue %runtime._string %0, 0 + call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #4 + %2 = icmp eq i32 %b.len, 0 + br i1 %2, label %lookup.throw, label %lookup.next + +lookup.next: ; preds = %entry + store i8 122, ptr %b.data, align 1 + %3 = extractvalue %runtime._string %0, 0 + %4 = extractvalue %runtime._string %0, 1 + %5 = call i1 @runtime.stringEqual(ptr %3, i32 %4, ptr %s.data, i32 %s.len, ptr undef) #4 + ret i1 %5 + +lookup.throw: ; preds = %entry + call void @runtime.lookupPanic(ptr undef) #4 + br label %unwind.return + +unwind.return: ; preds = %lookup.throw + ret i1 undef +} + +; Function Attrs: nounwind +define hidden i1 @main.mixedByteStringCall(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %b.data, i32 %b.len, i32 %b.cap, ptr readonly %s.data, i32 %s.len, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) #4 + %1 = extractvalue %runtime._string %0, 0 + call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #4 + %2 = call { ptr, i32, i32 } @main.mutateBytes(ptr %b.data, i32 %b.len, i32 %b.cap, ptr undef) + %3 = extractvalue { ptr, i32, i32 } %2, 0 + call void @runtime.trackPointer(ptr %3, ptr nonnull %stackalloc, ptr undef) #4 + %4 = extractvalue %runtime._string %0, 0 + %5 = extractvalue %runtime._string %0, 1 + %6 = call i1 @runtime.stringLess(ptr %s.data, i32 %s.len, ptr %4, i32 %5, ptr undef) #4 + ret i1 %6 +} + +; Function Attrs: nounwind +define hidden i1 @main.mixedByteStringAcrossBlock(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %b.data, i32 %b.len, i32 %b.cap, ptr readonly %s.data, i32 %s.len, i1 %mutate, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) #4 + %1 = extractvalue %runtime._string %0, 0 + call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #4 + br i1 %mutate, label %if.then, label %if.done + +if.then: ; preds = %entry + %2 = icmp eq i32 %b.len, 0 + br i1 %2, label %lookup.throw, label %lookup.next + +lookup.next: ; preds = %if.then + store i8 122, ptr %b.data, align 1 + br label %if.done + +if.done: ; preds = %lookup.next, %entry + %3 = extractvalue %runtime._string %0, 0 + %4 = extractvalue %runtime._string %0, 1 + %5 = call i1 @runtime.stringEqual(ptr %3, i32 %4, ptr %s.data, i32 %s.len, ptr undef) #4 + ret i1 %5 + +lookup.throw: ; preds = %if.then + call void @runtime.lookupPanic(ptr undef) #4 + br label %unwind.return + +unwind.return: ; preds = %lookup.throw + ret i1 undef +} + +; Function Attrs: nounwind +define hidden i1 @main.mixedByteStringLoad(ptr %a.data, i32 %a.len, i32 %a.cap, ptr dereferenceable_or_null(8) %s, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) #4 + %1 = extractvalue %runtime._string %0, 0 + call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #4 + %2 = icmp eq ptr %s, null + br i1 %2, label %deref.throw, label %deref.next + +deref.next: ; preds = %entry + %.unpack = load ptr, ptr %s, align 4 + %.elt1 = getelementptr inbounds nuw i8, ptr %s, i32 4 + %.unpack2 = load i32, ptr %.elt1, align 4 + call void @runtime.trackPointer(ptr %.unpack, ptr nonnull %stackalloc, ptr undef) #4 + %3 = extractvalue %runtime._string %0, 0 + %4 = extractvalue %runtime._string %0, 1 + %5 = call i1 @runtime.stringEqual(ptr %3, i32 %4, ptr %.unpack, i32 %.unpack2, ptr undef) #4 + ret i1 %5 + +deref.throw: ; preds = %entry + call void @runtime.nilPanic(ptr undef) #4 + br label %unwind.return + +unwind.return: ; preds = %deref.throw + ret i1 undef +} + +; Function Attrs: nounwind +define hidden i1 @main.mixedStringLoadByte(ptr dereferenceable_or_null(8) %s, ptr %a.data, i32 %a.len, i32 %a.cap, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + %0 = icmp eq ptr %s, null + br i1 %0, label %deref.throw, label %deref.next + +deref.next: ; preds = %entry + %.unpack = load ptr, ptr %s, align 4 + %.elt1 = getelementptr inbounds nuw i8, ptr %s, i32 4 + %.unpack2 = load i32, ptr %.elt1, align 4 + call void @runtime.trackPointer(ptr %.unpack, ptr nonnull %stackalloc, ptr undef) #4 + call void @runtime.trackPointer(ptr %a.data, ptr nonnull %stackalloc, ptr undef) #4 + %1 = call i1 @runtime.stringEqual(ptr %.unpack, i32 %.unpack2, ptr %a.data, i32 %a.len, ptr undef) #4 + ret i1 %1 + +deref.throw: ; preds = %entry + call void @runtime.nilPanic(ptr undef) #4 + br label %unwind.return + +unwind.return: ; preds = %deref.throw + ret i1 undef +} + +; Function Attrs: nounwind +define hidden i1 @main.mixedByteStringSlice(ptr %a.data, i32 %a.len, i32 %a.cap, ptr readonly %s.data, i32 %s.len, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) #4 + %1 = extractvalue %runtime._string %0, 0 + call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #4 + %slice.maxcap = icmp ult i32 %s.len, 2 + br i1 %slice.maxcap, label %slice.throw, label %slice.next + +slice.next: ; preds = %entry + %2 = extractvalue %runtime._string %0, 0 + %3 = extractvalue %runtime._string %0, 1 + %4 = call i1 @runtime.stringEqual(ptr %2, i32 %3, ptr %s.data, i32 2, ptr undef) #4 + ret i1 %4 + +slice.throw: ; preds = %entry + call void @runtime.slicePanic(ptr undef) #4 + br label %unwind.return + +unwind.return: ; preds = %slice.throw + ret i1 undef +} + +; Function Attrs: nounwind +define hidden i1 @main.mixedByteStringChangeType(ptr %a.data, i32 %a.len, i32 %a.cap, ptr readonly %s.data, i32 %s.len, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) #4 + %1 = extractvalue %runtime._string %0, 0 + call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #4 + %2 = extractvalue %runtime._string %0, 0 + %3 = extractvalue %runtime._string %0, 1 + %4 = call i1 @runtime.stringEqual(ptr %2, i32 %3, ptr %s.data, i32 %s.len, ptr undef) #4 + ret i1 %4 +} + +; Function Attrs: nounwind +define hidden i1 @main.mixedByteStringInterface(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %s.typecode, ptr %s.value, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + %0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef) #4 + %1 = extractvalue %runtime._string %0, 0 + call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #4 + %2 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 69 to ptr), ptr undef) #4 + call void @runtime.trackPointer(ptr nonnull %2, ptr nonnull %stackalloc, ptr undef) #4 + %.elt = extractvalue %runtime._string %0, 0 + store ptr %.elt, ptr %2, align 4 + %.repack1 = getelementptr inbounds nuw i8, ptr %2, i32 4 + %.elt2 = extractvalue %runtime._string %0, 1 + store i32 %.elt2, ptr %.repack1, align 4 + call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:basic:string", ptr nonnull %stackalloc, ptr undef) #4 + call void @runtime.trackPointer(ptr nonnull %2, ptr nonnull %stackalloc, ptr undef) #4 + %3 = call i1 @runtime.interfaceEqual(ptr nonnull @"reflect/types.type:basic:string", ptr nonnull %2, ptr %s.typecode, ptr %s.value, ptr undef) #4 + ret i1 %3 +} + +declare i1 @runtime.interfaceEqual(ptr, ptr, ptr, ptr, ptr) #0 + ; Function Attrs: nounwind define hidden i1 @main.stringCompareLarger(ptr readonly %s1.data, i32 %s1.len, ptr readonly %s2.data, i32 %s2.len, ptr %context) unnamed_addr #1 { entry: diff --git a/main_test.go b/main_test.go index d752baa0df..2ec56564ba 100644 --- a/main_test.go +++ b/main_test.go @@ -536,8 +536,8 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { if name == "finalizerinvariants.go" || name == "finalizerlarge.go" { testOptions.Tags = append(append([]string(nil), options.Tags...), "runtime_asserts") } - if testOptions.Target == "simavr" && name == "math.go" { - // This test exceeds simavr's default 384-byte goroutine stack. + if testOptions.Target == "simavr" && (name == "math.go" || name == "string.go") { + // These tests exceed simavr's default 384-byte goroutine stack. testOptions.StackSize = 512 } runTest(name, testOptions, t, nil, nil) @@ -1406,6 +1406,26 @@ func TestTest(t *testing.T) { emuCheck(t, targ.opts) + t.Run("StringComparisons", func(t *testing.T) { + t.Parallel() + + var wg sync.WaitGroup + defer wg.Wait() + + out := ioLogger(t, &wg) + defer out.Close() + + opts := targ.opts + opts.GC = "conservative" + passed, err := Test("github.com/tinygo-org/tinygo/tests/stringcompare", out, out, &opts, "") + if err != nil { + t.Errorf("test error: %v", err) + } + if !passed { + t.Error("test failed") + } + }) + t.Run("Pass", func(t *testing.T) { t.Parallel() diff --git a/testdata/string.go b/testdata/string.go index 71b15121a2..ba1f2fa8dc 100644 --- a/testdata/string.go +++ b/testdata/string.go @@ -364,6 +364,206 @@ func namedGreaterEqualByteStrings(a, b myBytes) bool { return myString(a) >= myString(b) } +//go:noinline +func mixedByteString(a []byte, s string) [6]bool { + return [6]bool{string(a) == s, string(a) != s, string(a) < s, + string(a) <= s, string(a) > s, string(a) >= s} +} + +//go:noinline +func mixedStringByte(s string, a []byte) [6]bool { + return [6]bool{s == string(a), s != string(a), s < string(a), + s <= string(a), s > string(a), s >= string(a)} +} + +//go:noinline +func mixedByteLiteral(a []byte) [6]bool { + return [6]bool{string(a) == "abc", string(a) != "abc", string(a) < "abc", + string(a) <= "abc", string(a) > "abc", string(a) >= "abc"} +} + +//go:noinline +func mixedLiteralByte(a []byte) [6]bool { + return [6]bool{"abc" == string(a), "abc" != string(a), "abc" < string(a), + "abc" <= string(a), "abc" > string(a), "abc" >= string(a)} +} + +//go:noinline +func mixedNamedByteString(a myBytes, s myString) [6]bool { + return [6]bool{myString(a) == s, myString(a) != s, myString(a) < s, + myString(a) <= s, myString(a) > s, myString(a) >= s} +} + +//go:noinline +func mixedNamedStringByte(s myString, a myBytes) [6]bool { + return [6]bool{s == myString(a), s != myString(a), s < myString(a), + s <= myString(a), s > myString(a), s >= myString(a)} +} + +//go:noinline +func mixedStringReference(s, t string) [6]bool { + return [6]bool{s == t, s != t, s < t, s <= t, s > t, s >= t} +} + +//go:noinline +func mixedByteStringReuse(a []byte, s string) (bool, bool) { + t := string(a) + equal := t == s + a[0] = 'z' + return equal, t < s +} + +//go:noinline +func mixedByteStringEscape(a []byte, s string) (bool, string) { + t := string(a) + return t == s, t +} + +//go:noinline +func mixedByteStringStore(a []byte, s string, dst *string) bool { + t := string(a) + equal := t == s + *dst = t + return equal +} + +//go:noinline +func mixedByteStringBox(a []byte, s string) (bool, any) { + t := string(a) + return t == s, any(t) +} + +//go:noinline +func mixedByteStringMutation(a, b []byte, s string) bool { + t := string(a) + b[0] = 'z' + return t == s +} + +//go:noinline +func mixedByteStringCall(a, b []byte, s string) bool { + t := string(a) + mutateByteSlice(b) + return s < t +} + +//go:noinline +func mixedByteStringAcrossBlock(a, b []byte, s string, mutate bool) bool { + t := string(a) + if mutate { + b[0] = 'z' + } + return t == s +} + +//go:noinline +func mixedByteStringAfter(a []byte, s string) bool { + result := string(a) == s + a[0] = 'z' + return result +} + +var mixedEvaluationOrder int + +//go:noinline +func mixedBytesOperand(a []byte) []byte { + mixedEvaluationOrder = mixedEvaluationOrder*10 + 1 + return a +} + +//go:noinline +func mixedStringOperand(s string) string { + mixedEvaluationOrder = mixedEvaluationOrder*10 + 2 + return s +} + +//go:noinline +func mixedByteStringEvaluation(a []byte, s string) bool { + return string(mixedBytesOperand(a)) == mixedStringOperand(s) +} + +//go:noinline +func mixedStringByteEvaluation(s string, a []byte) bool { + return mixedStringOperand(s) == string(mixedBytesOperand(a)) +} + +func testMixedByteStringComparisons() { + cases := append(byteStringOrderCases, struct{ a, b []byte }{ + make([]byte, 0, 1), []byte{'x'}[:0], + }) + for _, tc := range cases { + s, t := string(tc.a), string(tc.b) + if mixedByteString(tc.a, t) != mixedStringReference(s, t) || + mixedStringByte(t, tc.a) != mixedStringReference(t, s) { + panic("mixed string comparison") + } + if mixedByteLiteral(tc.a) != mixedStringReference(s, "abc") || + mixedLiteralByte(tc.a) != mixedStringReference("abc", s) { + panic("mixed literal comparison") + } + var named myBytes + for _, c := range tc.a { + named = append(named, myByte(c)) + } + if mixedNamedByteString(named, myString(t)) != mixedStringReference(s, t) || + mixedNamedStringByte(myString(t), named) != mixedStringReference(t, s) { + panic("mixed named string comparison") + } + } + + a := []byte("abc") + equal, less := mixedByteStringReuse(a, "abd") + if equal || !less { + panic("mixed reused snapshot") + } + a[0] = 'a' + equal, snapshot := mixedByteStringEscape(a, "abc") + a[0] = 'z' + if !equal || snapshot != "abc" { + panic("mixed escaping snapshot") + } + a[0] = 'a' + equal = mixedByteStringStore(a, "abc", &snapshot) + a[0] = 'z' + if !equal || snapshot != "abc" { + panic("mixed stored snapshot") + } + a[0] = 'a' + equal, boxed := mixedByteStringBox(a, "abc") + a[0] = 'z' + if !equal || boxed.(string) != "abc" { + panic("mixed boxed snapshot") + } + a[0] = 'a' + if !mixedByteStringMutation(a, a, "abc") { + panic("mixed aliased mutation") + } + a[0] = 'a' + if mixedByteStringCall(a, a, "mbc") { + panic("mixed aliased call") + } + for _, mutate := range []bool{false, true} { + a[0] = 'a' + if !mixedByteStringAcrossBlock(a, a, "abc", mutate) { + panic("mixed cross block snapshot") + } + } + a[0] = 'a' + if !mixedByteStringAfter(a, "abc") || a[0] != 'z' { + panic("mixed mutation after comparison") + } + a[0] = 'a' + mixedEvaluationOrder = 0 + if !mixedByteStringEvaluation(a, "abc") || mixedEvaluationOrder != 12 { + panic("mixed left evaluation order") + } + mixedEvaluationOrder = 0 + if !mixedStringByteEvaluation("abc", a) || mixedEvaluationOrder != 21 { + panic("mixed right evaluation order") + } + println("mixed comparisons:", len(cases)*6*6, "snapshot and evaluation guards passed") +} + func main() { testRangeString() testStringToRunes() @@ -381,5 +581,6 @@ func main() { mutateGreaterByteString, namedGreaterByteStrings) testByteSliceStringOrder(">=", greaterEqualByteStrings, escapeGreaterEqualByteString, mutateGreaterEqualByteString, namedGreaterEqualByteStrings) + testMixedByteStringComparisons() var _ = len([]byte(myString("foobar"))) // issue 1246 } diff --git a/testdata/string.txt b/testdata/string.txt index 0139b9b8bb..972a7cc3d7 100644 --- a/testdata/string.txt +++ b/testdata/string.txt @@ -154,3 +154,4 @@ named order: >= false named order: >= true named order: >= false named order: >= true +mixed comparisons: 612 snapshot and evaluation guards passed diff --git a/tests/stringcompare/allocs_test.go b/tests/stringcompare/allocs_test.go new file mode 100644 index 0000000000..edd3afe737 --- /dev/null +++ b/tests/stringcompare/allocs_test.go @@ -0,0 +1,71 @@ +//go:build tinygo && gc.conservative + +package stringcompare + +import ( + "runtime" + "strconv" + "testing" +) + +func checkAllocs(t *testing.T, compare func([]byte, string) bool, n int, wantAllocs uint64) { + t.Helper() + a := make([]byte, n) + for i := range a { + a[i] = 'a' + } + s := string(a) + const runs = 100 + wantMatches := 0 + for i := 0; i < 2; i++ { + if n > 0 { + a[0] = byte('a' + i) + } + if compare(a, s) { + wantMatches += runs / 2 + } + } + runtime.GC() + var before, after runtime.MemStats + runtime.ReadMemStats(&before) + matches := 0 + for i := 0; i < runs; i++ { + if n > 0 { + a[0] = byte('a' + i&1) + } + if compare(a, s) { + matches++ + } + } + runtime.ReadMemStats(&after) + if matches != wantMatches { + t.Errorf("got %d matches, want %d", matches, wantMatches) + } + if got := after.Mallocs - before.Mallocs; got != runs*wantAllocs { + t.Errorf("got %d allocations, want %d", got, runs*wantAllocs) + } + if got, want := after.TotalAlloc-before.TotalAlloc, runs*wantAllocs*uint64(n); got != want { + t.Errorf("got %d allocated bytes, want %d", got, want) + } +} + +func TestComparisonAllocs(t *testing.T) { + for _, tc := range comparisons { + for _, n := range []int{0, 3, 64, 4096} { + t.Run(tc.name+"/"+strconv.Itoa(n), func(t *testing.T) { + checkAllocs(t, tc.compare, n, 0) + }) + } + } + for _, tc := range []struct { + name string + compare func([]byte, string) bool + }{ + {"reused", reused}, + {"afterCall", afterCall}, + } { + t.Run(tc.name, func(t *testing.T) { + checkAllocs(t, tc.compare, 64, 1) + }) + } +} diff --git a/tests/stringcompare/string_test.go b/tests/stringcompare/string_test.go new file mode 100644 index 0000000000..7d1ab99a36 --- /dev/null +++ b/tests/stringcompare/string_test.go @@ -0,0 +1,271 @@ +package stringcompare + +import ( + "strconv" + "testing" +) + +//go:noinline +func equal(a []byte, s string) bool { return string(a) == s } + +//go:noinline +func notEqual(a []byte, s string) bool { return string(a) != s } + +//go:noinline +func less(a []byte, s string) bool { return string(a) < s } + +//go:noinline +func lessEqual(a []byte, s string) bool { return string(a) <= s } + +//go:noinline +func greater(a []byte, s string) bool { return string(a) > s } + +//go:noinline +func greaterEqual(a []byte, s string) bool { return string(a) >= s } + +//go:noinline +func reverseEqual(a []byte, s string) bool { return s == string(a) } + +//go:noinline +func reverseNotEqual(a []byte, s string) bool { return s != string(a) } + +//go:noinline +func reverseLess(a []byte, s string) bool { return s < string(a) } + +//go:noinline +func reverseLessEqual(a []byte, s string) bool { return s <= string(a) } + +//go:noinline +func reverseGreater(a []byte, s string) bool { return s > string(a) } + +//go:noinline +func reverseGreaterEqual(a []byte, s string) bool { return s >= string(a) } + +var comparisons = []struct { + name string + compare func([]byte, string) bool +}{ + {"equal", equal}, + {"notEqual", notEqual}, + {"less", less}, + {"lessEqual", lessEqual}, + {"greater", greater}, + {"greaterEqual", greaterEqual}, + {"reverseEqual", reverseEqual}, + {"reverseNotEqual", reverseNotEqual}, + {"reverseLess", reverseLess}, + {"reverseLessEqual", reverseLessEqual}, + {"reverseGreater", reverseGreater}, + {"reverseGreaterEqual", reverseGreaterEqual}, +} + +func byteOrder(a, b []byte) int { + for i := 0; i < len(a) && i < len(b); i++ { + if a[i] < b[i] { + return -1 + } + if a[i] > b[i] { + return 1 + } + } + if len(a) < len(b) { + return -1 + } + if len(a) > len(b) { + return 1 + } + return 0 +} + +func TestBoundaries(t *testing.T) { + for _, n := range []int{0, 1, 2, 3, 7, 8, 15, 16, 31, 32, 33, 63, 64, 65, 127, 128, 129, 255, 256, 257, 4095, 4096} { + backing := make([]byte, n+8) + other := make([]byte, n+1) + for offset := 0; offset < 8; offset++ { + a := backing[offset : offset+n] + for i := range a { + a[i] = byte(i*97 + offset*19) + } + for _, mismatch := range []string{"equal", "first", "last", "shorter", "longer"} { + b := other[:n] + copy(b, a) + switch mismatch { + case "first": + if n > 0 { + b[0] ^= 0x80 + } + case "last": + if n > 0 { + b[n-1] ^= 0x80 + } + case "shorter": + if n > 0 { + b = b[:n-1] + } + case "longer": + b = other[:n+1] + b[n] = 0 + } + order := byteOrder(a, b) + want := [12]bool{ + order == 0, order != 0, order < 0, order <= 0, order > 0, order >= 0, + order == 0, order != 0, order > 0, order >= 0, order < 0, order <= 0, + } + s := string(b) + for i, tc := range comparisons { + if got := tc.compare(a, s); got != want[i] { + t.Fatalf("length %d offset %d %s %s: got %v, want %v", n, offset, mismatch, tc.name, got, want[i]) + } + } + } + } + } +} + +//go:noinline +func loadRight(a []byte, s *string) bool { return string(a) == *s } + +//go:noinline +func loadLeft(a []byte, s *string) bool { return *s == string(a) } + +//go:noinline +func sliceRight(a []byte, s string, n int) bool { return string(a) == s[:n] } + +//go:noinline +func sliceLeft(a []byte, s string, n int) bool { return s[:n] == string(a) } + +func mustPanic(t *testing.T, f func()) { + t.Helper() + defer func() { + if recover() == nil { + t.Error("expected a panic") + } + }() + f() +} + +func TestFallbacks(t *testing.T) { + a := []byte("abc") + s := "abc" + if !loadRight(a, &s) || !loadLeft(a, &s) || + !sliceRight(a, s, 3) || !sliceLeft(a, s, 3) { + t.Fatal("load or slice comparison failed") + } + for _, tc := range []struct { + name string + call func() + }{ + {"nilRight", func() { loadRight(a, nil) }}, + {"nilLeft", func() { loadLeft(a, nil) }}, + {"boundsRight", func() { sliceRight(a, s, 4) }}, + {"boundsLeft", func() { sliceLeft(a, s, 4) }}, + {"negativeRight", func() { sliceRight(a, s, -1) }}, + {"negativeLeft", func() { sliceLeft(a, s, -1) }}, + } { + t.Run(tc.name, func(t *testing.T) { + mustPanic(t, tc.call) + }) + } +} + +//go:noinline +func phiSnapshot(a []byte, alternate string, choose bool) bool { + s := string(a) + if choose { + s = alternate + } + a[0] = 'z' + return s == "abc" +} + +func TestPhiSnapshot(t *testing.T) { + a := []byte("abc") + if !phiSnapshot(a, "xyz", false) || a[0] != 'z' { + t.Fatal("phi did not preserve the converted string") + } + a[0] = 'a' + if phiSnapshot(a, "xyz", true) || a[0] != 'z' { + t.Fatal("phi did not select the alternate string") + } +} + +//go:noinline +func reused(a []byte, s string) bool { + t := string(a) + return t == s || t < s +} + +//go:noinline +func mutate(a []byte) { a[0] = 'z' } + +//go:noinline +func afterCall(a []byte, s string) bool { + t := string(a) + mutate(a) + return t == s +} + +func TestCallSnapshot(t *testing.T) { + a := []byte("abc") + if !afterCall(a, "abc") || a[0] != 'z' { + t.Fatal("call did not preserve the converted string") + } +} + +func BenchmarkComparison(b *testing.B) { + for _, tc := range []struct { + name string + compare func([]byte, string) bool + }{ + {"equal", equal}, + {"less", less}, + {"reused", reused}, + {"afterCall", afterCall}, + } { + for _, n := range []int{3, 64, 4096} { + for _, mismatch := range []string{"equal", "first", "last", "length"} { + b.Run(tc.name+"/"+strconv.Itoa(n)+"/"+mismatch, func(b *testing.B) { + a := make([]byte, n) + other := make([]byte, n) + for i := range a { + a[i], other[i] = 'a', 'a' + } + var strings [2]string + for i := range strings { + other[0] = byte('a' + i) + switch mismatch { + case "first": + other[0] = 'z' + case "last": + other[n-1] = 'z' + } + strings[i] = string(other) + if mismatch == "length" { + strings[i] += "x" + } + } + want := 0 + for i, s := range strings { + a[0] = byte('a' + i) + if tc.compare(a, s) { + want += (b.N + 1 - i) / 2 + } + } + b.ReportAllocs() + b.ResetTimer() + matches := 0 + for i := 0; i < b.N; i++ { + a[0] = byte('a' + i&1) + if tc.compare(a, strings[i&1]) { + matches++ + } + } + b.StopTimer() + if matches != want { + b.Fatalf("got %d matches, want %d", matches, want) + } + }) + } + } + } +}