Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions docs/architecture/infrastructure.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,9 @@ adding a generic cache or transplanting these artifacts by stable ID alone.

`diagnostics.Diagnostic` carries severity, message, stable code, file path, ordered
labels, and extras. A primary label is the diagnostic origin; secondary labels carry
context such as a previous declaration or recursive generic application. Code hints,
insertions, removals, replacements, notes, and help are extras.
context such as a previous declaration or recursive generic application. Notes and
help lines are extras; each can carry fixes (`Fix.Remove`, `Fix.Insert`, `Fix.Replace`)
whose fixed source line is shown beneath it.

`DiagnosticBag` stores `groups[producingPhase][moduleScope]`. `BeginPhase` replaces
one phase/module group and returns a scoped writer. `AppendPhase` appends to an
Expand Down Expand Up @@ -348,7 +349,8 @@ back to bounded file scanning.
`diagnostics.SourceCache` stores split source lines under an RW mutex. The bag accepts
in-memory source through `AddSourceContent`, allowing diagnostics for source
overrides. The emitter supports ANSI/HTML strings and stderr output, source
snippets, labels, code hints, tab expansion, and optional syntax highlighting.
snippets, labels, help and note lines with the fixed source line beneath them,
tab expansion, and optional syntax highlighting.
`SyntaxHighlighter` performs lightweight line tokenization; `suggest.go` supplies
Levenshtein-based, ambiguity-aware nearest-name suggestions with stable priorities.

Expand Down
109 changes: 109 additions & 0 deletions internal/backend/llvm/shift_guard_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package llvm

import (
"fmt"
"strings"
"testing"

"compiler/internal/diagnostics"
"compiler/internal/ir"
"compiler/internal/ir/mir"
)

func TestGenerateLLVMIRShiftGuardWidths(t *testing.T) {
for _, target := range []struct {
name string
bits int
}{
{name: "amd64", bits: 64},
{name: "386", bits: 32},
} {
for _, tt := range []struct {
name string
leftBits int
countBits int
leftSigned bool
countSigned bool
}{
{name: "narrow unsigned", leftBits: 32, countBits: 5},
{name: "narrow signed", leftBits: 32, countBits: 5, countSigned: true},
{name: "signed operand", leftBits: 32, countBits: 5, leftSigned: true},
{name: "one bit", leftBits: 1, countBits: 1},
{name: "two bits signed count", leftBits: 2, countBits: 1, countSigned: true},
{name: "odd width", leftBits: 3, countBits: 2},
{name: "wide operand", leftBits: 128, countBits: 5},
{name: "wide unsigned count", leftBits: 8, countBits: 64},
{name: "wide signed count", leftBits: 8, countBits: 64, countSigned: true},
{name: "equal width signed count", leftBits: 32, countBits: 32, countSigned: true},
} {
for _, op := range []string{"<<", ">>"} {
t.Run(target.name+"/"+tt.name+"/"+op, func(t *testing.T) {
types := newLLVMTypeFixture(target.bits)
leftType := types.table.Intern(ir.Type{Kind: ir.TypeInteger, Bits: tt.leftBits, IsSigned: tt.leftSigned})
countType := types.table.Intern(ir.Type{Kind: ir.TypeInteger, Bits: tt.countBits, IsSigned: tt.countSigned})
mod := &mir.Module{
Name: "test", Types: types.table,
Funcs: []*mir.Function{{
Name: "apply",
Params: []ir.Param{{Name: "left", Type: leftType}, {Name: "right", Type: countType}},
ReturnType: leftType,
Blocks: []*mir.Block{{
ID: 0,
Instrs: []mir.Instr{&mir.Assign{Name: "result", Value: &mir.Binary{
Op: op, Left: &mir.RefName{Name: "left", Type: leftType}, Right: &mir.RefName{Name: "right", Type: countType}, Type: leftType,
}}},
Term: &mir.Ret{Value: &mir.RefName{Name: "result", Type: leftType}},
}},
}},
}
targetInfo := testLinuxAMD64
if target.bits == 32 {
targetInfo = testLinux386
}
diag := diagnostics.NewDiagnosticBag()
out := GenerateLLVMIR(mod, diag, targetInfo, false)
if diag.HasErrors() || out == "" {
t.Fatalf("shift lowering failed:\n%s", out)
}

count := "%right"
if tt.countBits < tt.leftBits {
extension := "zext"
if tt.countSigned {
extension = "sext"
}
cast := fmt.Sprintf(" = %s i%d %%right to i%d", extension, tt.countBits, tt.leftBits)
if strings.Count(out, cast) != 1 {
t.Fatalf("expected one lossless count extension %q:\n%s", cast, out)
}
before, _, _ := strings.Cut(out, cast)
count = strings.TrimSpace(before[strings.LastIndex(before, "\n")+1:])
}
guardText := fmt.Sprintf("icmp uge i%d %s, %d", max(tt.leftBits, tt.countBits), count, tt.leftBits)
guard := strings.Index(out, guardText)
trap := strings.Index(out, "call void @llvm.trap()")
ready := strings.Index(out, "\nshift_ready_")
opcode := "shl"
if op == ">>" {
opcode = "lshr"
if tt.leftSigned {
opcode = "ashr"
}
}
shift := strings.Index(out, fmt.Sprintf(" = %s i%d %%left,", opcode, tt.leftBits))
if guard < 0 || trap < guard || ready < trap || shift < ready {
t.Fatalf("expected guard %q before trap/ready/shift:\n%s", guardText, out)
}
if tt.countBits > tt.leftBits {
cast := strings.Index(out, fmt.Sprintf("trunc i%d %%right to i%d", tt.countBits, tt.leftBits))
if cast < ready || shift < cast {
t.Fatalf("wide count must only truncate after guard succeeds:\n%s", out)
}
} else if !strings.Contains(out, fmt.Sprintf(" = %s i%d %%left, %s", opcode, tt.leftBits, count)) {
t.Fatalf("shift must reuse validated count:\n%s", out)
}
})
}
}
}
}
4 changes: 2 additions & 2 deletions internal/diagnostics/bag.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import (
)

const (
compileFailedMsg = "\nCompilation failed with %d error(s)"
compileFailedMsg = "Compilation failed with %d error(s)"
andWarningMsg = " and %d warning(s)"
compileSuccessWithWarning = "\nCompilation succeeded with %d warning(s)\n"
compileSuccessWithWarning = "Compilation succeeded with %d warning(s)\n"
)

// DiagnosticBag collects diagnostics during compilation
Expand Down
146 changes: 62 additions & 84 deletions internal/diagnostics/diagnostic.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,53 @@ type DiagnosticExtraKind int

const (
ExtraText DiagnosticExtraKind = iota
ExtraCodeHint
)

type DiagnosticText struct {
Kind string
Message string
Color colors.COLOR
// Fixes are the source edits this text proposes, shown beneath it.
Fixes []CodeFix
}

// CodeFix replaces the source at Location with NewText. An empty Location
// inserts; an empty NewText removes. Build one with Fix.Remove, Fix.Insert or
// Fix.Replace.
type CodeFix struct {
Location *source.Location
NewText string
}

// Fix groups the ways to build a CodeFix, so a call site reads as the edit it
// proposes: Fix.Remove(where), Fix.Insert(where, text), Fix.Replace(where, text).
var Fix fixBuilder

type fixBuilder struct{}

// Remove deletes the source at where.
func (fixBuilder) Remove(where *source.Location) CodeFix {
return CodeFix{Location: where}
}

// Insert adds text at the start of where and keeps everything already there.
func (fixBuilder) Insert(where *source.Location, text string) CodeFix {
if where == nil {
return CodeFix{NewText: text}
}
point := *where
point.End = where.Start
return CodeFix{Location: &point, NewText: text}
}

// Replace swaps the source at where for newText.
func (fixBuilder) Replace(where *source.Location, newText string) CodeFix {
return CodeFix{Location: where, NewText: newText}
}

type DiagnosticExtra struct {
Kind DiagnosticExtraKind
Text DiagnosticText
CodeHint CodeHint
Kind DiagnosticExtraKind
Text DiagnosticText
}

type Diagnostic struct {
Expand All @@ -74,29 +108,6 @@ type Diagnostic struct {

const internalCompilerErrorCode = "ICE0001"

type CodeHintLine struct {
Prefix string
Code string
BaseColor colors.COLOR
}

type CodeHint struct {
Code string
Lines []CodeHintLine
Labels []CodeHintLabel
Location *source.Location
BaseColor colors.COLOR
GutterColor colors.COLOR
}

type CodeHintLabel struct {
Line int
Column int
Length int
Message string
Style LabelStyle
}

func NewError(message string) *Diagnostic {
return &Diagnostic{Severity: Error, Message: message}
}
Expand Down Expand Up @@ -182,81 +193,48 @@ func (d *Diagnostic) markInternalCompilerError(message string) *Diagnostic {
return d
}

func (d *Diagnostic) WithCodeHint(loc *source.Location, code string, labels ...CodeHintLabel) *Diagnostic {
if loc == nil {
// WithText adds a `= kind: message` line. Fixes are the source edits the
// message proposes; each fixed line is shown beneath it.
func (d *Diagnostic) WithText(kind, message string, color colors.COLOR, fixes ...CodeFix) *Diagnostic {
if message == "" {
return d
}
d.WithPrimaryLabel(loc, "")
d.Extras = append(d.Extras, DiagnosticExtra{
Kind: ExtraCodeHint,
CodeHint: CodeHint{
Code: code,
Labels: labels,
Location: loc,
GutterColor: colors.GREEN,
},
})
return d
}

func (d *Diagnostic) WithCodeHintLines(loc *source.Location, lines []CodeHintLine, labels ...CodeHintLabel) *Diagnostic {
if loc == nil {
return d
if color == "" {
color = colors.WHITE
}
d.WithPrimaryLabel(loc, "")
d.Extras = append(d.Extras, DiagnosticExtra{
Kind: ExtraCodeHint,
CodeHint: CodeHint{
Lines: append([]CodeHintLine(nil), lines...),
Labels: labels,
Location: loc,
GutterColor: colors.GREEN,
},
Kind: ExtraText,
Text: DiagnosticText{Kind: kind, Message: message, Color: color, Fixes: fixes},
})
return d
}

func (d *Diagnostic) WithCodeInsertion(loc *source.Location, code string, labels ...CodeHintLabel) *Diagnostic {
return d.WithCodeHintLines(loc, []CodeHintLine{
{Prefix: "+", Code: code, BaseColor: colors.GREEN},
}, labels...)
func (d *Diagnostic) Note(message string, fixes ...CodeFix) *Diagnostic {
return d.WithText("note", message, colors.CYAN, fixes...)
}

func (d *Diagnostic) WithCodeRemoval(loc *source.Location, code string, labels ...CodeHintLabel) *Diagnostic {
return d.WithCodeHintLines(loc, []CodeHintLine{
{Prefix: "-", Code: code, BaseColor: colors.RED},
}, labels...)
func (d *Diagnostic) Help(help string, fixes ...CodeFix) *Diagnostic {
return d.WithText("help", help, colors.GREEN, fixes...)
}

func (d *Diagnostic) WithCodeReplacement(loc *source.Location, oldCode, newCode string, labels ...CodeHintLabel) *Diagnostic {
return d.WithCodeHintLines(loc, []CodeHintLine{
{Prefix: "-", Code: oldCode, BaseColor: colors.RED},
{Prefix: "+", Code: newCode, BaseColor: colors.GREEN},
}, labels...)
// Choice is one of several texts that would each repair the code when
// inserted at the same place, and the condition that makes it the right one.
type Choice struct {
If string
Insert string
}

func (d *Diagnostic) WithText(kind, message string, color colors.COLOR) *Diagnostic {
if message == "" {
return d
}
if color == "" {
color = colors.WHITE
// HelpWithChoices adds a help line for each choice, with the line as that
// choice would leave it. It is for code that is wrong where the author's
// intent cannot be told, so no single fix may be offered as the answer.
// Insert may carry the spacing the insertion needs; the help names it bare.
func (d *Diagnostic) HelpWithChoices(at *source.Location, choices ...Choice) *Diagnostic {
for _, choice := range choices {
d.Help("if "+choice.If+", add `"+strings.TrimSpace(choice.Insert)+"`", Fix.Insert(at, choice.Insert))
}
d.Extras = append(d.Extras, DiagnosticExtra{
Kind: ExtraText,
Text: DiagnosticText{Kind: kind, Message: message, Color: color},
})
return d
}

func (d *Diagnostic) WithNote(message string) *Diagnostic {
return d.WithText("note", message, colors.CYAN)
}

func (d *Diagnostic) WithHelp(help string) *Diagnostic {
return d.WithText("help", help, colors.GREEN)
}

const internalCompilerFailureMessage = "internal compiler failure"

type PresentationOptions struct {
Expand Down
Loading
Loading