diff --git a/interp/compiler.go b/interp/compiler.go index e909df80b..f4c8fd23b 100644 --- a/interp/compiler.go +++ b/interp/compiler.go @@ -140,13 +140,25 @@ func (r *runner) compileFunction(llvmFn llvm.Value) *function { panic("unknown number of operands") } case llvm.Switch: - // A switch is an array of (value, label) pairs, of which the + // Compile to an array of (value, label) pairs, of which the // first one indicates the to-switch value and the default // label. - numOperands := llvmInst.OperandsCount() - for i := 0; i < numOperands; i += 2 { - inst.operands = append(inst.operands, r.getValue(llvmInst.Operand(i))) - inst.operands = append(inst.operands, literalValue{uint32(blockIndices[llvmInst.Operand(i+1)])}) + // + // Successor 0 is always the default destination; successors + // 1..N-1 are the individual cases. This must be read via + // GetSwitchCaseValue/Successor rather than raw operands, + // because LLVM 22 stopped exposing switch case values as + // regular instruction operands (only the condition and + // destination-block operands remain). + inst.operands = append(inst.operands, + r.getValue(llvmInst.Operand(0)), + literalValue{uint32(blockIndices[llvmInst.Successor(0).AsValue()])}, + ) + for i := 1; i < llvmInst.SuccessorsCount(); i++ { + inst.operands = append(inst.operands, + r.getValue(llvmInst.GetSwitchCaseValue(i)), + literalValue{uint32(blockIndices[llvmInst.Successor(i).AsValue()])}, + ) } case llvm.PHI: inst.name = llvmInst.Name() diff --git a/interp/interp_test.go b/interp/interp_test.go index bfb1ae2be..b1223e081 100644 --- a/interp/interp_test.go +++ b/interp/interp_test.go @@ -2,6 +2,7 @@ package interp import ( "os" + "regexp" "strings" "testing" "time" @@ -92,6 +93,16 @@ func runTest(t *testing.T, pathPrefix string) { // 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) + lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n")) lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n")) if len(lines1) != len(lines2) { @@ -107,6 +118,21 @@ func fuzzyEqualIR(s1, s2 string) bool { return true } +// 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 +// around). +var capturesNoneAttrRe = regexp.MustCompile(`\b(readonly|readnone|writeonly|nonnull)\s+captures\(none\)`) + +// normalizeCapturesAttr rewrites LLVM 21+'s 'captures(none)' attribute back +// to the pre-LLVM21 'nocapture' spelling and position, so golden IR files +// written against LLVM <21 keep matching. +func normalizeCapturesAttr(s string) string { + s = capturesNoneAttrRe.ReplaceAllString(s, "nocapture $1") + s = strings.ReplaceAll(s, "captures(none)", "nocapture") + return s +} + // filterIrrelevantIRLines removes lines from the input slice of strings that // are not relevant in comparing IR. For example, empty lines and comments are // stripped out.