diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 462a9bb8d..99de5bc62 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -115,7 +115,7 @@ func TestCompiler(t *testing.T) { // Update test if needed. Do not check the result. if *flagUpdate { - err := os.WriteFile(outPath, []byte(mod.String()), 0666) + err := os.WriteFile(outPath, []byte(normalizeIR(mod.String())), 0666) if err != nil { t.Error("failed to write updated output file:", err) } @@ -174,9 +174,8 @@ func TestOptimizedLargeAggregateABI(t *testing.T) { } } -// normalizeIR canonicalizes LLVM IR so a single golden file keeps matching -// across LLVM versions. Golden files are written against LLVM <21; newer LLVM -// prints some attributes differently. +// normalizeIR canonicalizes LLVM-version-specific IR spellings for comparison +// and when regenerating golden files. func normalizeIR(s string) string { // Golden files are written using the pre-LLVM21 'nocapture' spelling, // which LLVM printed before any co-occurring attribute such as diff --git a/compiler/testdata/channel.ll b/compiler/testdata/channel.ll index bd91a5773..31466ead0 100644 --- a/compiler/testdata/channel.ll +++ b/compiler/testdata/channel.ll @@ -19,33 +19,33 @@ define hidden void @main.chanIntSend(ptr dereferenceable_or_null(36) %ch, ptr %c entry: %chan.op = alloca %runtime.channelOp, align 8 %chan.value = alloca i32, align 4 - call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value) + call void @llvm.lifetime.start.p0(ptr nonnull %chan.value) store i32 3, ptr %chan.value, align 4 - call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) + call void @llvm.lifetime.start.p0(ptr nonnull %chan.op) call void @runtime.chanSend(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #3 - call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) - call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value) + call void @llvm.lifetime.end.p0(ptr nonnull %chan.op) + call void @llvm.lifetime.end.p0(ptr nonnull %chan.value) ret void } ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) -declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #2 +declare void @llvm.lifetime.start.p0(ptr nocapture) #2 declare void @runtime.chanSend(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0 ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) -declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #2 +declare void @llvm.lifetime.end.p0(ptr nocapture) #2 ; Function Attrs: nounwind define hidden void @main.chanIntRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 { entry: %chan.op = alloca %runtime.channelOp, align 8 %chan.value = alloca i32, align 4 - call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value) - call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) + call void @llvm.lifetime.start.p0(ptr nonnull %chan.value) + call void @llvm.lifetime.start.p0(ptr nonnull %chan.op) %0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #3 - call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value) - call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) + call void @llvm.lifetime.end.p0(ptr nonnull %chan.value) + call void @llvm.lifetime.end.p0(ptr nonnull %chan.op) ret void } @@ -55,9 +55,9 @@ declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(36), ptr, ptr dereferen define hidden void @main.chanZeroSend(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 { entry: %chan.op = alloca %runtime.channelOp, align 8 - call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) + call void @llvm.lifetime.start.p0(ptr nonnull %chan.op) call void @runtime.chanSend(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #3 - call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) + call void @llvm.lifetime.end.p0(ptr nonnull %chan.op) ret void } @@ -65,9 +65,9 @@ entry: define hidden void @main.chanZeroRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 { entry: %chan.op = alloca %runtime.channelOp, align 8 - call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) + call void @llvm.lifetime.start.p0(ptr nonnull %chan.op) %0 = call i1 @runtime.chanRecv(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #3 - call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) + call void @llvm.lifetime.end.p0(ptr nonnull %chan.op) ret void } @@ -77,7 +77,7 @@ entry: %select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8 %select.send.value = alloca i32, align 4 store i32 1, ptr %select.send.value, align 4 - call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %select.states.alloca) + call void @llvm.lifetime.start.p0(ptr nonnull %select.states.alloca) store ptr %ch1, ptr %select.states.alloca, align 4 %select.states.alloca.repack1 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 4 store ptr %select.send.value, ptr %select.states.alloca.repack1, align 4 @@ -86,7 +86,7 @@ entry: %.repack3 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 12 store ptr null, ptr %.repack3, align 4 %select.result = call { i32, i1 } @runtime.chanSelect(ptr undef, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr null, i32 0, i32 0, ptr undef) #3 - call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %select.states.alloca) + call void @llvm.lifetime.end.p0(ptr nonnull %select.states.alloca) %1 = extractvalue { i32, i1 } %select.result, 0 %2 = icmp eq i32 %1, 0 br i1 %2, label %select.done, label %select.next diff --git a/compiler/testdata/large.ll b/compiler/testdata/large.ll index 27da84606..4be9d9e4f 100644 --- a/compiler/testdata/large.ll +++ b/compiler/testdata/large.ll @@ -15,7 +15,7 @@ target triple = "wasm32-unknown-wasi" @"reflect/types.type:basic:string" = linkonce_odr constant { i8, ptr } { i8 81, ptr @"reflect/types.type:pointer:basic:string" }, align 4 @"reflect/types.type:pointer:basic:string" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:string" }, align 4 -declare void @runtime.trackPointer(ptr readonly captures(none), ptr, ptr) #0 +declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #0 ; Function Attrs: nounwind define hidden void @main.init(ptr %context) unnamed_addr #1 { @@ -31,7 +31,7 @@ entry: } ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite) -declare void @llvm.memcpy.p0.p0.i32(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i32, i1 immarg) #2 +declare void @llvm.memcpy.p0.p0.i32(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i32, i1 immarg) #2 ; Function Attrs: nounwind define hidden i8 @"(main.largeReceiver).readLargeValue"(ptr readonly dereferenceable_or_null(1025) %receiver, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 { @@ -344,7 +344,7 @@ if.then: ; preds = %typeassert.next declare i1 @runtime.typeAssert(ptr, ptr dereferenceable_or_null(1), ptr) #0 ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: write) -declare void @llvm.memset.p0.i32(ptr writeonly captures(none), i8, i32, i1 immarg) #7 +declare void @llvm.memset.p0.i32(ptr nocapture writeonly, i8, i32, i1 immarg) #7 ; Function Attrs: nounwind define hidden i8 @main.useLargeMap(ptr readonly dereferenceable_or_null(1025) %key, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 { @@ -423,12 +423,12 @@ if.then: ; preds = %entry } ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) -declare void @llvm.lifetime.start.p0(ptr captures(none)) #8 +declare void @llvm.lifetime.start.p0(ptr nocapture) #8 declare void @runtime.chanSend(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0 ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) -declare void @llvm.lifetime.end.p0(ptr captures(none)) #8 +declare void @llvm.lifetime.end.p0(ptr nocapture) #8 declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0 diff --git a/compiler/testdata/zeromap.ll b/compiler/testdata/zeromap.ll index be8b924c2..cd7468409 100644 --- a/compiler/testdata/zeromap.ll +++ b/compiler/testdata/zeromap.ll @@ -21,23 +21,23 @@ entry: %0 = insertvalue %main.hasPadding zeroinitializer, i1 %s.b1, 0 %1 = insertvalue %main.hasPadding %0, i32 %s.i, 1 %2 = insertvalue %main.hasPadding %1, i1 %s.b2, 2 - call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) - call void @llvm.lifetime.start.p0(i64 12, ptr nonnull %hashmap.key) + call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.value) + call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.key) store %main.hasPadding %2, ptr %hashmap.key, align 4 %3 = call i1 @runtime.hashmapGenericGet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, i32 4, ptr undef) #4 - call void @llvm.lifetime.end.p0(i64 12, ptr nonnull %hashmap.key) + call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.key) %4 = load i32, ptr %hashmap.value, align 4 - call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value) + call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.value) ret i32 %4 } ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) -declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #3 +declare void @llvm.lifetime.start.p0(ptr nocapture) #3 declare i1 @runtime.hashmapGenericGet(ptr dereferenceable_or_null(48), ptr nocapture, ptr nocapture, i32, ptr) #0 ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) -declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #3 +declare void @llvm.lifetime.end.p0(ptr nocapture) #3 ; Function Attrs: noinline nounwind define hidden void @main.testZeroSet(ptr dereferenceable_or_null(48) %m, i1 %s.b1, i32 %s.i, i1 %s.b2, ptr %context) unnamed_addr #2 { @@ -47,13 +47,13 @@ entry: %0 = insertvalue %main.hasPadding zeroinitializer, i1 %s.b1, 0 %1 = insertvalue %main.hasPadding %0, i32 %s.i, 1 %2 = insertvalue %main.hasPadding %1, i1 %s.b2, 2 - call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) + call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.value) store i32 5, ptr %hashmap.value, align 4 - call void @llvm.lifetime.start.p0(i64 12, ptr nonnull %hashmap.key) + call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.key) store %main.hasPadding %2, ptr %hashmap.key, align 4 call void @runtime.hashmapGenericSet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, ptr undef) #4 - call void @llvm.lifetime.end.p0(i64 12, ptr nonnull %hashmap.key) - call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value) + call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.key) + call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.value) ret void } @@ -64,17 +64,17 @@ define hidden i32 @main.testZeroArrayGet(ptr dereferenceable_or_null(48) %m, [2 entry: %hashmap.key = alloca [2 x %main.hasPadding], align 8 %hashmap.value = alloca i32, align 4 - call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) - call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %hashmap.key) + call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.value) + call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.key) %s.elt = extractvalue [2 x %main.hasPadding] %s, 0 store %main.hasPadding %s.elt, ptr %hashmap.key, align 4 %hashmap.key.repack1 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 12 %s.elt2 = extractvalue [2 x %main.hasPadding] %s, 1 store %main.hasPadding %s.elt2, ptr %hashmap.key.repack1, align 4 %0 = call i1 @runtime.hashmapGenericGet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, i32 4, ptr undef) #4 - call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %hashmap.key) + call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.key) %1 = load i32, ptr %hashmap.value, align 4 - call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value) + call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.value) ret i32 %1 } @@ -83,17 +83,17 @@ define hidden void @main.testZeroArraySet(ptr dereferenceable_or_null(48) %m, [2 entry: %hashmap.key = alloca [2 x %main.hasPadding], align 8 %hashmap.value = alloca i32, align 4 - call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) + call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.value) store i32 5, ptr %hashmap.value, align 4 - call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %hashmap.key) + call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.key) %s.elt = extractvalue [2 x %main.hasPadding] %s, 0 store %main.hasPadding %s.elt, ptr %hashmap.key, align 4 %hashmap.key.repack1 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 12 %s.elt2 = extractvalue [2 x %main.hasPadding] %s, 1 store %main.hasPadding %s.elt2, ptr %hashmap.key.repack1, align 4 call void @runtime.hashmapGenericSet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, ptr undef) #4 - call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %hashmap.key) - call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value) + call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.key) + call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.value) ret void } diff --git a/transform/transform_test.go b/transform/transform_test.go index b79e17763..4af128387 100644 --- a/transform/transform_test.go +++ b/transform/transform_test.go @@ -76,7 +76,7 @@ func testTransform(t *testing.T, pathPrefix string, transform func(mod llvm.Modu actual = actual[strings.Index(actual, "\ntarget datalayout = ")+1:] if *update { - err := os.WriteFile(pathPrefix+".out.ll", []byte(actual), 0666) + err := os.WriteFile(pathPrefix+".out.ll", []byte(normalizeIR(actual)), 0666) if err != nil { t.Error("failed to write out new output:", err) } @@ -100,28 +100,8 @@ func testTransform(t *testing.T, pathPrefix string, transform func(mod llvm.Modu // equal. That means, only relevant lines are compared (excluding comments // etc.). func fuzzyEqualIR(s1, s2 string) bool { - // Golden files are written using the pre-LLVM21 'nocapture' spelling, - // which LLVM printed before any co-occurring attribute such as - // 'readonly' (e.g. "ptr nocapture readonly"). LLVM 21+ prints the - // equivalent 'captures(none)' instead, and after such attributes (e.g. - // "ptr readonly captures(none)"). Normalize both name and position back - // to the old spelling to keep a single golden file working across LLVM - // versions. - s1 = normalizeCapturesAttr(s1) - s2 = normalizeCapturesAttr(s2) - - // LLVM 21+ also added an explicit 'nocreateundeforpoison' attribute to - // certain intrinsic declarations (e.g. llvm.umin) that were implicitly - // assumed not to create undef/poison before. It's unrelated to the - // escape-analysis behavior under test, so ignore it for comparison. - s1 = strings.ReplaceAll(s1, "nocreateundeforpoison ", "") - s2 = strings.ReplaceAll(s2, "nocreateundeforpoison ", "") - - // LLVM 22 dropped the (redundant) i64 size argument from - // llvm.lifetime.start/end. Normalize away that argument so golden files - // written against the two-argument form still match. - s1 = lifetimeSizeArgRe.ReplaceAllString(s1, "$1") - s2 = lifetimeSizeArgRe.ReplaceAllString(s2, "$1") + s1 = normalizeIR(s1) + s2 = normalizeIR(s2) lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n")) lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n")) @@ -138,6 +118,29 @@ func fuzzyEqualIR(s1, s2 string) bool { return true } +func normalizeIR(s string) string { + // Golden files are written using the pre-LLVM21 'nocapture' spelling, + // which LLVM printed before any co-occurring attribute such as + // 'readonly' (e.g. "ptr nocapture readonly"). LLVM 21+ prints the + // equivalent 'captures(none)' instead, and after such attributes (e.g. + // "ptr readonly captures(none)"). Normalize both name and position back + // to the old spelling. + s = normalizeCapturesAttr(s) + + // LLVM 21+ also added an explicit 'nocreateundeforpoison' attribute to + // certain intrinsic declarations (e.g. llvm.umin) that were implicitly + // assumed not to create undef/poison before. It's unrelated to the + // escape-analysis behavior under test, so ignore it for comparison. + s = strings.ReplaceAll(s, "nocreateundeforpoison ", "") + + // LLVM 22 dropped the (redundant) i64 size argument from + // llvm.lifetime.start/end. Normalize away that argument so golden files + // written against the two-argument form still match. + s = lifetimeSizeArgRe.ReplaceAllString(s, "$1") + + return s +} + // capturesNoneAttrRe matches a co-occurring attribute directly followed by // 'captures(none)', which is how LLVM 21+ orders these two attributes when // printing IR (the pre-LLVM21 'nocapture' attribute printed the other way