diff --git a/compiler/compiler.go b/compiler/compiler.go index 8cbc1a919..f51a9871c 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -422,19 +422,19 @@ func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type { return c.ctx.Int8Type() case types.Int16, types.Uint16: return c.ctx.Int16Type() - case types.Int32, types.Uint32: + case types.Int32, types.Uint32, types.UntypedRune: return c.ctx.Int32Type() - case types.Int, types.Uint: + case types.Int, types.Uint, types.UntypedInt: return c.intType case types.Int64, types.Uint64: return c.ctx.Int64Type() case types.Float32: return c.ctx.FloatType() - case types.Float64: + case types.Float64, types.UntypedFloat: return c.ctx.DoubleType() case types.Complex64: 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) case types.String, types.UntypedString: return c.getLLVMRuntimeType("_string") diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index e5d781849..a11d4f11d 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -123,53 +123,79 @@ func TestCompiler(t *testing.T) { t.Fatal("failed to read golden file:", err) } - if !fuzzyEqualIR(mod.String(), string(expected)) { - t.Errorf("output does not match expected output:\n%s", mod.String()) + if diff := diffIR(string(expected), mod.String()); diff != "" { + 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 -// equal. That means, only relevant lines are compared (excluding comments -// etc.). -func fuzzyEqualIR(s1, s2 string) bool { +// 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. +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 to keep a single golden file working across LLVM - // versions. - s1 = normalizeCapturesAttr(s1) - s2 = normalizeCapturesAttr(s2) + // 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 // behavior under test, so ignore it for comparison. - s1 = strings.ReplaceAll(s1, "nocreateundeforpoison ", "") - s2 = strings.ReplaceAll(s2, "nocreateundeforpoison ", "") + 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. - s1 = lifetimeSizeArgRe.ReplaceAllString(s1, "$1") - s2 = lifetimeSizeArgRe.ReplaceAllString(s2, "$1") + s = lifetimeSizeArgRe.ReplaceAllString(s, "$1") - lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n")) - lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n")) - if len(lines1) != len(lines2) { - return false + return s +} + +// 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 { - line2 := lines2[i] - if line1 != line2 { - return false - } + // Trim the common suffix. + e, a := len(exp), len(act) + for e > start && a > start && exp[e-1] == act[a-1] { + 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 diff --git a/compiler/testdata/basic.go b/compiler/testdata/basic.go index 6c6f983e3..16d8c6ce8 100644 --- a/compiler/testdata/basic.go +++ b/compiler/testdata/basic.go @@ -66,6 +66,11 @@ func complexSub(x, y complex64) complex64 { 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 { return x * y } diff --git a/compiler/testdata/basic.ll b/compiler/testdata/basic.ll index f593d6b06..0eae6bbf2 100644 --- a/compiler/testdata/basic.ll +++ b/compiler/testdata/basic.ll @@ -176,6 +176,13 @@ entry: 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 define hidden { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, ptr %context) unnamed_addr #1 { entry: