compiler: handle nested unsigned shift being untyped after type resolution (#5497)

* apply compiler patch for fix of #5496

* add compiler/testdata smoketest

* make diff error more explicit on difference

* apply golden fix
This commit is contained in:
Pat Whittingslow
2026-07-22 02:35:18 -03:00
committed by GitHub
parent 3ad913acb8
commit b536dd6f79
4 changed files with 66 additions and 28 deletions
+4 -4
View File
@@ -422,19 +422,19 @@ func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type {
return c.ctx.Int8Type() return c.ctx.Int8Type()
case types.Int16, types.Uint16: case types.Int16, types.Uint16:
return c.ctx.Int16Type() return c.ctx.Int16Type()
case types.Int32, types.Uint32: case types.Int32, types.Uint32, types.UntypedRune:
return c.ctx.Int32Type() return c.ctx.Int32Type()
case types.Int, types.Uint: case types.Int, types.Uint, types.UntypedInt:
return c.intType return c.intType
case types.Int64, types.Uint64: case types.Int64, types.Uint64:
return c.ctx.Int64Type() return c.ctx.Int64Type()
case types.Float32: case types.Float32:
return c.ctx.FloatType() return c.ctx.FloatType()
case types.Float64: case types.Float64, types.UntypedFloat:
return c.ctx.DoubleType() return c.ctx.DoubleType()
case types.Complex64: case types.Complex64:
return c.ctx.StructType([]llvm.Type{c.ctx.FloatType(), c.ctx.FloatType()}, false) return c.ctx.StructType([]llvm.Type{c.ctx.FloatType(), c.ctx.FloatType()}, false)
case types.Complex128: case types.Complex128, types.UntypedComplex:
return c.ctx.StructType([]llvm.Type{c.ctx.DoubleType(), c.ctx.DoubleType()}, false) return c.ctx.StructType([]llvm.Type{c.ctx.DoubleType(), c.ctx.DoubleType()}, false)
case types.String, types.UntypedString: case types.String, types.UntypedString:
return c.getLLVMRuntimeType("_string") return c.getLLVMRuntimeType("_string")
+50 -24
View File
@@ -123,53 +123,79 @@ func TestCompiler(t *testing.T) {
t.Fatal("failed to read golden file:", err) t.Fatal("failed to read golden file:", err)
} }
if !fuzzyEqualIR(mod.String(), string(expected)) { if diff := diffIR(string(expected), mod.String()); diff != "" {
t.Errorf("output does not match expected output:\n%s", mod.String()) t.Errorf("output does not match expected output (re-run with -update to regenerate):\n%s", diff)
} }
}) })
} }
} }
// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly // normalizeIR canonicalizes LLVM IR so a single golden file keeps matching
// equal. That means, only relevant lines are compared (excluding comments // across LLVM versions. Golden files are written against LLVM <21; newer LLVM
// etc.). // prints some attributes differently.
func fuzzyEqualIR(s1, s2 string) bool { func normalizeIR(s string) string {
// Golden files are written using the pre-LLVM21 'nocapture' spelling, // Golden files are written using the pre-LLVM21 'nocapture' spelling,
// which LLVM printed before any co-occurring attribute such as // which LLVM printed before any co-occurring attribute such as
// 'readonly' (e.g. "ptr nocapture readonly"). LLVM 21+ prints the // 'readonly' (e.g. "ptr nocapture readonly"). LLVM 21+ prints the
// equivalent 'captures(none)' instead, and after such attributes (e.g. // equivalent 'captures(none)' instead, and after such attributes (e.g.
// "ptr readonly captures(none)"). Normalize both name and position back // "ptr readonly captures(none)"). Normalize both name and position back
// to the old spelling to keep a single golden file working across LLVM // to the old spelling.
// versions. s = normalizeCapturesAttr(s)
s1 = normalizeCapturesAttr(s1)
s2 = normalizeCapturesAttr(s2)
// LLVM 21+ also added an explicit 'nocreateundeforpoison' attribute to // LLVM 21+ also added an explicit 'nocreateundeforpoison' attribute to
// certain intrinsic declarations (e.g. llvm.umin) that were implicitly // certain intrinsic declarations (e.g. llvm.umin) that were implicitly
// assumed not to create undef/poison before. It's unrelated to the // assumed not to create undef/poison before. It's unrelated to the
// behavior under test, so ignore it for comparison. // behavior under test, so ignore it for comparison.
s1 = strings.ReplaceAll(s1, "nocreateundeforpoison ", "") s = strings.ReplaceAll(s, "nocreateundeforpoison ", "")
s2 = strings.ReplaceAll(s2, "nocreateundeforpoison ", "")
// LLVM 22 dropped the (redundant) i64 size argument from // LLVM 22 dropped the (redundant) i64 size argument from
// llvm.lifetime.start/end. Normalize away that argument so golden files // llvm.lifetime.start/end. Normalize away that argument so golden files
// written against the two-argument form still match. // written against the two-argument form still match.
s1 = lifetimeSizeArgRe.ReplaceAllString(s1, "$1") s = lifetimeSizeArgRe.ReplaceAllString(s, "$1")
s2 = lifetimeSizeArgRe.ReplaceAllString(s2, "$1")
lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n")) return s
lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n")) }
if len(lines1) != len(lines2) {
return false // diffIR compares two LLVM IR strings, ignoring irrelevant lines (comments,
// empty lines, etc.) and normalizing LLVM-version-specific spellings via
// normalizeIR. It returns "" when they are equal. Otherwise it returns a
// compact diff of only the region that differs: the common prefix and suffix
// are trimmed, then the differing expected lines (prefixed "-") are shown
// followed by the differing actual lines (prefixed "+").
func diffIR(expected, actual string) string {
exp := filterIrrelevantIRLines(strings.Split(normalizeIR(expected), "\n"))
act := filterIrrelevantIRLines(strings.Split(normalizeIR(actual), "\n"))
// Trim the common prefix.
start := 0
for start < len(exp) && start < len(act) && exp[start] == act[start] {
start++
} }
for i, line1 := range lines1 { // Trim the common suffix.
line2 := lines2[i] e, a := len(exp), len(act)
if line1 != line2 { for e > start && a > start && exp[e-1] == act[a-1] {
return false e--
} a--
}
if start == e && start == a {
return "" // equal
} }
return true var b strings.Builder
b.WriteString("first difference at relevant line ")
b.WriteString(strconv.Itoa(start + 1))
b.WriteString(":\n")
for _, line := range exp[start:e] {
b.WriteString("- ")
b.WriteString(line)
b.WriteByte('\n')
}
for _, line := range act[start:a] {
b.WriteString("+ ")
b.WriteString(line)
b.WriteByte('\n')
}
return b.String()
} }
// capturesNoneAttrRe matches a co-occurring attribute directly followed by // capturesNoneAttrRe matches a co-occurring attribute directly followed by
+5
View File
@@ -66,6 +66,11 @@ func complexSub(x, y complex64) complex64 {
return x - y return x - y
} }
func shiftNested(x uint64) uint64 {
k := 3
return x >> (1 << k) // https://github.com/tinygo-org/tinygo/issues/5496
}
func complexMul(x, y complex64) complex64 { func complexMul(x, y complex64) complex64 {
return x * y return x * y
} }
+7
View File
@@ -176,6 +176,13 @@ entry:
ret { float, float } %3 ret { float, float } %3
} }
; Function Attrs: nounwind
define hidden i64 @main.shiftNested(i64 %x, ptr %context) unnamed_addr #1 {
entry:
%0 = lshr i64 %x, 8
ret i64 %0
}
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, ptr %context) unnamed_addr #1 { define hidden { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, ptr %context) unnamed_addr #1 {
entry: entry: