- commit
- 0e50106
- parent
- 90279ca
- author
- xplshn
- date
- 2025-08-24 02:21:18 +0000 UTC
gbc: Add LLVM target Signed-off-by: xplshn <[email protected]>
+282,
-0
1@@ -0,0 +1,282 @@
2+package main
3+
4+import (
5+ "fmt"
6+ "os"
7+ "os/exec"
8+ "path/filepath"
9+ "runtime"
10+ "strings"
11+
12+ "github.com/xplshn/gbc/pkg/ast"
13+ "github.com/xplshn/gbc/pkg/cli"
14+ "github.com/xplshn/gbc/pkg/codegen"
15+ "github.com/xplshn/gbc/pkg/config"
16+ "github.com/xplshn/gbc/pkg/lexer"
17+ "github.com/xplshn/gbc/pkg/parser"
18+ "github.com/xplshn/gbc/pkg/token"
19+ "github.com/xplshn/gbc/pkg/typeChecker"
20+ "github.com/xplshn/gbc/pkg/util"
21+)
22+
23+func setupFlags(cfg *config.Config, fs *cli.FlagSet) ([]cli.FlagGroupEntry, []cli.FlagGroupEntry) {
24+ var warningFlags, featureFlags []cli.FlagGroupEntry
25+
26+ for i := config.Warning(0); i < config.WarnCount; i++ {
27+ pEnable := new(bool)
28+ *pEnable = cfg.Warnings[i].Enabled
29+ pDisable := new(bool)
30+ warningFlags = append(warningFlags, cli.FlagGroupEntry{
31+ Name: cfg.Warnings[i].Name,
32+ Prefix: "W",
33+ Usage: cfg.Warnings[i].Description,
34+ Enabled: pEnable,
35+ Disabled: pDisable,
36+ })
37+ }
38+
39+ for i := config.Feature(0); i < config.FeatCount; i++ {
40+ pEnable := new(bool)
41+ *pEnable = cfg.Features[i].Enabled
42+ pDisable := new(bool)
43+ featureFlags = append(featureFlags, cli.FlagGroupEntry{
44+ Name: cfg.Features[i].Name,
45+ Prefix: "F",
46+ Usage: cfg.Features[i].Description,
47+ Enabled: pEnable,
48+ Disabled: pDisable,
49+ })
50+ }
51+
52+ fs.AddFlagGroup("Warning Flags", "Enable or disable specific warnings", "warning flag", "Available Warning Flags:", warningFlags)
53+ fs.AddFlagGroup("Feature Flags", "Enable or disable specific features", "feature flag", "Available feature flags:", featureFlags)
54+
55+ return warningFlags, featureFlags
56+}
57+
58+func main() {
59+ app := cli.NewApp("gbc")
60+ app.Synopsis = "[options] <input.b> ..."
61+ app.Description = "A compiler for the B programming language and its extensions, written in Go."
62+ app.Authors = []string{"xplshn"}
63+ app.Repository = "<https://github.com/xplshn/gbc>"
64+ app.Since = 2025
65+
66+ var (
67+ outFile string
68+ std string
69+ target string
70+ linkerArgs []string
71+ compilerArgs []string
72+ userIncludePaths []string
73+ libRequests []string
74+ )
75+
76+ fs := app.FlagSet
77+ fs.String(&outFile, "output", "o", "a.out", "Place the output into <file>", "file")
78+ fs.String(&std, "std", "", "Bx", "Specify language standard (B, Bx)", "std")
79+ fs.String(&target, "target", "t", "", "Set the backend and target ABI (e.g., llvm/x86_64-linux-gnu)", "backend/target")
80+ fs.List(&linkerArgs, "linker-arg", "", []string{}, "Pass an argument to the linker", "arg")
81+ fs.List(&compilerArgs, "compiler-arg", "", []string{}, "Pass a compiler-specific argument (e.g., -C linker_args='-s')", "arg")
82+ fs.List(&userIncludePaths, "include", "", []string{}, "Add a directory to the include path", "path")
83+ fs.Special(&libRequests, "l", "Link with a library (e.g., -lb for 'b')", "lib")
84+
85+ cfg := config.NewConfig()
86+ warningFlags, featureFlags := setupFlags(cfg, fs)
87+
88+ // Actual compilation pipeline
89+ app.Action = func(inputFiles []string) error {
90+ // Apply warning flag updates to config
91+ for i, entry := range warningFlags {
92+ if entry.Enabled != nil && *entry.Enabled {
93+ cfg.SetWarning(config.Warning(i), true)
94+ }
95+ if entry.Disabled != nil && *entry.Disabled {
96+ cfg.SetWarning(config.Warning(i), false)
97+ }
98+ }
99+
100+ // Apply feature flag updates to config
101+ for i, entry := range featureFlags {
102+ if entry.Enabled != nil && *entry.Enabled {
103+ cfg.SetFeature(config.Feature(i), true)
104+ }
105+ if entry.Disabled != nil && *entry.Disabled {
106+ cfg.SetFeature(config.Feature(i), false)
107+ }
108+ }
109+
110+ // Apply language standard
111+ if err := cfg.ApplyStd(std); err != nil {
112+ util.Error(token.Token{}, err.Error())
113+ }
114+
115+ // Set target, defaulting to the host if not specified
116+ cfg.SetTarget(runtime.GOOS, runtime.GOARCH, target)
117+
118+ // Process compiler arguments for linker args
119+ for _, carg := range compilerArgs {
120+ if parts := strings.SplitN(carg, "=", 2); len(parts) == 2 && parts[0] == "linker_args" {
121+ linkerArgs = append(linkerArgs, strings.Fields(parts[1])...)
122+ }
123+ }
124+
125+ // Process input files, searching for libraries based on the target configuration
126+ finalInputFiles := processInputFiles(inputFiles, libRequests, userIncludePaths, cfg)
127+ if len(finalInputFiles) == 0 {
128+ util.Error(token.Token{}, "no input files specified.")
129+ }
130+
131+ fmt.Println("----------------------")
132+ isTyped := cfg.IsFeatureEnabled(config.FeatTyped)
133+ fmt.Printf("Tokenizing %d source file(s) (Typed Pass: %v)...\n", len(finalInputFiles), isTyped)
134+ records, allTokens := readAndTokenizeFiles(finalInputFiles, cfg)
135+ util.SetSourceFiles(records)
136+
137+ fmt.Println("Parsing tokens into AST...")
138+ p := parser.NewParser(allTokens, cfg)
139+ astRoot := p.Parse()
140+
141+ fmt.Println("Constant folding...")
142+ astRoot = ast.FoldConstants(astRoot)
143+
144+ if isTyped {
145+ fmt.Println("Type checking...")
146+ tc := typeChecker.NewTypeChecker(cfg)
147+ tc.Check(astRoot)
148+ }
149+
150+ fmt.Println("Generating backend-agnostic IR...")
151+ cg := codegen.NewContext(cfg)
152+ irProg, inlineAsm := cg.GenerateIR(astRoot)
153+
154+ fmt.Printf("Generating target code with '%s' backend...\n", cfg.BackendName)
155+ backend := selectBackend(cfg.BackendName)
156+ backendOutput, err := backend.Generate(irProg, cfg)
157+ if err != nil {
158+ util.Error(token.Token{}, "backend code generation failed: %v", err)
159+ }
160+
161+ fmt.Printf("Assembling and linking to create '%s'...\n", outFile)
162+ if err := assembleAndLink(outFile, backendOutput.String(), inlineAsm, linkerArgs); err != nil {
163+ util.Error(token.Token{}, "assembler/linker failed: %v", err)
164+ }
165+
166+ fmt.Println("----------------------")
167+ fmt.Println("Compilation successful!")
168+ return nil
169+ }
170+
171+ if err := app.Run(os.Args[1:]); err != nil {
172+ os.Exit(1)
173+ }
174+}
175+
176+func processInputFiles(args []string, libRequests []string, userPaths []string, cfg *config.Config) []string {
177+ inputFiles := args
178+ for _, libName := range libRequests {
179+ if libPath := findLibrary(libName, userPaths, cfg); libPath != "" {
180+ inputFiles = append(inputFiles, libPath)
181+ } else {
182+ util.Error(token.Token{}, "could not find library '%s' for target %s/%s", libName, cfg.GOOS, cfg.GOARCH)
183+ }
184+ }
185+ return inputFiles
186+}
187+
188+func selectBackend(name string) codegen.Backend {
189+ switch name {
190+ case "qbe": return codegen.NewQBEBackend()
191+ case "llvm": return codegen.NewLLVMBackend()
192+ default:
193+ util.Error(token.Token{}, "unsupported backend '%s'", name)
194+ return nil
195+ }
196+}
197+
198+func findLibrary(libName string, userPaths []string, cfg *config.Config) string {
199+ // Search for libraries matching the target architecture and OS
200+ filenames := []string{
201+ fmt.Sprintf("%s_%s_%s.b", libName, cfg.GOARCH, cfg.GOOS),
202+ fmt.Sprintf("%s_%s.b", libName, cfg.GOARCH),
203+ fmt.Sprintf("%s_%s.b", libName, cfg.GOOS),
204+ fmt.Sprintf("%s.b", libName),
205+ fmt.Sprintf("%s/%s_%s.b", libName, cfg.GOARCH, cfg.GOOS),
206+ fmt.Sprintf("%s/%s.b", libName, cfg.GOARCH),
207+ fmt.Sprintf("%s/%s.b", libName, cfg.GOOS),
208+ }
209+ searchPaths := append(userPaths, []string{"./lib", "/usr/local/lib/gbc", "/usr/lib/gbc", "/lib/gbc"}...)
210+ for _, path := range searchPaths {
211+ for _, fname := range filenames {
212+ fullPath := filepath.Join(path, fname)
213+ if _, err := os.Stat(fullPath); err == nil {
214+ return fullPath
215+ }
216+ }
217+ }
218+ return ""
219+}
220+
221+func assembleAndLink(outFile, mainAsm, inlineAsm string, linkerArgs []string) error {
222+ mainAsmFile, err := os.CreateTemp("", "gbc-main-*.s")
223+ if err != nil {
224+ return fmt.Errorf("failed to create temp file for main asm: %w", err)
225+ }
226+ defer os.Remove(mainAsmFile.Name())
227+ if _, err := mainAsmFile.WriteString(mainAsm); err != nil {
228+ return fmt.Errorf("failed to write to temp file for main asm: %w", err)
229+ }
230+ mainAsmFile.Close()
231+
232+ // TODO: We want PIE support
233+ // - Fix LLVM backend to achieve that
234+ // - Our QBE backend seems to have some issues with PIE as well, but only two cases fail when doing `make examples`
235+ // We should, by default, use `-static-pie`
236+ ccArgs := []string{"-no-pie", "-o", outFile, mainAsmFile.Name()}
237+ if inlineAsm != "" {
238+ inlineAsmFile, err := os.CreateTemp("", "gbc-inline-*.s")
239+ if err != nil {
240+ return fmt.Errorf("failed to create temp file for inline asm: %w", err)
241+ }
242+ defer os.Remove(inlineAsmFile.Name())
243+ if _, err := inlineAsmFile.WriteString(inlineAsm); err != nil {
244+ return fmt.Errorf("failed to write to temp file for inline asm: %w", err)
245+ }
246+ inlineAsmFile.Close()
247+ ccArgs = append(ccArgs, inlineAsmFile.Name())
248+ }
249+ ccArgs = append(ccArgs, linkerArgs...)
250+
251+ cmd := exec.Command("cc", ccArgs...)
252+ if output, err := cmd.CombinedOutput(); err != nil {
253+ return fmt.Errorf("cc command failed: %w\nOutput:\n%s", err, string(output))
254+ }
255+ return nil
256+}
257+
258+func readAndTokenizeFiles(paths []string, cfg *config.Config) ([]util.SourceFileRecord, []token.Token) {
259+ var records []util.SourceFileRecord
260+ var allTokens []token.Token
261+
262+ for i, path := range paths {
263+ content, err := os.ReadFile(path)
264+ if err != nil {
265+ util.Error(token.Token{FileIndex: -1}, "could not read file '%s': %v", path, err)
266+ continue
267+ }
268+ runeContent := []rune(string(content))
269+ records = append(records, util.SourceFileRecord{Name: path, Content: runeContent})
270+ l := lexer.NewLexer(runeContent, i, cfg)
271+ for {
272+ tok := l.Next()
273+ if tok.Type == token.EOF {
274+ break
275+ }
276+ allTokens = append(allTokens, tok)
277+ }
278+ }
279+ finalFileIndex := 0
280+ if len(paths) > 0 { finalFileIndex = len(paths) - 1 }
281+ allTokens = append(allTokens, token.Token{Type: token.EOF, FileIndex: finalFileIndex})
282+ return records, allTokens
283+}
+19,
-16
1@@ -1,3 +1,6 @@
2+// indentation logic: vibecoded
3+// bad logic: written by me
4+// sucks: absolutely, but instead of bitching about it, open a PR. Thanks.
5 package main
6
7 import (
8@@ -426,9 +429,9 @@ func compareRuntimeResults(file string, refResult, targetResult *TargetResult) *
9 refStderr := filterOutput(refRun.Result.Stderr, ignoredSubstrings)
10 targetStderr := filterOutput(targetRun.Result.Stderr, ignoredSubstrings)
11
12- // Normalize by replacing binary paths (argv[0]) if they exist.
13- // This handles cases where a program prints its own name.
14- // We replace both the full path and the basename with a generic placeholder.
15+ // Normalize by replacing binary paths (argv[0]) if they exist
16+ // This handles cases where a program prints its own name
17+ // We replace both the full path and the basename with a generic placeholder
18 const binaryPlaceholder = "__BINARY__"
19 if refResult.BinaryPath != "" {
20 refStdout = strings.ReplaceAll(refStdout, refResult.BinaryPath, binaryPlaceholder)
21@@ -447,13 +450,13 @@ func compareRuntimeResults(file string, refResult, targetResult *TargetResult) *
22
23 if refStdout != targetStdout {
24 failed = true
25- // Show the diff of the original, unmodified output for clarity.
26+ // Show the diff of the original, unmodified output for clarity
27 diffs.WriteString(fmt.Sprintf("Run '%s' STDOUT mismatch:\n%s", refRun.Name, cmp.Diff(refRun.Result.Stdout, targetRun.Result.Stdout)))
28 }
29
30 if refStderr != targetStderr {
31 failed = true
32- // Show the diff of the original, unmodified output for clarity.
33+ // Show the diff of the original, unmodified output for clarity
34 diffs.WriteString(fmt.Sprintf("Run '%s' STDERR mismatch:\n%s", refRun.Name, cmp.Diff(refRun.Result.Stderr, targetRun.Result.Stderr)))
35 }
36 }
37@@ -536,8 +539,8 @@ func compileAndRun(compiler string, compilerArgs []string, sourceFile, tempDir,
38 return &TargetResult{Compile: compileResult}, fmt.Errorf("compilation succeeded but binary was not created at %s", binaryPath)
39 }
40
41- // Probe to see if the binary waits for stdin by running it with a very short timeout.
42- // If it times out, it's likely waiting for input.
43+ // Probe to see if the binary waits for stdin by running it with a very short timeout
44+ // If it times out, it's likely waiting for input
45 probeCtx, probeCancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
46 defer probeCancel()
47 probeResult := executeCommand(probeCtx, binaryPath, "")
48@@ -573,8 +576,8 @@ func compileAndRun(compiler string, compilerArgs []string, sourceFile, tempDir,
49 var inputData string
50 var unstableOutput bool
51
52- // If the program is detected to read from stdin, we join the args to form the input string.
53- // Otherwise, we pass them as command-line arguments.
54+ // If the program is detected to read from stdin, we join the args to form the input string
55+ // Otherwise, we pass them as command-line arguments
56 if readsStdin {
57 inputData = strings.Join(args, "\n")
58 if len(args) > 0 {
59@@ -599,9 +602,9 @@ func compileAndRun(compiler string, compilerArgs []string, sourceFile, tempDir,
60
61 if firstRunResult.ExitCode != runResult.ExitCode || filteredFirstStdout != filteredCurrentStdout || filteredFirstStderr != filteredCurrentStderr {
62 unstableOutput = true
63- // Inconsistent output. We'll use the first run's result but mark it.
64+ // Inconsistent output. We'll use the first run's result but mark it
65 // We stop iterating because finding the "fastest" run is meaningless
66- // if the output is different each time.
67+ // if the output is different each time
68 break
69 }
70 }
71@@ -628,7 +631,7 @@ func compileAndRun(compiler string, compilerArgs []string, sourceFile, tempDir,
72 return &TargetResult{Compile: compileResult, Runs: runResults, BinaryPath: binaryPath}, nil
73 }
74
75-// filterOutput removes lines containing any of the given substrings.
76+// filterOutput removes lines containing any of the given substrings
77 func filterOutput(output string, ignoredSubstrings []string) string {
78 if len(ignoredSubstrings) == 0 || output == "" {
79 return output
80@@ -664,7 +667,7 @@ func printSummary(results []*FileTestResult) {
81 var totalTargetCompile, totalRefCompile, totalTargetRuntime, totalRefRuntime time.Duration
82 var comparedFileCount, runtimeFileCount int
83
84- // Pre-calculate max lengths for alignment.
85+ // Pre-calculate max lengths for alignment
86 var maxTestNameLen int
87 targetName := filepath.Base(*targetCompiler)
88 refName := filepath.Base(*refCompiler)
89@@ -685,7 +688,7 @@ func printSummary(results []*FileTestResult) {
90 }
91 }
92
93- // Only calculate maxTestNameLen if in verbose mode, as it's only used there.
94+ // Only calculate maxTestNameLen if in verbose mode, as it's only used there
95 if *verbose {
96 for _, result := range results {
97 isBothFailed := result.Message == "Both compilers failed to compile as expected"
98@@ -768,11 +771,11 @@ func printSummary(results []*FileTestResult) {
99
100 var summaryPadding string
101 if *verbose && maxTestNameLen > 0 {
102- // Aligns the summary block with the performance block in verbose mode.
103+ // Aligns the summary block with the performance block in verbose mode
104 // Prefix is " [PASS] " (8) + name (maxTestNameLen) + " " (1)
105 summaryPadding = strings.Repeat(" ", 8+maxTestNameLen+1)
106 } else {
107- // In non-verbose mode, just indent slightly.
108+ // In non-verbose mode, just indent slightly
109 summaryPadding = " "
110 }
111
M
go.mod
+3,
-2
1@@ -7,7 +7,8 @@ toolchain go1.24.6
2 require (
3 github.com/cespare/xxhash/v2 v2.3.0
4 github.com/google/go-cmp v0.7.0
5- modernc.org/libqbe v0.3.21
6+ golang.org/x/term v0.34.0
7+ modernc.org/libqbe v0.3.23
8 )
9
10 require (
11@@ -17,7 +18,7 @@ require (
12 github.com/mattn/go-isatty v0.0.20 // indirect
13 github.com/ncruces/go-strftime v0.1.9 // indirect
14 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
15- golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect
16+ golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect
17 golang.org/x/sys v0.35.0 // indirect
18 modernc.org/goabi0 v0.2.0 // indirect
19 modernc.org/libc v1.66.7 // indirect
M
go.sum
+9,
-0
1@@ -22,6 +22,8 @@ golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGc
2 golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
3 golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE=
4 golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
5+golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0=
6+golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
7 golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
8 golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
9 golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
10@@ -32,20 +34,25 @@ golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
11 golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
12 golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
13 golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
14+golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
15+golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
16 golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
17 golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
18 golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
19 modernc.org/cc/v4 v4.26.3 h1:yEN8dzrkRFnn4PUUKXLYIqVf2PJYAEjMTFjO3BDGc3I=
20 modernc.org/cc/v4 v4.26.3/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
21+modernc.org/cc/v4 v4.26.4 h1:jPhG8oNjtTYuP2FA4YefTJ/wioNUGALmGuEWt7SUR6s=
22 modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
23 modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
24 modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM=
25 modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
26 modernc.org/fileutil v1.3.15 h1:rJAXTP6ilMW/1+kzDiqmBlHLWszheUFXIyGQIAvjJpY=
27+modernc.org/fileutil v1.3.20 h1:HxYM7QaeqszXhtIbmcao35huy9YTYRrRZuN4saQovG8=
28 modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
29 modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
30 modernc.org/gc/v3 v3.1.0 h1:CiObI+9ROz7pjjH3iAgMPaFCN5zE3sN5KF4jet8BWdc=
31 modernc.org/gc/v3 v3.1.0/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
32+modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
33 modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
34 modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
35 modernc.org/libc v1.66.6 h1:RyQpwAhM/19nXD8y3iejM/AjmKwY2TjxZTlUWTsWw2U=
36@@ -62,6 +69,8 @@ modernc.org/libqbe v0.3.20 h1:MQ7/yQ1YOww6iYUrYo+ffrm8v+7L0FR/ZTHtWJmJaQ8=
37 modernc.org/libqbe v0.3.20/go.mod h1:v9jfQ3pPqP0lloc3x9s/O0QyTrAyWl7nBRDc3CA1EKY=
38 modernc.org/libqbe v0.3.21 h1:qDlRpTO1aQ4gPUXZv/6SLblMq1nOajWsi4ibsPIaZVY=
39 modernc.org/libqbe v0.3.21/go.mod h1:v9jfQ3pPqP0lloc3x9s/O0QyTrAyWl7nBRDc3CA1EKY=
40+modernc.org/libqbe v0.3.23 h1:EDYelNyP3blv6lOTGsVwreNqeto3ZzgqAcP8dYSzBOA=
41+modernc.org/libqbe v0.3.23/go.mod h1:V1AfFp9d5BdPzws+QyZENCC+xl/LZ3//WFDithgZAhs=
42 modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
43 modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
44 modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
+23,
-63
1@@ -46,13 +46,13 @@ const (
2 Directive
3 )
4
5-// Node represents a node in the Abstract Syntax Tree.
6+// Node represents a node in the Abstract Syntax Tree
7 type Node struct {
8 Type NodeType
9 Tok token.Token
10 Parent *Node
11 Data interface{}
12- Typ *BxType // Set by the type checker.
13+ Typ *BxType // Set by the type checker
14 }
15
16 type BxTypeKind int
17@@ -329,62 +329,30 @@ func FoldConstants(node *Node) *Node {
18 var res int64
19 folded := true
20 switch d.Op {
21- case token.Plus:
22- res = l + r
23- case token.Minus:
24- res = l - r
25- case token.Star:
26- res = l * r
27- case token.And:
28- res = l & r
29- case token.Or:
30- res = l | r
31- case token.Xor:
32- res = l ^ r
33- case token.Shl:
34- res = l << uint64(r)
35- case token.Shr:
36- res = l >> uint64(r)
37- case token.EqEq:
38- if l == r {
39- res = 1
40- }
41- case token.Neq:
42- if l != r {
43- res = 1
44- }
45- case token.Lt:
46- if l < r {
47- res = 1
48- }
49- case token.Gt:
50- if l > r {
51- res = 1
52- }
53- case token.Lte:
54- if l <= r {
55- res = 1
56- }
57- case token.Gte:
58- if l >= r {
59- res = 1
60- }
61+ case token.Plus: res = l + r
62+ case token.Minus: res = l - r
63+ case token.Star: res = l * r
64+ case token.And: res = l & r
65+ case token.Or: res = l | r
66+ case token.Xor: res = l ^ r
67+ case token.Shl: res = l << uint64(r)
68+ case token.Shr: res = l >> uint64(r)
69+ case token.EqEq: if l == r { res = 1 }
70+ case token.Neq: if l != r { res = 1 }
71+ case token.Lt: if l < r { res = 1 }
72+ case token.Gt: if l > r { res = 1 }
73+ case token.Lte: if l <= r { res = 1 }
74+ case token.Gte: if l >= r { res = 1 }
75 case token.Slash:
76- if r == 0 {
77- util.Error(node.Tok, "Compile-time division by zero.")
78- }
79+ if r == 0 { util.Error(node.Tok, "Compile-time division by zero.") }
80 res = l / r
81 case token.Rem:
82- if r == 0 {
83- util.Error(node.Tok, "Compile-time modulo by zero.")
84- }
85+ if r == 0 { util.Error(node.Tok, "Compile-time modulo by zero.") }
86 res = l % r
87 default:
88 folded = false
89 }
90- if folded {
91- return NewNumber(node.Tok, res)
92- }
93+ if folded { return NewNumber(node.Tok, res) }
94 }
95 case UnaryOp:
96 d := node.Data.(UnaryOpNode)
97@@ -393,22 +361,14 @@ func FoldConstants(node *Node) *Node {
98 var res int64
99 folded := true
100 switch d.Op {
101- case token.Minus:
102- res = -val
103- case token.Complement:
104- res = ^val
105- case token.Not:
106- if val == 0 {
107- res = 1
108- }
109+ case token.Minus: res = -val
110+ case token.Complement: res = ^val
111+ case token.Not: if val == 0 { res = 1 }
112 default:
113 folded = false
114 }
115- if folded {
116- return NewNumber(node.Tok, res)
117- }
118+ if folded { return NewNumber(node.Tok, res) }
119 }
120 }
121-
122 return node
123 }
+563,
-0
1@@ -0,0 +1,563 @@
2+// package cli is ugly and tries but fails miserable at being a general-purpose, usable CLi library
3+package cli
4+
5+import (
6+ "fmt"
7+ "os"
8+ "sort"
9+ "strconv"
10+ "strings"
11+ "time"
12+
13+ "golang.org/x/term"
14+)
15+
16+// IndentState manages hierarchical indentation levels
17+type IndentState struct {
18+ levels []uint8
19+ baseUnit uint8
20+}
21+
22+func NewIndentState() *IndentState {
23+ return &IndentState{
24+ levels: []uint8{0},
25+ baseUnit: 4,
26+ }
27+}
28+
29+func (is *IndentState) Push() {
30+ currentLevel := is.levels[len(is.levels)-1]
31+ is.levels = append(is.levels, currentLevel+1)
32+}
33+
34+func (is *IndentState) Pop() {
35+ if len(is.levels) > 1 {
36+ is.levels = is.levels[:len(is.levels)-1]
37+ }
38+}
39+
40+func (is *IndentState) Current() string {
41+ level := is.levels[len(is.levels)-1]
42+ return strings.Repeat(" ", int(is.baseUnit*level))
43+}
44+
45+func (is *IndentState) AtLevel(level int) string {
46+ return strings.Repeat(" ", int(is.baseUnit*uint8(level)))
47+}
48+
49+// Value is the interface to the dynamic value stored in a flag
50+type Value interface {
51+ String() string
52+ Set(string) error
53+ Get() any
54+}
55+
56+type stringValue struct{ p *string }
57+
58+func (v *stringValue) Set(s string) error { *v.p = s; return nil }
59+func (v *stringValue) String() string { return *v.p }
60+func (v *stringValue) Get() any { return *v.p }
61+func newStringValue(p *string) *stringValue { return &stringValue{p} }
62+
63+type boolValue struct{ p *bool }
64+
65+func (v *boolValue) Set(s string) error {
66+ val, err := strconv.ParseBool(s)
67+ // Allow setting a bool flag without a value, e.g., --verbose
68+ if err != nil && s != "" {
69+ return fmt.Errorf("invalid boolean value '%s': %w", s, err)
70+ }
71+ *v.p = val || s == ""
72+ return nil
73+}
74+func (v *boolValue) String() string { return strconv.FormatBool(*v.p) }
75+func (v *boolValue) Get() any { return *v.p }
76+func newBoolValue(p *bool) *boolValue {
77+ return &boolValue{p}
78+}
79+
80+type listValue struct{ p *[]string }
81+
82+func (v *listValue) Set(s string) error { *v.p = append(*v.p, s); return nil }
83+func (v *listValue) String() string { return strings.Join(*v.p, ", ") }
84+func (v *listValue) Get() any { return *v.p }
85+func newListValue(p *[]string) *listValue { return &listValue{p} }
86+
87+type Flag struct {
88+ Name string
89+ Shorthand string
90+ Usage string
91+ Value Value
92+ DefValue string
93+ ExpectedType string // Type placeholder for non-boolean flags (e.g., "<file>")
94+}
95+
96+// FlagGroup is a collection of related flags, like feature or warning flags
97+type FlagGroup struct {
98+ Name string
99+ Description string
100+ Flags []FlagGroupEntry
101+ GroupType string // e.g., "warning flag", "feature flag"
102+ AvailableFlagsHeader string // e.g., "Available Warning Flags:"
103+}
104+
105+type FlagGroupEntry struct {
106+ Name string // Name without prefix (e.g., "all" for "-Wall")
107+ Prefix string // Group prefix (e.g., "W", "F")
108+ Usage string
109+ Enabled *bool
110+ Disabled *bool
111+}
112+
113+type FlagSet struct {
114+ name string
115+ flags map[string]*Flag
116+ shorthands map[string]*Flag
117+ specialPrefix map[string]*Flag
118+ args []string
119+ flagGroups []FlagGroup
120+}
121+
122+func NewFlagSet(name string) *FlagSet {
123+ return &FlagSet{
124+ name: name,
125+ flags: make(map[string]*Flag),
126+ shorthands: make(map[string]*Flag),
127+ specialPrefix: make(map[string]*Flag),
128+ }
129+}
130+
131+func (f *FlagSet) Args() []string { return f.args }
132+
133+func (f *FlagSet) String(p *string, name, shorthand, value, usage, expectedType string) {
134+ *p = value
135+ f.Var(newStringValue(p), name, shorthand, usage, value, expectedType)
136+}
137+
138+func (f *FlagSet) Bool(p *bool, name, shorthand string, value bool, usage string) {
139+ *p = value
140+ f.Var(newBoolValue(p), name, shorthand, usage, strconv.FormatBool(value), "")
141+}
142+
143+// List defines a flag that can be specified multiple times to build a list of strings
144+func (f *FlagSet) List(p *[]string, name, shorthand string, value []string, usage, expectedType string) {
145+ *p = value
146+ f.Var(newListValue(p), name, shorthand, usage, fmt.Sprintf("%v", value), expectedType)
147+}
148+
149+// Special defines a flag with a prefix that captures the value directly, like -lm for library 'm'
150+func (f *FlagSet) Special(p *[]string, prefix, usage, expectedType string) {
151+ *p = []string{}
152+ f.Var(newListValue(p), prefix, "", usage, "", expectedType)
153+ f.specialPrefix[prefix] = f.flags[prefix]
154+}
155+
156+func (f *FlagSet) AddFlagGroup(name, description, groupType, availableFlagsHeader string, entries []FlagGroupEntry) {
157+ for i := range entries {
158+ if entries[i].Enabled != nil {
159+ f.Bool(entries[i].Enabled, entries[i].Prefix+entries[i].Name, "", *entries[i].Enabled, entries[i].Usage)
160+ }
161+ if entries[i].Disabled != nil {
162+ // Automatically generate the 'disable' usage message. Unreliable and bad.
163+ disableUsage := "Disable '" + entries[i].Name + "'"
164+ f.Bool(entries[i].Disabled, entries[i].Prefix+"no-"+entries[i].Name, "", *entries[i].Disabled, disableUsage)
165+ }
166+ }
167+ f.flagGroups = append(f.flagGroups, FlagGroup{
168+ Name: name,
169+ Description: description,
170+ Flags: entries,
171+ GroupType: groupType,
172+ AvailableFlagsHeader: availableFlagsHeader,
173+ })
174+}
175+
176+func (f *FlagSet) Var(value Value, name, shorthand, usage, defValue, expectedType string) {
177+ if name == "" { panic("flag name cannot be empty") }
178+ flag := &Flag{Name: name, Shorthand: shorthand, Usage: usage, Value: value, DefValue: defValue, ExpectedType: expectedType}
179+ if _, ok := f.flags[name]; ok { panic(fmt.Sprintf("flag redefined: %s", name)) }
180+ f.flags[name] = flag
181+ if shorthand != "" {
182+ if _, ok := f.shorthands[shorthand]; ok {
183+ panic(fmt.Sprintf("shorthand flag redefined: %s", shorthand))
184+ }
185+ f.shorthands[shorthand] = flag
186+ }
187+}
188+
189+func (f *FlagSet) Parse(arguments []string) error {
190+ f.args = []string{}
191+ for i := 0; i < len(arguments); i++ {
192+ arg := arguments[i]
193+ if len(arg) < 2 || arg[0] != '-' {
194+ f.args = append(f.args, arg)
195+ continue
196+ }
197+ if arg == "--" {
198+ f.args = append(f.args, arguments[i+1:]...)
199+ break
200+ }
201+ if strings.HasPrefix(arg, "--") {
202+ if err := f.parseLongFlag(arg, arguments, &i); err != nil {
203+ return err
204+ }
205+ } else {
206+ if err := f.parseShortFlag(arg, arguments, &i); err != nil {
207+ return err
208+ }
209+ }
210+ }
211+ return nil
212+}
213+
214+func (f *FlagSet) parseLongFlag(arg string, arguments []string, i *int) error {
215+ parts := strings.SplitN(arg[2:], "=", 2)
216+ name := parts[0]
217+ if name == "" {
218+ return fmt.Errorf("empty flag name")
219+ }
220+ flag, ok := f.flags[name]
221+ if !ok {
222+ return fmt.Errorf("unknown flag: --%s", name)
223+ }
224+ if len(parts) == 2 {
225+ return flag.Value.Set(parts[1])
226+ }
227+ if _, isBool := flag.Value.(*boolValue); isBool {
228+ return flag.Value.Set("") // E.g., --verbose
229+ }
230+ if *i+1 >= len(arguments) {
231+ return fmt.Errorf("flag needs an argument: --%s", name)
232+ }
233+ *i++
234+ return flag.Value.Set(arguments[*i])
235+}
236+
237+func (f *FlagSet) parseShortFlag(arg string, arguments []string, i *int) error {
238+ // Handle special prefix flags like -I/path/to/include or -lm
239+ for prefix, flag := range f.specialPrefix {
240+ if strings.HasPrefix(arg, "-"+prefix) && len(arg) > len(prefix)+1 {
241+ return flag.Value.Set(arg[len(prefix)+1:])
242+ }
243+ }
244+
245+ shorthand := arg[1:2]
246+ flag, ok := f.shorthands[shorthand]
247+ if !ok {
248+ return fmt.Errorf("unknown shorthand flag: -%s", shorthand)
249+ }
250+ if _, isBool := flag.Value.(*boolValue); isBool {
251+ return flag.Value.Set("") // E.g., -h
252+ }
253+ // assume -o <val> and not combined short flags
254+ value := arg[2:]
255+ if value == "" {
256+ if *i+1 >= len(arguments) {
257+ return fmt.Errorf("flag needs an argument: -%s", shorthand)
258+ }
259+ *i++
260+ value = arguments[*i]
261+ }
262+ return flag.Value.Set(value)
263+}
264+
265+type App struct {
266+ Name string
267+ Synopsis string
268+ Description string
269+ Authors []string
270+ Repository string
271+ Since int
272+ FlagSet *FlagSet
273+ Action func(args []string) error
274+}
275+
276+func NewApp(name string) *App {
277+ return &App{
278+ Name: name,
279+ FlagSet: NewFlagSet(name),
280+ }
281+}
282+
283+func (f *FlagSet) Lookup(name string) *Flag {
284+ return f.flags[name]
285+}
286+
287+func (a *App) Run(arguments []string) error {
288+ help := false
289+ a.FlagSet.Bool(&help, "help", "h", false, "Display this information")
290+
291+ if err := a.FlagSet.Parse(arguments); err != nil {
292+ fmt.Fprintln(os.Stderr, err)
293+ a.generateHelpPage(os.Stderr)
294+ return err
295+ }
296+ if help {
297+ a.generateHelpPage(os.Stdout)
298+ return nil
299+ }
300+ if a.Action != nil {
301+ return a.Action(a.FlagSet.Args())
302+ }
303+ return nil
304+}
305+
306+func (a *App) generateHelpPage(w *os.File) {
307+ var sb strings.Builder
308+ termWidth := getTerminalWidth()
309+ indent := NewIndentState()
310+
311+ globalMaxWidth := a.calculateGlobalMaxWidth()
312+
313+ // Calculate the maximum usage string width across all flag sections for alignment
314+ globalMaxUsageWidth := 0
315+ updateMaxUsage := func(s string) {
316+ if len(s) > globalMaxUsageWidth {
317+ globalMaxUsageWidth = len(s)
318+ }
319+ }
320+ optionFlags := a.getOptionFlags() // Get once to reuse
321+ for _, flag := range optionFlags {
322+ updateMaxUsage(flag.Usage)
323+ }
324+ for _, group := range a.FlagSet.flagGroups {
325+ for _, entry := range group.Flags {
326+ updateMaxUsage(entry.Usage)
327+ }
328+ }
329+
330+ year := time.Now().Year()
331+ sb.WriteString("\n")
332+ fmt.Fprintf(&sb, "%sCopyright (c) %d: %s\n", indent.AtLevel(1), year, strings.Join(a.Authors, ", ")+" and contributors")
333+ if a.Repository != "" {
334+ fmt.Fprintf(&sb, "%sFor more details refer to %s\n", indent.AtLevel(1), a.Repository)
335+ }
336+
337+ if a.Synopsis != "" {
338+ sb.WriteString("\n")
339+ fmt.Fprintf(&sb, "%sSynopsis\n", indent.AtLevel(1))
340+ fmt.Fprintf(&sb, "%s%s %s\n", indent.AtLevel(2), a.Name, a.Synopsis)
341+ }
342+
343+ if a.Description != "" {
344+ sb.WriteString("\n")
345+ fmt.Fprintf(&sb, "%sDescription\n", indent.AtLevel(1))
346+ fmt.Fprintf(&sb, "%s%s\n", indent.AtLevel(2), a.Description)
347+ }
348+
349+ if len(optionFlags) > 0 {
350+ sb.WriteString("\n")
351+ fmt.Fprintf(&sb, "%sOptions\n", indent.AtLevel(1))
352+ sort.Slice(optionFlags, func(i, j int) bool { return optionFlags[i].Name < optionFlags[j].Name })
353+ for _, flag := range optionFlags {
354+ a.formatFlagLine(&sb, flag, indent, termWidth, globalMaxWidth, globalMaxUsageWidth)
355+ }
356+ }
357+
358+ if len(a.FlagSet.flagGroups) > 0 {
359+ sortedGroups := make([]FlagGroup, len(a.FlagSet.flagGroups))
360+ copy(sortedGroups, a.FlagSet.flagGroups)
361+ sort.Slice(sortedGroups, func(i, j int) bool { return sortedGroups[i].Name < sortedGroups[j].Name })
362+ for _, group := range sortedGroups {
363+ a.formatFlagGroup(&sb, group, indent, termWidth, globalMaxWidth, globalMaxUsageWidth)
364+ }
365+ }
366+ fmt.Fprint(w, sb.String())
367+}
368+
369+func (a *App) getOptionFlags() []*Flag {
370+ var optionFlags []*Flag
371+ for _, flag := range a.FlagSet.flags {
372+ if _, isSpecial := a.FlagSet.specialPrefix[flag.Name]; isSpecial {
373+ continue
374+ }
375+ if a.isGroupFlag(flag.Name) {
376+ continue
377+ }
378+ optionFlags = append(optionFlags, flag)
379+ }
380+ return optionFlags
381+}
382+
383+func (a *App) isGroupFlag(flagName string) bool {
384+ for _, group := range a.FlagSet.flagGroups {
385+ for _, entry := range group.Flags {
386+ if flagName == entry.Prefix+entry.Name || flagName == entry.Prefix+"no-"+entry.Name {
387+ return true
388+ }
389+ }
390+ }
391+ return false
392+}
393+
394+func (a *App) calculateGlobalMaxWidth() int {
395+ maxWidth := 0
396+ checkWidth := func(s string) {
397+ if len(s) > maxWidth {
398+ maxWidth = len(s)
399+ }
400+ }
401+ for _, flag := range a.getOptionFlags() {
402+ checkWidth(a.formatFlagString(flag))
403+ }
404+ for _, group := range a.FlagSet.flagGroups {
405+ prefix := group.Flags[0].Prefix
406+ groupType := strings.ToLower(strings.TrimSuffix(group.Name, "s"))
407+ checkWidth(fmt.Sprintf("-%s<%s>", prefix, groupType))
408+ checkWidth(fmt.Sprintf("-%sno-<%s>", prefix, groupType))
409+ for _, entry := range group.Flags {
410+ checkWidth(entry.Name)
411+ }
412+ }
413+ return maxWidth
414+}
415+
416+func (a *App) formatFlagString(flag *Flag) string {
417+ var flagStr strings.Builder
418+ _, isBool := flag.Value.(*boolValue)
419+
420+ if flag.Shorthand != "" {
421+ fmt.Fprintf(&flagStr, "-%s", flag.Shorthand)
422+ if !isBool {
423+ fmt.Fprintf(&flagStr, " <%s>", flag.ExpectedType)
424+ }
425+ fmt.Fprintf(&flagStr, ", --%s", flag.Name)
426+ if !isBool {
427+ fmt.Fprintf(&flagStr, " <%s>", flag.ExpectedType)
428+ }
429+ } else {
430+ fmt.Fprintf(&flagStr, "--%s", flag.Name)
431+ if !isBool {
432+ fmt.Fprintf(&flagStr, "<%s>", flag.ExpectedType)
433+ }
434+ }
435+ return flagStr.String()
436+}
437+
438+func (a *App) formatEntry(sb *strings.Builder, indent *IndentState, termWidth int, leftPart, usagePart, rightPart string, globalLeftWidth, globalMaxUsageWidth int) {
439+ indentStr := indent.AtLevel(2)
440+ indentWidth := len(indentStr)
441+ spaceWidth := 1
442+
443+ fixedPartsWidth := indentWidth + globalLeftWidth + spaceWidth + 2 + len(rightPart)
444+ maxFirstUsageWidth := termWidth - fixedPartsWidth
445+ if maxFirstUsageWidth < 10 {
446+ maxFirstUsageWidth = 10
447+ }
448+
449+ usageLines := wrapText(usagePart, maxFirstUsageWidth)
450+
451+ firstUsageLine := ""
452+ if len(usageLines) > 0 {
453+ firstUsageLine = usageLines[0]
454+ }
455+
456+ desiredUsageWidth := globalMaxUsageWidth
457+ if desiredUsageWidth > maxFirstUsageWidth {
458+ desiredUsageWidth = maxFirstUsageWidth
459+ }
460+
461+ if rightPart != "" {
462+ fmt.Fprintf(sb, "%s%-*s %-*s %s\n", indent.AtLevel(2), globalLeftWidth, leftPart, desiredUsageWidth, firstUsageLine, rightPart)
463+ } else {
464+ fmt.Fprintf(sb, "%s%-*s %s\n", indent.AtLevel(2), globalLeftWidth, leftPart, firstUsageLine)
465+ }
466+
467+ wrappedIndent := strings.Repeat(" ", globalLeftWidth+spaceWidth)
468+
469+ availableWrappedWidth := termWidth - (indentWidth + globalLeftWidth + spaceWidth)
470+ if availableWrappedWidth < 10 {
471+ availableWrappedWidth = 10
472+ }
473+
474+ wrappedLineMaxWidth := desiredUsageWidth + 2
475+ termAvailable := termWidth - (indentWidth + globalLeftWidth + spaceWidth)
476+ if wrappedLineMaxWidth > termAvailable {
477+ wrappedLineMaxWidth = termAvailable
478+ }
479+
480+ for i := 1; i < len(usageLines); i++ {
481+ fmt.Fprintf(sb, "%s%s%s\n", indentStr, wrappedIndent, usageLines[i])
482+ }
483+}
484+
485+func (a *App) formatFlagLine(sb *strings.Builder, flag *Flag, indent *IndentState, termWidth, globalMaxWidth, globalMaxUsageWidth int) {
486+ leftPart := a.formatFlagString(flag)
487+ usagePart := flag.Usage
488+
489+ rightPart := ""
490+ if flag.DefValue != "" && flag.DefValue != "false" && flag.DefValue != "[]" {
491+ if _, isBool := flag.Value.(*boolValue); !isBool {
492+ rightPart = fmt.Sprintf("|%s|", flag.DefValue)
493+ }
494+ }
495+ a.formatEntry(sb, indent, termWidth, leftPart, usagePart, rightPart, globalMaxWidth, globalMaxUsageWidth)
496+}
497+
498+func (a *App) formatFlagGroup(sb *strings.Builder, group FlagGroup, indent *IndentState, termWidth, globalMaxWidth, globalMaxUsageWidth int) {
499+ sb.WriteString("\n")
500+ fmt.Fprintf(sb, "%s%s\n", indent.AtLevel(1), group.Name)
501+
502+ prefix := group.Flags[0].Prefix
503+ groupType := group.GroupType
504+ if groupType == "" {
505+ groupType = "flag" // Default value if not provided
506+ }
507+
508+ fmt.Fprintf(sb, "%s%-*s Enable a specific %s\n", indent.AtLevel(2), globalMaxWidth, fmt.Sprintf("-%s<%s>", prefix, groupType), groupType)
509+ fmt.Fprintf(sb, "%s%-*s Disable a specific %s\n", indent.AtLevel(2), globalMaxWidth, fmt.Sprintf("-%sno-<%s>", prefix, groupType), groupType)
510+
511+ if group.AvailableFlagsHeader != "" {
512+ fmt.Fprintf(sb, "%s%s\n", indent.AtLevel(1), group.AvailableFlagsHeader)
513+ }
514+
515+ sortedEntries := make([]FlagGroupEntry, len(group.Flags))
516+ copy(sortedEntries, group.Flags)
517+ sort.Slice(sortedEntries, func(i, j int) bool { return sortedEntries[i].Name < sortedEntries[j].Name })
518+
519+ for _, entry := range sortedEntries {
520+ rightPart := ""
521+ if entry.Enabled != nil && *entry.Enabled && (entry.Disabled == nil || !*entry.Disabled) {
522+ rightPart = "|x|"
523+ } else {
524+ rightPart = "|-|"
525+ }
526+ a.formatEntry(sb, indent, termWidth, entry.Name, entry.Usage, rightPart, globalMaxWidth, globalMaxUsageWidth)
527+ }
528+}
529+
530+func getTerminalWidth() int {
531+ width, _, err := term.GetSize(int(os.Stdout.Fd()))
532+ if err != nil { return 80 }
533+ if width < 20 { return 20 }
534+ return width
535+}
536+
537+func wrapText(text string, maxWidth int) []string {
538+ if maxWidth <= 0 { return []string{text} }
539+ words := strings.Fields(text)
540+ if len(words) == 0 { return []string{} }
541+
542+ var lines []string
543+ var currentLine strings.Builder
544+ currentLen := 0
545+
546+ for _, word := range words {
547+ wordLen := len(word)
548+ if currentLen+wordLen+1 > maxWidth && currentLen > 0 {
549+ lines = append(lines, currentLine.String())
550+ currentLine.Reset()
551+ currentLen = 0
552+ }
553+ if currentLen > 0 {
554+ currentLine.WriteString(" ")
555+ currentLen++
556+ }
557+ currentLine.WriteString(word)
558+ currentLen += wordLen
559+ }
560+ if currentLine.Len() > 0 {
561+ lines = append(lines, currentLine.String())
562+ }
563+ return lines
564+}
+14,
-0
1@@ -0,0 +1,14 @@
2+package codegen
3+
4+import (
5+ "bytes"
6+ "github.com/xplshn/gbc/pkg/config"
7+ "github.com/xplshn/gbc/pkg/ir"
8+)
9+
10+// Backend is the interface that all code generation backends must implement.
11+type Backend interface {
12+ // Generate takes an IR program and a configuration, and produces the target
13+ // assembly or intermediate language as a byte buffer.
14+ Generate(prog *ir.Program, cfg *config.Config) (*bytes.Buffer, error)
15+}
+415,
-1076
1@@ -2,11 +2,10 @@ package codegen
2
3 import (
4 "fmt"
5- "strconv"
6- "strings"
7
8 "github.com/xplshn/gbc/pkg/ast"
9 "github.com/xplshn/gbc/pkg/config"
10+ "github.com/xplshn/gbc/pkg/ir"
11 "github.com/xplshn/gbc/pkg/token"
12 "github.com/xplshn/gbc/pkg/util"
13 )
14@@ -25,7 +24,7 @@ type symbol struct {
15 Name string
16 Type symbolType
17 BxType *ast.BxType
18- QbeName string
19+ IRVal ir.Value
20 IsVector bool
21 IsByteArray bool
22 StackOffset int64
23@@ -38,43 +37,49 @@ type scope struct {
24 Parent *scope
25 }
26
27-type stringEntry struct {
28- Value string
29- Label string
30-}
31-
32 type autoVarInfo struct {
33 Node *ast.Node
34 Size int64
35 }
36
37+type switchContext struct {
38+ Node *ast.SwitchNode
39+ CaseIndex int
40+}
41+
42+// Context holds the state for the codegen pass
43 type Context struct {
44- out strings.Builder
45- asmOut strings.Builder
46- strings []stringEntry
47- tempCount int
48- labelCount int
49- currentScope *scope
50- currentFunc *ast.FuncDeclNode
51- currentFuncFrame string
52- currentBlockLabel string
53- breakLabel string
54- continueLabel string
55- wordType string
56- wordSize int
57- stackAlign int
58- isTypedPass bool
59- cfg *config.Config
60+ prog *ir.Program
61+ inlineAsm string
62+ tempCount int
63+ labelCount int
64+ currentScope *scope
65+ currentFunc *ir.Func
66+ currentBlock *ir.BasicBlock
67+ breakLabel *ir.Label
68+ continueLabel *ir.Label
69+ wordSize int
70+ stackAlign int
71+ isTypedPass bool
72+ cfg *config.Config
73+ switchStack []*switchContext
74 }
75
76+// NewContext creates a new codegen context
77 func NewContext(cfg *config.Config) *Context {
78 return &Context{
79+ prog: &ir.Program{
80+ Strings: make(map[string]string),
81+ ExtrnFuncs: make([]string, 0),
82+ ExtrnVars: make(map[string]bool),
83+ WordSize: cfg.WordSize,
84+ },
85 currentScope: newScope(nil),
86 wordSize: cfg.WordSize,
87- wordType: cfg.WordType,
88 stackAlign: cfg.StackAlignment,
89 isTypedPass: cfg.IsFeatureEnabled(config.FeatTyped),
90 cfg: cfg,
91+ switchStack: make([]*switchContext, 0),
92 }
93 }
94
95@@ -113,28 +118,28 @@ func (ctx *Context) findSymbolInCurrentScope(name string) *symbol {
96 }
97
98 func (ctx *Context) addSymbol(name string, symType symbolType, bxType *ast.BxType, isVector bool, node *ast.Node) *symbol {
99- var qbeName string
100+ var irVal ir.Value
101 switch symType {
102 case symVar:
103- if ctx.currentScope.Parent == nil {
104- qbeName = "$" + name
105- } else {
106- qbeName = fmt.Sprintf("%%.%s_%d", name, ctx.tempCount)
107+ if ctx.currentScope.Parent == nil { // Global
108+ irVal = &ir.Global{Name: name}
109+ } else { // Local
110+ irVal = &ir.Temporary{Name: name, ID: ctx.tempCount}
111 ctx.tempCount++
112 }
113 case symFunc, symExtrn:
114- qbeName = "$" + name
115+ irVal = &ir.Global{Name: name}
116 case symLabel:
117- qbeName = "@" + name
118+ irVal = &ir.Label{Name: name}
119 case symType:
120- qbeName = ":" + name
121+ // Types don't have a direct IR value in this model
122 }
123
124 sym := &symbol{
125 Name: name,
126 Type: symType,
127 BxType: bxType,
128- QbeName: qbeName,
129+ IRVal: irVal,
130 IsVector: isVector,
131 Next: ctx.currentScope.Symbols,
132 Node: node,
133@@ -143,32 +148,64 @@ func (ctx *Context) addSymbol(name string, symType symbolType, bxType *ast.BxTyp
134 return sym
135 }
136
137-func (ctx *Context) newTemp() string {
138- name := fmt.Sprintf("%%.t%d", ctx.tempCount)
139+func (ctx *Context) newTemp() *ir.Temporary {
140+ t := &ir.Temporary{ID: ctx.tempCount}
141 ctx.tempCount++
142- return name
143+ return t
144 }
145
146-func (ctx *Context) newLabel() string {
147- name := fmt.Sprintf("@L%d", ctx.labelCount)
148+func (ctx *Context) newLabel() *ir.Label {
149+ l := &ir.Label{Name: fmt.Sprintf("L%d", ctx.labelCount)}
150 ctx.labelCount++
151- return name
152+ return l
153+}
154+
155+func (ctx *Context) startBlock(label *ir.Label) {
156+ block := &ir.BasicBlock{Label: label}
157+ ctx.currentFunc.Blocks = append(ctx.currentFunc.Blocks, block)
158+ ctx.currentBlock = block
159+}
160+
161+func (ctx *Context) addInstr(instr *ir.Instruction) {
162+ if ctx.currentBlock == nil {
163+ ctx.startBlock(ctx.newLabel())
164+ }
165+ ctx.currentBlock.Instructions = append(ctx.currentBlock.Instructions, instr)
166 }
167
168-func (ctx *Context) writeLabel(label string) {
169- ctx.out.WriteString(label + "\n")
170- ctx.currentBlockLabel = label
171+func (ctx *Context) addString(value string) ir.Value {
172+ if label, ok := ctx.prog.Strings[value]; ok {
173+ return &ir.Global{Name: label}
174+ }
175+ label := fmt.Sprintf("str%d", len(ctx.prog.Strings))
176+ ctx.prog.Strings[value] = label
177+ return &ir.Global{Name: label}
178 }
179
180-func (ctx *Context) addString(value string) string {
181- for _, entry := range ctx.strings {
182- if entry.Value == value {
183- return "$" + entry.Label
184+// evalConstExpr evaluates a compile-time constant expression node to an integer
185+func (ctx *Context) evalConstExpr(node *ast.Node) (int64, bool) {
186+ if node == nil {
187+ return 0, false
188+ }
189+ folded := ast.FoldConstants(node)
190+ if folded.Type == ast.Number {
191+ return folded.Data.(ast.NumberNode).Value, true
192+ }
193+ if folded.Type == ast.Ident {
194+ identName := folded.Data.(ast.IdentNode).Name
195+ sym := ctx.findSymbol(identName)
196+ if sym != nil && sym.Node != nil && sym.Node.Type == ast.VarDecl {
197+ decl := sym.Node.Data.(ast.VarDeclNode)
198+ if len(decl.InitList) == 1 {
199+ // Prevent infinite recursion on `auto x = x;`
200+ if decl.InitList[0] == node {
201+ return 0, false
202+ }
203+ return ctx.evalConstExpr(decl.InitList[0])
204+ }
205 }
206 }
207- label := fmt.Sprintf("str%d", len(ctx.strings))
208- ctx.strings = append(ctx.strings, stringEntry{Value: value, Label: label})
209- return "$" + label
210+ return 0, false
211 }
212
213 func (ctx *Context) getSizeof(typ *ast.BxType) int64 {
214@@ -184,27 +221,8 @@ func (ctx *Context) getSizeof(typ *ast.BxType) int64 {
215 elemSize := ctx.getSizeof(typ.Base)
216 var arrayLen int64 = 1
217 if typ.ArraySize != nil {
218- folded := ast.FoldConstants(typ.ArraySize)
219- if folded.Type == ast.Number {
220- arrayLen = folded.Data.(ast.NumberNode).Value
221- } else if folded.Type == ast.Ident {
222- identName := folded.Data.(ast.IdentNode).Name
223- sym := ctx.findSymbol(identName)
224- if sym == nil || sym.Node == nil || sym.Node.Type != ast.VarDecl {
225- util.Error(typ.ArraySize.Tok, "Array size '%s' is not a constant variable.", identName)
226- return elemSize
227- }
228- decl := sym.Node.Data.(ast.VarDeclNode)
229- if len(decl.InitList) != 1 {
230- util.Error(typ.ArraySize.Tok, "Array size '%s' is not a simple constant.", identName)
231- return elemSize
232- }
233- constVal := ast.FoldConstants(decl.InitList[0])
234- if constVal.Type != ast.Number {
235- util.Error(typ.ArraySize.Tok, "Array size '%s' is not a constant number.", identName)
236- return elemSize
237- }
238- arrayLen = constVal.Data.(ast.NumberNode).Value
239+ if val, ok := ctx.evalConstExpr(typ.ArraySize); ok {
240+ arrayLen = val
241 } else {
242 util.Error(typ.ArraySize.Tok, "Array size must be a constant expression.")
243 }
244@@ -247,189 +265,8 @@ func (ctx *Context) getSizeof(typ *ast.BxType) int64 {
245 return int64(ctx.wordSize)
246 }
247
248-func (ctx *Context) getQbeType(typ *ast.BxType) string {
249- if typ == nil || typ.Kind == ast.TYPE_UNTYPED {
250- return ctx.wordType
251- }
252- switch typ.Kind {
253- case ast.TYPE_VOID:
254- return ""
255- case ast.TYPE_POINTER, ast.TYPE_ARRAY:
256- return ctx.wordType
257- case ast.TYPE_FLOAT:
258- switch typ.Name {
259- case "float", "float32":
260- return "s"
261- case "float64":
262- return "d"
263- default:
264- return "s"
265- }
266- case ast.TYPE_PRIMITIVE:
267- switch typ.Name {
268- case "int", "uint", "string":
269- return ctx.wordType
270- case "int64", "uint64":
271- return "l"
272- case "int32", "uint32":
273- return "w"
274- case "int16", "uint16":
275- return "h"
276- case "byte", "bool", "int8", "uint8":
277- return "b"
278- default:
279- if sym := ctx.findSymbol(typ.Name); sym != nil {
280- return ctx.getQbeType(sym.BxType)
281- }
282- return ctx.wordType
283- }
284- case ast.TYPE_STRUCT:
285- return ctx.wordType
286- }
287- return ctx.wordType
288-}
289-
290-func getOpStr(op token.Type) string {
291- switch op {
292- case token.Plus, token.PlusEq, token.EqPlus:
293- return "add"
294- case token.Minus, token.MinusEq, token.EqMinus:
295- return "sub"
296- case token.Star, token.StarEq, token.EqStar:
297- return "mul"
298- case token.Slash, token.SlashEq, token.EqSlash:
299- return "div"
300- case token.Rem, token.RemEq, token.EqRem:
301- return "rem"
302- case token.And, token.AndEq, token.EqAnd:
303- return "and"
304- case token.Or, token.OrEq, token.EqOr:
305- return "or"
306- case token.Xor, token.XorEq, token.EqXor:
307- return "xor"
308- case token.Shl, token.ShlEq, token.EqShl:
309- return "shl"
310- case token.Shr, token.ShrEq, token.EqShr:
311- return "shr"
312- default:
313- return ""
314- }
315-}
316-
317-func (ctx *Context) getCmpOpStr(op token.Type, operandType string) string {
318- isFloat := operandType == "s" || operandType == "d"
319- isSigned := true
320-
321- var cmpType string
322-
323- switch op {
324- case token.EqEq:
325- cmpType = "eq"
326- case token.Neq:
327- cmpType = "ne"
328- case token.Lt:
329- if isFloat {
330- cmpType = "lt"
331- } else if isSigned {
332- cmpType = "slt"
333- } else {
334- cmpType = "ult"
335- }
336- case token.Gt:
337- if isFloat {
338- cmpType = "gt"
339- } else if isSigned {
340- cmpType = "sgt"
341- } else {
342- cmpType = "ugt"
343- }
344- case token.Lte:
345- if isFloat {
346- cmpType = "le"
347- } else if isSigned {
348- cmpType = "sle"
349- } else {
350- cmpType = "ule"
351- }
352- case token.Gte:
353- if isFloat {
354- cmpType = "ge"
355- } else if isSigned {
356- cmpType = "sge"
357- } else {
358- cmpType = "uge"
359- }
360- default:
361- return ""
362- }
363-
364- return "c" + cmpType + operandType
365-}
366-
367-func (ctx *Context) getLoadInstruction(typ *ast.BxType) (inst, resType string) {
368- qbeType := ctx.getQbeType(typ)
369- switch qbeType {
370- case "b":
371- return "loadub", ctx.wordType
372- case "h":
373- return "loaduh", ctx.wordType
374- case "w":
375- return "loadw", ctx.wordType
376- case "l":
377- return "loadl", "l"
378- case "s":
379- return "loads", "s"
380- case "d":
381- return "loadd", "d"
382- default:
383- return "load" + qbeType, qbeType
384- }
385-}
386-
387-func (ctx *Context) getStoreInstruction(typ *ast.BxType) string {
388- qbeType := ctx.getQbeType(typ)
389- switch qbeType {
390- case "b":
391- return "storeb"
392- case "h":
393- return "storeh"
394- case "w":
395- return "storew"
396- case "l":
397- return "storel"
398- case "s":
399- return "stores"
400- case "d":
401- return "stored"
402- default:
403- return "store" + qbeType
404- }
405-}
406-
407-func (ctx *Context) getAllocInstruction() string {
408- switch ctx.stackAlign {
409- case 16:
410- return "alloc16"
411- case 8:
412- return "alloc8"
413- default:
414- return "alloc4"
415- }
416-}
417-
418-func (ctx *Context) genLoad(addr string, typ *ast.BxType) string {
419- res := ctx.newTemp()
420- loadInst, resType := ctx.getLoadInstruction(typ)
421- ctx.out.WriteString(fmt.Sprintf("\t%s =%s %s %s\n", res, resType, loadInst, addr))
422- return res
423-}
424-
425-func (ctx *Context) genStore(addr, value string, typ *ast.BxType) {
426- storeInst := ctx.getStoreInstruction(typ)
427- ctx.out.WriteString(fmt.Sprintf("\t%s %s, %s\n", storeInst, value, addr))
428-}
429-
430-func (ctx *Context) Generate(root *ast.Node) (qbeIR, inlineAsm string) {
431+// GenerateIR translates the entire AST into an IR program
432+func (ctx *Context) GenerateIR(root *ast.Node) (*ir.Program, string) {
433 ctx.collectGlobals(root)
434 ctx.collectStrings(root)
435 if !ctx.isTypedPass {
436@@ -437,15 +274,79 @@ func (ctx *Context) Generate(root *ast.Node) (qbeIR, inlineAsm string) {
437 }
438 ctx.codegenStmt(root)
439
440- if len(ctx.strings) > 0 {
441- ctx.out.WriteString("\n# --- Data Section ---\n")
442- for _, s := range ctx.strings {
443- escaped := strconv.Quote(s.Value)
444- ctx.out.WriteString(fmt.Sprintf("data $%s = { b %s, b 0 }\n", s.Label, escaped))
445- }
446+ ctx.prog.BackendTempCount = ctx.tempCount
447+ return ctx.prog, ctx.inlineAsm
448+}
449+
450+// walkAST provides a generic way to traverse the AST
451+func walkAST(node *ast.Node, visitor func(n *ast.Node)) {
452+ if node == nil {
453+ return
454 }
455+ visitor(node)
456
457- return ctx.out.String(), ctx.asmOut.String()
458+ switch d := node.Data.(type) {
459+ case ast.AssignNode:
460+ walkAST(d.Lhs, visitor)
461+ walkAST(d.Rhs, visitor)
462+ case ast.BinaryOpNode:
463+ walkAST(d.Left, visitor)
464+ walkAST(d.Right, visitor)
465+ case ast.UnaryOpNode:
466+ walkAST(d.Expr, visitor)
467+ case ast.PostfixOpNode:
468+ walkAST(d.Expr, visitor)
469+ case ast.IndirectionNode:
470+ walkAST(d.Expr, visitor)
471+ case ast.AddressOfNode:
472+ walkAST(d.LValue, visitor)
473+ case ast.TernaryNode:
474+ walkAST(d.Cond, visitor)
475+ walkAST(d.ThenExpr, visitor)
476+ walkAST(d.ElseExpr, visitor)
477+ case ast.SubscriptNode:
478+ walkAST(d.Array, visitor)
479+ walkAST(d.Index, visitor)
480+ case ast.FuncCallNode:
481+ walkAST(d.FuncExpr, visitor)
482+ for _, arg := range d.Args {
483+ walkAST(arg, visitor)
484+ }
485+ case ast.FuncDeclNode:
486+ walkAST(d.Body, visitor)
487+ case ast.VarDeclNode:
488+ for _, init := range d.InitList {
489+ walkAST(init, visitor)
490+ }
491+ walkAST(d.SizeExpr, visitor)
492+ case ast.MultiVarDeclNode:
493+ for _, decl := range d.Decls {
494+ walkAST(decl, visitor)
495+ }
496+ case ast.IfNode:
497+ walkAST(d.Cond, visitor)
498+ walkAST(d.ThenBody, visitor)
499+ walkAST(d.ElseBody, visitor)
500+ case ast.WhileNode:
501+ walkAST(d.Cond, visitor)
502+ walkAST(d.Body, visitor)
503+ case ast.ReturnNode:
504+ walkAST(d.Expr, visitor)
505+ case ast.BlockNode:
506+ for _, s := range d.Stmts {
507+ walkAST(s, visitor)
508+ }
509+ case ast.SwitchNode:
510+ walkAST(d.Expr, visitor)
511+ walkAST(d.Body, visitor)
512+ case ast.CaseNode:
513+ walkAST(d.Value, visitor)
514+ walkAST(d.Body, visitor)
515+ case ast.DefaultNode:
516+ walkAST(d.Body, visitor)
517+ case ast.LabelNode:
518+ walkAST(d.Stmt, visitor)
519+ }
520 }
521
522 func (ctx *Context) collectGlobals(node *ast.Node) {
523@@ -501,6 +402,16 @@ func (ctx *Context) collectGlobals(node *ast.Node) {
524 name := nameNode.Data.(ast.IdentNode).Name
525 if ctx.findSymbolInCurrentScope(name) == nil {
526 ctx.addSymbol(name, symExtrn, ast.TypeUntyped, false, nameNode)
527+ isAlreadyExtrn := false
528+ for _, extrnName := range ctx.prog.ExtrnFuncs {
529+ if extrnName == name {
530+ isAlreadyExtrn = true
531+ break
532+ }
533+ }
534+ if !isAlreadyExtrn {
535+ ctx.prog.ExtrnFuncs = append(ctx.prog.ExtrnFuncs, name)
536+ }
537 }
538 }
539 case ast.TypeDecl:
540@@ -514,12 +425,10 @@ func (ctx *Context) collectGlobals(node *ast.Node) {
541 func (ctx *Context) findByteArrays(root *ast.Node) {
542 for {
543 changedInPass := false
544- var astWalker func(*ast.Node)
545- astWalker = func(n *ast.Node) {
546+ visitor := func(n *ast.Node) {
547 if n == nil {
548 return
549 }
550-
551 switch n.Type {
552 case ast.VarDecl:
553 d := n.Data.(ast.VarDeclNode)
554@@ -530,97 +439,31 @@ func (ctx *Context) findByteArrays(root *ast.Node) {
555 changedInPass = true
556 }
557 }
558-
559 case ast.Assign:
560 d := n.Data.(ast.AssignNode)
561- if d.Lhs.Type == ast.Ident {
562- lhsSym := ctx.findSymbol(d.Lhs.Data.(ast.IdentNode).Name)
563- if lhsSym != nil && !lhsSym.IsByteArray {
564- rhsIsByteArray := false
565- rhsNode := d.Rhs
566- switch rhsNode.Type {
567- case ast.String:
568- rhsIsByteArray = true
569- case ast.Ident:
570- rhsSym := ctx.findSymbol(rhsNode.Data.(ast.IdentNode).Name)
571- if rhsSym != nil && rhsSym.IsByteArray {
572- rhsIsByteArray = true
573- }
574- }
575-
576- if rhsIsByteArray {
577- lhsSym.IsByteArray = true
578- changedInPass = true
579- }
580- }
581+ if d.Lhs.Type != ast.Ident {
582+ return
583 }
584- }
585-
586- switch d := n.Data.(type) {
587- case ast.AssignNode:
588- astWalker(d.Lhs)
589- astWalker(d.Rhs)
590- case ast.BinaryOpNode:
591- astWalker(d.Left)
592- astWalker(d.Right)
593- case ast.UnaryOpNode:
594- astWalker(d.Expr)
595- case ast.PostfixOpNode:
596- astWalker(d.Expr)
597- case ast.IndirectionNode:
598- astWalker(d.Expr)
599- case ast.AddressOfNode:
600- astWalker(d.LValue)
601- case ast.TernaryNode:
602- astWalker(d.Cond)
603- astWalker(d.ThenExpr)
604- astWalker(d.ElseExpr)
605- case ast.SubscriptNode:
606- astWalker(d.Array)
607- astWalker(d.Index)
608- case ast.FuncCallNode:
609- astWalker(d.FuncExpr)
610- for _, arg := range d.Args {
611- astWalker(arg)
612+ lhsSym := ctx.findSymbol(d.Lhs.Data.(ast.IdentNode).Name)
613+ if lhsSym == nil || lhsSym.IsByteArray {
614+ return
615 }
616- case ast.FuncDeclNode:
617- astWalker(d.Body)
618- case ast.VarDeclNode:
619- for _, init := range d.InitList {
620- astWalker(init)
621- }
622- astWalker(d.SizeExpr)
623- case ast.MultiVarDeclNode:
624- for _, decl := range d.Decls {
625- astWalker(decl)
626+ rhsIsByteArray := false
627+ switch d.Rhs.Type {
628+ case ast.String:
629+ rhsIsByteArray = true
630+ case ast.Ident:
631+ if rhsSym := ctx.findSymbol(d.Rhs.Data.(ast.IdentNode).Name); rhsSym != nil && rhsSym.IsByteArray {
632+ rhsIsByteArray = true
633+ }
634 }
635- case ast.IfNode:
636- astWalker(d.Cond)
637- astWalker(d.ThenBody)
638- astWalker(d.ElseBody)
639- case ast.WhileNode:
640- astWalker(d.Cond)
641- astWalker(d.Body)
642- case ast.ReturnNode:
643- astWalker(d.Expr)
644- case ast.BlockNode:
645- for _, s := range d.Stmts {
646- astWalker(s)
647+ if rhsIsByteArray {
648+ lhsSym.IsByteArray = true
649+ changedInPass = true
650 }
651- case ast.SwitchNode:
652- astWalker(d.Expr)
653- astWalker(d.Body)
654- case ast.CaseNode:
655- astWalker(d.Value)
656- astWalker(d.Body)
657- case ast.DefaultNode:
658- astWalker(d.Body)
659- case ast.LabelNode:
660- astWalker(d.Stmt)
661 }
662 }
663-
664- astWalker(root)
665+ walkAST(root, visitor)
666 if !changedInPass {
667 break
668 }
669@@ -628,84 +471,29 @@ func (ctx *Context) findByteArrays(root *ast.Node) {
670 }
671
672 func (ctx *Context) collectStrings(root *ast.Node) {
673- var walk func(*ast.Node)
674- walk = func(n *ast.Node) {
675- if n == nil {
676- return
677- }
678- if n.Type == ast.String {
679+ walkAST(root, func(n *ast.Node) {
680+ if n != nil && n.Type == ast.String {
681 ctx.addString(n.Data.(ast.StringNode).Value)
682 }
683- switch d := n.Data.(type) {
684- case ast.AssignNode:
685- walk(d.Lhs)
686- walk(d.Rhs)
687- case ast.BinaryOpNode:
688- walk(d.Left)
689- walk(d.Right)
690- case ast.UnaryOpNode:
691- walk(d.Expr)
692- case ast.PostfixOpNode:
693- walk(d.Expr)
694- case ast.IndirectionNode:
695- walk(d.Expr)
696- case ast.AddressOfNode:
697- walk(d.LValue)
698- case ast.TernaryNode:
699- walk(d.Cond)
700- walk(d.ThenExpr)
701- walk(d.ElseExpr)
702- case ast.SubscriptNode:
703- walk(d.Array)
704- walk(d.Index)
705- case ast.FuncCallNode:
706- walk(d.FuncExpr)
707- for _, arg := range d.Args {
708- walk(arg)
709- }
710- case ast.FuncDeclNode:
711- walk(d.Body)
712- case ast.VarDeclNode:
713- for _, init := range d.InitList {
714- walk(init)
715- }
716- walk(d.SizeExpr)
717- case ast.MultiVarDeclNode:
718- for _, decl := range d.Decls {
719- walk(decl)
720- }
721- case ast.IfNode:
722- walk(d.Cond)
723- walk(d.ThenBody)
724- walk(d.ElseBody)
725- case ast.WhileNode:
726- walk(d.Cond)
727- walk(d.Body)
728- case ast.ReturnNode:
729- walk(d.Expr)
730- case ast.BlockNode:
731- for _, s := range d.Stmts {
732- walk(s)
733- }
734- case ast.SwitchNode:
735- walk(d.Expr)
736- walk(d.Body)
737- case ast.CaseNode:
738- walk(d.Value)
739- walk(d.Body)
740- case ast.DefaultNode:
741- walk(d.Body)
742- case ast.LabelNode:
743- walk(d.Stmt)
744- }
745- }
746- walk(root)
747+ })
748+}
749+
750+func (ctx *Context) genLoad(addr ir.Value, typ *ast.BxType) ir.Value {
751+ res := ctx.newTemp()
752+ loadType := ir.GetType(typ, ctx.wordSize)
753+ ctx.addInstr(&ir.Instruction{Op: ir.OpLoad, Typ: loadType, Result: res, Args: []ir.Value{addr}})
754+ return res
755 }
756
757-func (ctx *Context) codegenLvalue(node *ast.Node) string {
758+func (ctx *Context) genStore(addr, value ir.Value, typ *ast.BxType) {
759+ storeType := ir.GetType(typ, ctx.wordSize)
760+ ctx.addInstr(&ir.Instruction{Op: ir.OpStore, Typ: storeType, Args: []ir.Value{value, addr}})
761+}
762+
763+func (ctx *Context) codegenLvalue(node *ast.Node) ir.Value {
764 if node == nil {
765 util.Error(token.Token{}, "Internal error: null l-value node in codegen")
766- return ""
767+ return nil
768 }
769 switch node.Type {
770 case ast.Ident:
771@@ -718,436 +506,93 @@ func (ctx *Context) codegenLvalue(node *ast.Node) string {
772
773 if sym.Type == symFunc {
774 util.Error(node.Tok, "Cannot assign to function '%s'.", name)
775- return ""
776+ return nil
777 }
778 if sym.BxType != nil && sym.BxType.Kind == ast.TYPE_ARRAY {
779- return sym.QbeName
780+ return sym.IRVal
781 }
782 if sym.IsVector && sym.Node != nil && sym.Node.Type == ast.VarDecl {
783 d := sym.Node.Data.(ast.VarDeclNode)
784 if !d.IsBracketed && len(d.InitList) <= 1 && d.Type == nil {
785 util.Error(node.Tok, "Cannot assign to '%s', it is a constant.", name)
786- return ""
787+ return nil
788 }
789 }
790-
791- return sym.QbeName
792+ return sym.IRVal
793
794 case ast.Indirection:
795- res, _, _ := ctx.codegenExpr(node.Data.(ast.IndirectionNode).Expr)
796+ res, _ := ctx.codegenExpr(node.Data.(ast.IndirectionNode).Expr)
797 return res
798
799 case ast.Subscript:
800- d := node.Data.(ast.SubscriptNode)
801- arrayBasePtr, _, _ := ctx.codegenExpr(d.Array)
802- indexVal, _, _ := ctx.codegenExpr(d.Index)
803-
804- var scale int64 = int64(ctx.wordSize)
805- if d.Array.Typ != nil {
806- if d.Array.Typ.Kind == ast.TYPE_POINTER || d.Array.Typ.Kind == ast.TYPE_ARRAY {
807- if d.Array.Typ.Base != nil {
808- scale = ctx.getSizeof(d.Array.Typ.Base)
809- }
810- }
811- } else {
812- if !ctx.isTypedPass && d.Array.Type == ast.Ident {
813- sym := ctx.findSymbol(d.Array.Data.(ast.IdentNode).Name)
814- if sym != nil && sym.IsByteArray {
815- scale = 1
816- }
817- }
818- }
819-
820- var scaledIndex string
821- if scale > 1 {
822- scaledIndex = ctx.newTemp()
823- ctx.out.WriteString(fmt.Sprintf("\t%s =%s mul %s, %d\n", scaledIndex, ctx.wordType, indexVal, scale))
824- } else {
825- scaledIndex = indexVal
826- }
827-
828- resultAddr := ctx.newTemp()
829- ctx.out.WriteString(fmt.Sprintf("\t%s =%s add %s, %s\n", resultAddr, ctx.wordType, arrayBasePtr, scaledIndex))
830- return resultAddr
831+ return ctx.codegenSubscriptAddr(node)
832
833 default:
834 util.Error(node.Tok, "Expression is not a valid l-value.")
835- return ""
836+ return nil
837 }
838 }
839
840-func (ctx *Context) codegenLogicalCond(node *ast.Node, trueL, falseL string) {
841+func (ctx *Context) codegenLogicalCond(node *ast.Node, trueL, falseL *ir.Label) {
842 if node.Type == ast.BinaryOp {
843 d := node.Data.(ast.BinaryOpNode)
844 if d.Op == token.OrOr {
845 newFalseL := ctx.newLabel()
846 ctx.codegenLogicalCond(d.Left, trueL, newFalseL)
847- ctx.writeLabel(newFalseL)
848+ ctx.startBlock(newFalseL)
849 ctx.codegenLogicalCond(d.Right, trueL, falseL)
850 return
851 }
852 if d.Op == token.AndAnd {
853 newTrueL := ctx.newLabel()
854 ctx.codegenLogicalCond(d.Left, newTrueL, falseL)
855- ctx.writeLabel(newTrueL)
856+ ctx.startBlock(newTrueL)
857 ctx.codegenLogicalCond(d.Right, trueL, falseL)
858 return
859 }
860 }
861
862- condVal, _, _ := ctx.codegenExpr(node)
863- ctx.out.WriteString(fmt.Sprintf("\tjnz %s, %s, %s\n", condVal, trueL, falseL))
864+ condVal, _ := ctx.codegenExpr(node)
865+ ctx.addInstr(&ir.Instruction{Op: ir.OpJnz, Args: []ir.Value{condVal, trueL, falseL}})
866+ ctx.currentBlock = nil // This block is terminated
867 }
868
869-func (ctx *Context) codegenExpr(node *ast.Node) (result, predLabel string, terminates bool) {
870+func (ctx *Context) codegenExpr(node *ast.Node) (result ir.Value, terminates bool) {
871 if node == nil {
872- return "0", ctx.currentBlockLabel, false
873+ return &ir.Const{Value: 0}, false
874 }
875- startBlockLabel := ctx.currentBlockLabel
876
877 switch node.Type {
878 case ast.Number:
879- return fmt.Sprintf("%d", node.Data.(ast.NumberNode).Value), startBlockLabel, false
880+ return &ir.Const{Value: node.Data.(ast.NumberNode).Value}, false
881 case ast.String:
882- return ctx.addString(node.Data.(ast.StringNode).Value), startBlockLabel, false
883-
884+ return ctx.addString(node.Data.(ast.StringNode).Value), false
885 case ast.Ident:
886- name := node.Data.(ast.IdentNode).Name
887- sym := ctx.findSymbol(name)
888-
889- if sym == nil {
890- util.Warn(ctx.cfg, config.WarnImplicitDecl, node.Tok, "Implicit declaration of function '%s'", name)
891- sym = ctx.addSymbol(name, symFunc, ast.TypeUntyped, false, node)
892- return sym.QbeName, startBlockLabel, false
893- }
894-
895- switch sym.Type {
896- case symFunc:
897- return sym.QbeName, startBlockLabel, false
898- case symExtrn:
899- isCall := node.Parent != nil && node.Parent.Type == ast.FuncCall && node.Parent.Data.(ast.FuncCallNode).FuncExpr == node
900- if isCall {
901- return sym.QbeName, startBlockLabel, false
902- } else {
903- // It's an external variable (e.g., stderr), which is a pointer. Load its value.
904- return ctx.genLoad(sym.QbeName, sym.BxType), startBlockLabel, false
905- }
906- }
907-
908- isArr := sym.IsVector || (sym.BxType != nil && sym.BxType.Kind == ast.TYPE_ARRAY)
909- isPtr := sym.BxType != nil && sym.BxType.Kind == ast.TYPE_POINTER
910-
911- if isArr {
912- isParam := false
913- if ctx.currentFunc != nil {
914- for _, p := range ctx.currentFunc.Params {
915- var pName string
916- if p.Type == ast.Ident {
917- pName = p.Data.(ast.IdentNode).Name
918- } else {
919- pName = p.Data.(ast.VarDeclNode).Name
920- }
921- if pName == name {
922- isParam = true
923- break
924- }
925- }
926- }
927- if isParam {
928- return ctx.genLoad(sym.QbeName, sym.BxType), startBlockLabel, false
929- }
930- return sym.QbeName, startBlockLabel, false
931- }
932-
933- if isPtr {
934- return ctx.genLoad(sym.QbeName, sym.BxType), startBlockLabel, false
935- }
936-
937- return ctx.genLoad(sym.QbeName, sym.BxType), startBlockLabel, false
938-
939+ return ctx.codegenIdent(node)
940 case ast.Assign:
941- d := node.Data.(ast.AssignNode)
942- lvalAddr := ctx.codegenLvalue(node.Data.(ast.AssignNode).Lhs)
943- var rval string
944-
945- if d.Op == token.Eq {
946- rval, _, _ = ctx.codegenExpr(d.Rhs)
947- } else {
948- currentLvalVal := ctx.genLoad(lvalAddr, d.Lhs.Typ)
949- rhsVal, _, _ := ctx.codegenExpr(d.Rhs)
950- opStr := getOpStr(d.Op)
951- rval = ctx.newTemp()
952- valType := ctx.getQbeType(d.Lhs.Typ)
953- if valType == "b" || valType == "h" {
954- valType = ctx.wordType
955- }
956- ctx.out.WriteString(fmt.Sprintf("\t%s =%s %s %s, %s\n", rval, valType, opStr, currentLvalVal, rhsVal))
957- }
958-
959- ctx.genStore(lvalAddr, rval, d.Lhs.Typ)
960- return rval, startBlockLabel, false
961-
962+ return ctx.codegenAssign(node)
963 case ast.BinaryOp:
964- d := node.Data.(ast.BinaryOpNode)
965- if d.Op == token.OrOr || d.Op == token.AndAnd {
966- res := ctx.newTemp()
967- trueL, falseL, endL := ctx.newLabel(), ctx.newLabel(), ctx.newLabel()
968- ctx.codegenLogicalCond(node, trueL, falseL)
969- ctx.writeLabel(trueL)
970- truePred := ctx.currentBlockLabel
971- ctx.out.WriteString(fmt.Sprintf("\tjmp %s\n", endL))
972- ctx.writeLabel(falseL)
973- falsePred := ctx.currentBlockLabel
974- ctx.out.WriteString(fmt.Sprintf("\tjmp %s\n", endL))
975- ctx.writeLabel(endL)
976- ctx.out.WriteString(fmt.Sprintf("\t%s =%s phi %s 1, %s 0\n", res, ctx.wordType, truePred, falsePred))
977- return res, endL, false
978- }
979-
980- l, _, _ := ctx.codegenExpr(d.Left)
981- r, _, _ := ctx.codegenExpr(d.Right)
982- res := ctx.newTemp()
983-
984- lType, rType := ctx.getQbeType(d.Left.Typ), ctx.getQbeType(d.Right.Typ)
985- opType := ctx.wordType
986- if lType == "s" || lType == "d" {
987- opType = lType
988- } else if rType == "s" || rType == "d" {
989- opType = rType
990- }
991-
992- if opStr := getOpStr(d.Op); opStr != "" {
993- ctx.out.WriteString(fmt.Sprintf("\t%s =%s %s %s, %s\n", res, opType, opStr, l, r))
994- } else if cmpOpStr := ctx.getCmpOpStr(d.Op, opType); cmpOpStr != "" {
995- ctx.out.WriteString(fmt.Sprintf("\t%s =%s %s %s, %s\n", res, ctx.wordType, cmpOpStr, l, r))
996- } else {
997- util.Error(node.Tok, "Invalid binary operator token %d", d.Op)
998- }
999- return res, startBlockLabel, false
1000-
1001+ return ctx.codegenBinaryOp(node)
1002 case ast.UnaryOp:
1003- d := node.Data.(ast.UnaryOpNode)
1004- res := ctx.newTemp()
1005- switch d.Op {
1006- case token.Minus:
1007- val, _, _ := ctx.codegenExpr(d.Expr)
1008- valType := ctx.getQbeType(d.Expr.Typ)
1009- if valType == "s" || valType == "d" {
1010- ctx.out.WriteString(fmt.Sprintf("\t%s =%s neg %s\n", res, valType, val))
1011- } else {
1012- ctx.out.WriteString(fmt.Sprintf("\t%s =%s sub 0, %s\n", res, valType, val))
1013- }
1014- case token.Plus:
1015- val, _, _ := ctx.codegenExpr(d.Expr)
1016- return val, startBlockLabel, false
1017- case token.Not:
1018- val, _, _ := ctx.codegenExpr(d.Expr)
1019- ctx.out.WriteString(fmt.Sprintf("\t%s =%s ceq%s %s, 0\n", res, ctx.wordType, ctx.wordType, val))
1020- case token.Complement:
1021- val, _, _ := ctx.codegenExpr(d.Expr)
1022- ctx.out.WriteString(fmt.Sprintf("\t%s =%s xor %s, -1\n", res, ctx.wordType, val))
1023- case token.Inc, token.Dec: // Prefix
1024- lvalAddr := ctx.codegenLvalue(d.Expr)
1025- op := map[token.Type]string{token.Inc: "add", token.Dec: "sub"}[d.Op]
1026- currentVal := ctx.genLoad(lvalAddr, d.Expr.Typ)
1027- valType := ctx.getQbeType(d.Expr.Typ)
1028- if valType == "b" || valType == "h" {
1029- valType = ctx.wordType
1030- }
1031-
1032- var oneConst string
1033- if valType == "s" || valType == "d" {
1034- oneConst = fmt.Sprintf("%s_1.0", valType)
1035- } else {
1036- oneConst = "1"
1037- }
1038- ctx.out.WriteString(fmt.Sprintf("\t%s =%s %s %s, %s\n", res, valType, op, currentVal, oneConst))
1039- ctx.genStore(lvalAddr, res, d.Expr.Typ)
1040- default:
1041- util.Error(node.Tok, "Unsupported unary operator")
1042- }
1043- return res, startBlockLabel, false
1044-
1045+ return ctx.codegenUnaryOp(node)
1046 case ast.PostfixOp:
1047- d := node.Data.(ast.PostfixOpNode)
1048- lvalAddr := ctx.codegenLvalue(d.Expr)
1049- res := ctx.genLoad(lvalAddr, d.Expr.Typ) // Original value
1050-
1051- newVal := ctx.newTemp()
1052- op := map[token.Type]string{token.Inc: "add", token.Dec: "sub"}[d.Op]
1053- valType := ctx.getQbeType(d.Expr.Typ)
1054- if valType == "b" || valType == "h" {
1055- valType = ctx.wordType
1056- }
1057-
1058- var oneConst string
1059- if valType == "s" || valType == "d" {
1060- oneConst = fmt.Sprintf("%s_1.0", valType)
1061- } else {
1062- oneConst = "1"
1063- }
1064- ctx.out.WriteString(fmt.Sprintf("\t%s =%s %s %s, %s\n", newVal, valType, op, res, oneConst))
1065- ctx.genStore(lvalAddr, newVal, d.Expr.Typ)
1066- return res, startBlockLabel, false
1067-
1068+ return ctx.codegenPostfixOp(node)
1069 case ast.Indirection:
1070- exprNode := node.Data.(ast.IndirectionNode).Expr
1071- addr, _, _ := ctx.codegenExpr(exprNode)
1072- loadType := node.Typ
1073- if !ctx.isTypedPass {
1074- if exprNode.Type == ast.Ident {
1075- if sym := ctx.findSymbol(exprNode.Data.(ast.IdentNode).Name); sym != nil && sym.IsByteArray {
1076- loadType = ast.TypeByte
1077- }
1078- }
1079- }
1080- return ctx.genLoad(addr, loadType), startBlockLabel, false
1081-
1082+ return ctx.codegenIndirection(node)
1083 case ast.Subscript:
1084- addr := ctx.codegenLvalue(node)
1085- loadType := node.Typ
1086- if !ctx.isTypedPass && node.Data.(ast.SubscriptNode).Array.Type == ast.Ident {
1087- if sym := ctx.findSymbol(node.Data.(ast.SubscriptNode).Array.Data.(ast.IdentNode).Name); sym != nil && sym.IsByteArray {
1088- loadType = ast.TypeByte
1089- }
1090- }
1091- return ctx.genLoad(addr, loadType), startBlockLabel, false
1092-
1093+ addr := ctx.codegenSubscriptAddr(node)
1094+ return ctx.genLoad(addr, node.Typ), false
1095 case ast.AddressOf:
1096- lvalNode := node.Data.(ast.AddressOfNode).LValue
1097- if lvalNode.Type == ast.Ident {
1098- name := lvalNode.Data.(ast.IdentNode).Name
1099- sym := ctx.findSymbol(name)
1100- if sym != nil {
1101- isTypedArray := sym.BxType != nil && sym.BxType.Kind == ast.TYPE_ARRAY
1102- if sym.Type == symFunc || isTypedArray {
1103- return sym.QbeName, startBlockLabel, false
1104- }
1105- if sym.IsVector {
1106- res, _, _ := ctx.codegenExpr(lvalNode)
1107- return res, startBlockLabel, false
1108- }
1109- }
1110- }
1111- return ctx.codegenLvalue(lvalNode), startBlockLabel, false
1112-
1113+ return ctx.codegenAddressOf(node)
1114 case ast.FuncCall:
1115- d := node.Data.(ast.FuncCallNode)
1116- if d.FuncExpr.Type == ast.Ident {
1117- name := d.FuncExpr.Data.(ast.IdentNode).Name
1118- if sym := ctx.findSymbol(name); sym != nil && sym.Type == symVar && !sym.IsVector {
1119- util.Error(d.FuncExpr.Tok, "'%s' is a variable but is used as a function", name)
1120- }
1121- }
1122-
1123- argVals := make([]string, len(d.Args))
1124- for i := len(d.Args) - 1; i >= 0; i-- {
1125- argVals[i], _, _ = ctx.codegenExpr(d.Args[i])
1126- }
1127- funcVal, _, _ := ctx.codegenExpr(d.FuncExpr)
1128-
1129- isStmt := node.Parent != nil && node.Parent.Type == ast.Block
1130-
1131- res := "0"
1132- callStr := new(strings.Builder)
1133-
1134- var returnQbeType string
1135- var funcSym *symbol
1136- if d.FuncExpr.Type == ast.Ident {
1137- funcSym = ctx.findSymbol(d.FuncExpr.Data.(ast.IdentNode).Name)
1138- }
1139- if funcSym != nil && funcSym.BxType != nil && funcSym.BxType.Kind != ast.TYPE_UNTYPED {
1140- returnQbeType = ctx.getQbeType(funcSym.BxType)
1141- } else {
1142- returnQbeType = ctx.wordType
1143- }
1144-
1145- if !isStmt && returnQbeType != "" {
1146- res = ctx.newTemp()
1147- resultTempType := returnQbeType
1148- if resultTempType == "b" || resultTempType == "h" {
1149- resultTempType = "w"
1150- }
1151- fmt.Fprintf(callStr, "\t%s =%s call %s(", res, resultTempType, funcVal)
1152- } else {
1153- fmt.Fprintf(callStr, "\tcall %s(", funcVal)
1154- }
1155-
1156- for i, argVal := range argVals {
1157- argType := ctx.getQbeType(d.Args[i].Typ)
1158- switch argType {
1159- case "b":
1160- if d.Args[i].Typ != nil && d.Args[i].Typ.Name == "int8" {
1161- argType = "sb"
1162- } else {
1163- argType = "ub"
1164- }
1165- case "h":
1166- if d.Args[i].Typ != nil && d.Args[i].Typ.Name == "uint16" {
1167- argType = "uh"
1168- } else {
1169- argType = "sh"
1170- }
1171- }
1172- fmt.Fprintf(callStr, "%s %s", argType, argVal)
1173- if i < len(argVals)-1 {
1174- callStr.WriteString(", ")
1175- }
1176- }
1177- callStr.WriteString(")\n")
1178- ctx.out.WriteString(callStr.String())
1179- return res, startBlockLabel, false
1180-
1181+ return ctx.codegenFuncCall(node)
1182 case ast.Ternary:
1183- d := node.Data.(ast.TernaryNode)
1184- thenLabel := ctx.newLabel()
1185- endLabel := ctx.newLabel()
1186- res := ctx.newTemp()
1187- elseLabel := ctx.newLabel()
1188-
1189- ctx.codegenLogicalCond(d.Cond, thenLabel, elseLabel)
1190-
1191- ctx.writeLabel(thenLabel)
1192- thenVal, thenPred, thenTerminates := ctx.codegenExpr(d.ThenExpr)
1193- if !thenTerminates {
1194- ctx.out.WriteString(fmt.Sprintf("\tjmp %s\n", endLabel))
1195- }
1196-
1197- ctx.writeLabel(elseLabel)
1198- elseVal, elsePred, elseTerminates := ctx.codegenExpr(d.ElseExpr)
1199- if !elseTerminates {
1200- ctx.out.WriteString(fmt.Sprintf("\tjmp %s\n", endLabel))
1201- }
1202-
1203- if !thenTerminates || !elseTerminates {
1204- ctx.writeLabel(endLabel)
1205- resType := ctx.getQbeType(node.Typ)
1206-
1207- phiArgs := new(strings.Builder)
1208- if !thenTerminates {
1209- fmt.Fprintf(phiArgs, "%s %s", thenPred, thenVal)
1210- }
1211- if !elseTerminates {
1212- if !thenTerminates {
1213- phiArgs.WriteString(", ")
1214- }
1215- fmt.Fprintf(phiArgs, "%s %s", elsePred, elseVal)
1216- }
1217- ctx.out.WriteString(fmt.Sprintf("\t%s =%s phi %s\n", res, resType, phiArgs.String()))
1218- return res, endLabel, thenTerminates && elseTerminates
1219- }
1220-
1221- return "0", endLabel, true
1222+ return ctx.codegenTernary(node)
1223 case ast.AutoAlloc:
1224- d := node.Data.(ast.AutoAllocNode)
1225- sizeVal, _, _ := ctx.codegenExpr(d.Size)
1226- res := ctx.newTemp()
1227- allocInst := "alloc" + strconv.Itoa(ctx.wordSize)
1228- ctx.out.WriteString(fmt.Sprintf("\t%s =%s %s %s\n", res, ctx.wordType, allocInst, sizeVal))
1229- return res, startBlockLabel, false
1230+ return ctx.codegenAutoAlloc(node)
1231 }
1232 util.Error(node.Tok, "Internal error: unhandled expression type in codegen: %v", node.Type)
1233- return "", startBlockLabel, true
1234+ return nil, true
1235 }
1236
1237 func (ctx *Context) codegenStmt(node *ast.Node) (terminates bool) {
1238@@ -1169,6 +614,7 @@ func (ctx *Context) codegenStmt(node *ast.Node) (terminates bool) {
1239 continue
1240 }
1241 blockTerminates = false
1242+ ctx.currentBlock = nil
1243 }
1244 blockTerminates = ctx.codegenStmt(stmt)
1245 }
1246@@ -1180,20 +626,16 @@ func (ctx *Context) codegenStmt(node *ast.Node) (terminates bool) {
1247 case ast.FuncDecl:
1248 ctx.codegenFuncDecl(node)
1249 return false
1250-
1251 case ast.VarDecl:
1252 ctx.codegenVarDecl(node)
1253 return false
1254-
1255 case ast.MultiVarDecl:
1256 for _, decl := range node.Data.(ast.MultiVarDeclNode).Decls {
1257 ctx.codegenVarDecl(decl)
1258 }
1259 return false
1260-
1261 case ast.TypeDecl, ast.Directive:
1262 return false
1263-
1264 case ast.ExtrnDecl:
1265 d := node.Data.(ast.ExtrnDeclNode)
1266 for _, nameNode := range d.Names {
1267@@ -1203,115 +645,52 @@ func (ctx *Context) codegenStmt(node *ast.Node) (terminates bool) {
1268 }
1269 }
1270 return false
1271-
1272 case ast.Return:
1273- if node.Data.(ast.ReturnNode).Expr != nil {
1274- val, _, _ := ctx.codegenExpr(node.Data.(ast.ReturnNode).Expr)
1275- ctx.out.WriteString(fmt.Sprintf("\tret %s\n", val))
1276- } else {
1277- ctx.out.WriteString("\tret\n")
1278- }
1279- return true
1280-
1281+ return ctx.codegenReturn(node)
1282 case ast.If:
1283- d := node.Data.(ast.IfNode)
1284- thenLabel := ctx.newLabel()
1285- endLabel := ctx.newLabel()
1286- elseLabel := endLabel
1287- if d.ElseBody != nil {
1288- elseLabel = ctx.newLabel()
1289- }
1290-
1291- ctx.codegenLogicalCond(d.Cond, thenLabel, elseLabel)
1292-
1293- ctx.writeLabel(thenLabel)
1294- thenTerminates := ctx.codegenStmt(d.ThenBody)
1295- if !thenTerminates {
1296- ctx.out.WriteString(fmt.Sprintf("\tjmp %s\n", endLabel))
1297- }
1298-
1299- var elseTerminates bool
1300- if d.ElseBody != nil {
1301- ctx.writeLabel(elseLabel)
1302- elseTerminates = ctx.codegenStmt(d.ElseBody)
1303- if !elseTerminates {
1304- ctx.out.WriteString(fmt.Sprintf("\tjmp %s\n", endLabel))
1305- }
1306- }
1307- ctx.writeLabel(endLabel)
1308- return thenTerminates && elseTerminates
1309-
1310+ return ctx.codegenIf(node)
1311 case ast.While:
1312- d := node.Data.(ast.WhileNode)
1313- startLabel := ctx.newLabel()
1314- bodyLabel := ctx.newLabel()
1315- endLabel := ctx.newLabel()
1316-
1317- oldBreakLabel := ctx.breakLabel
1318- oldContinueLabel := ctx.continueLabel
1319- ctx.breakLabel = endLabel
1320- ctx.continueLabel = startLabel
1321- defer func() {
1322- ctx.breakLabel = oldBreakLabel
1323- ctx.continueLabel = oldContinueLabel
1324- }()
1325-
1326- ctx.out.WriteString(fmt.Sprintf("\tjmp %s\n", startLabel))
1327- ctx.writeLabel(startLabel)
1328-
1329- ctx.codegenLogicalCond(d.Cond, bodyLabel, endLabel)
1330-
1331- ctx.writeLabel(bodyLabel)
1332- ctx.codegenStmt(d.Body)
1333- ctx.out.WriteString(fmt.Sprintf("\tjmp %s\n", startLabel))
1334-
1335- ctx.writeLabel(endLabel)
1336- return false
1337-
1338+ return ctx.codegenWhile(node)
1339 case ast.Switch:
1340 return ctx.codegenSwitch(node)
1341-
1342 case ast.Label:
1343 d := node.Data.(ast.LabelNode)
1344- labelName := "@" + d.Name
1345- ctx.writeLabel(labelName)
1346+ label := &ir.Label{Name: d.Name}
1347+ if ctx.currentBlock != nil {
1348+ ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{label}})
1349+ }
1350+ ctx.startBlock(label)
1351 return ctx.codegenStmt(d.Stmt)
1352
1353 case ast.Goto:
1354 d := node.Data.(ast.GotoNode)
1355- ctx.out.WriteString(fmt.Sprintf("\tjmp @%s\n", d.Label))
1356+ ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{&ir.Label{Name: d.Label}}})
1357+ ctx.currentBlock = nil
1358 return true
1359
1360 case ast.Break:
1361- if ctx.breakLabel == "" {
1362+ if ctx.breakLabel == nil {
1363 util.Error(node.Tok, "'break' not in a loop or switch.")
1364 }
1365- ctx.out.WriteString(fmt.Sprintf("\tjmp %s\n", ctx.breakLabel))
1366+ ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{ctx.breakLabel}})
1367+ ctx.currentBlock = nil
1368 return true
1369
1370 case ast.Continue:
1371- if ctx.continueLabel == "" {
1372+ if ctx.continueLabel == nil {
1373 util.Error(node.Tok, "'continue' not in a loop.")
1374 }
1375- ctx.out.WriteString(fmt.Sprintf("\tjmp %s\n", ctx.continueLabel))
1376+ ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{ctx.continueLabel}})
1377+ ctx.currentBlock = nil
1378 return true
1379
1380- case ast.Case:
1381- d := node.Data.(ast.CaseNode)
1382- ctx.writeLabel(d.QbeLabel)
1383- return ctx.codegenStmt(d.Body)
1384-
1385- case ast.Default:
1386- d := node.Data.(ast.DefaultNode)
1387- ctx.writeLabel(d.QbeLabel)
1388- return ctx.codegenStmt(d.Body)
1389+ case ast.Case, ast.Default:
1390+ return ctx.codegenCaseOrDefault(node)
1391
1392 default:
1393- if node.Type <= ast.AutoAlloc {
1394- _, _, terminates := ctx.codegenExpr(node)
1395- return terminates
1396- }
1397- return false
1398+ // Any other node type is treated as an expression statement
1399+ _, terminates := ctx.codegenExpr(node)
1400+ return terminates
1401 }
1402 }
1403
1404@@ -1342,9 +721,7 @@ func (ctx *Context) findAllAutosInFunc(node *ast.Node, autoVars *[]autoVarInfo,
1405 } else {
1406 dataSizeInWords = int64(len(varData.InitList))
1407 }
1408- if varData.IsBracketed {
1409- dataSizeInWords++
1410- }
1411+ // Dope vector: 1 word for the pointer to data
1412 size = int64(ctx.wordSize) + dataSizeInWords*int64(ctx.wordSize)
1413 } else {
1414 size = int64(ctx.wordSize)
1415@@ -1383,81 +760,56 @@ func (ctx *Context) codegenFuncDecl(node *ast.Node) {
1416 d := node.Data.(ast.FuncDeclNode)
1417 if d.Body != nil && d.Body.Type == ast.AsmStmt {
1418 asmCode := d.Body.Data.(ast.AsmStmtNode).Code
1419- ctx.asmOut.WriteString(fmt.Sprintf(".globl %s\n%s:\n\t%s\n", d.Name, d.Name, strings.ReplaceAll(asmCode, "\n", "\n\t")))
1420+ ctx.inlineAsm += fmt.Sprintf(".globl %s\n%s:\n\t%s\n", d.Name, d.Name, asmCode)
1421 return
1422 }
1423 if d.Body == nil {
1424 return
1425 }
1426
1427+ fn := &ir.Func{
1428+ Name: d.Name,
1429+ ReturnType: ir.GetType(d.ReturnType, ctx.wordSize),
1430+ HasVarargs: d.HasVarargs,
1431+ }
1432+ ctx.prog.Funcs = append(ctx.prog.Funcs, fn)
1433+
1434 prevFunc := ctx.currentFunc
1435- ctx.currentFunc = &d
1436+ ctx.currentFunc = fn
1437 defer func() { ctx.currentFunc = prevFunc }()
1438
1439 ctx.enterScope()
1440 defer ctx.exitScope()
1441
1442 ctx.tempCount = 0
1443- returnQbeType := ctx.getQbeType(d.ReturnType)
1444- switch returnQbeType {
1445- case "b":
1446- if d.ReturnType != nil && d.ReturnType.Name == "int8" {
1447- returnQbeType = "sb"
1448- } else {
1449- returnQbeType = "ub"
1450- }
1451- case "h":
1452- if d.ReturnType != nil && d.ReturnType.Name == "uint16" {
1453- returnQbeType = "uh"
1454- } else {
1455- returnQbeType = "sh"
1456- }
1457- }
1458-
1459- if returnQbeType != "" {
1460- ctx.out.WriteString(fmt.Sprintf("export function %s $%s(", returnQbeType, d.Name))
1461- } else {
1462- ctx.out.WriteString(fmt.Sprintf("export function $%s(", d.Name))
1463- }
1464+ ctx.startBlock(&ir.Label{Name: "start"})
1465
1466 for i, p := range d.Params {
1467- var paramQbeType string
1468+ var name string
1469+ var typ *ast.BxType
1470 if d.IsTyped {
1471 paramData := p.Data.(ast.VarDeclNode)
1472- paramQbeType = ctx.getQbeType(paramData.Type)
1473- switch paramQbeType {
1474- case "b":
1475- if paramData.Type != nil && paramData.Type.Name == "int8" {
1476- paramQbeType = "sb"
1477- } else {
1478- paramQbeType = "ub"
1479- }
1480- case "h":
1481- if paramData.Type != nil && paramData.Type.Name == "uint16" {
1482- paramQbeType = "uh"
1483- } else {
1484- paramQbeType = "sh"
1485- }
1486- }
1487+ name, typ = paramData.Name, paramData.Type
1488 } else {
1489- paramQbeType = ctx.wordType
1490- }
1491- fmt.Fprintf(&ctx.out, "%s %%p%d", paramQbeType, i)
1492- if i < len(d.Params)-1 {
1493- ctx.out.WriteString(", ")
1494+ name = p.Data.(ast.IdentNode).Name
1495 }
1496+ paramVal := &ir.Temporary{Name: name, ID: i}
1497+ fn.Params = append(fn.Params, &ir.Param{
1498+ Name: name,
1499+ Typ: ir.GetType(typ, ctx.wordSize),
1500+ Val: paramVal,
1501+ })
1502 }
1503
1504- if d.HasVarargs {
1505- if len(d.Params) > 0 {
1506- ctx.out.WriteString(", ")
1507- }
1508- ctx.out.WriteString("...")
1509+ // Determine stack layout
1510+ var paramInfos []autoVarInfo
1511+ for _, p := range d.Params {
1512+ paramInfos = append(paramInfos, autoVarInfo{Node: p, Size: int64(ctx.wordSize)})
1513+ }
1514+ for i, j := 0, len(paramInfos)-1; i < j; i, j = i+1, j-1 {
1515+ paramInfos[i], paramInfos[j] = paramInfos[j], paramInfos[i]
1516 }
1517- ctx.out.WriteString(") {\n")
1518- ctx.writeLabel("@start")
1519
1520- var autoVars []autoVarInfo
1521 definedInFunc := make(map[string]bool)
1522 for _, p := range d.Params {
1523 var name string
1524@@ -1468,66 +820,97 @@ func (ctx *Context) codegenFuncDecl(node *ast.Node) {
1525 }
1526 definedInFunc[name] = true
1527 }
1528+ var autoVars []autoVarInfo
1529 ctx.findAllAutosInFunc(d.Body, &autoVars, definedInFunc)
1530
1531 for i, j := 0, len(autoVars)-1; i < j; i, j = i+1, j-1 {
1532 autoVars[i], autoVars[j] = autoVars[j], autoVars[i]
1533 }
1534
1535- paramInfos := make([]autoVarInfo, len(d.Params))
1536- for i, p := range d.Params {
1537- paramInfos[i] = autoVarInfo{Node: p, Size: int64(ctx.wordSize)}
1538- }
1539- for i, j := 0, len(paramInfos)-1; i < j; i, j = i+1, j-1 {
1540- paramInfos[i], paramInfos[j] = paramInfos[j], paramInfos[i]
1541- }
1542-
1543 allLocals := append(paramInfos, autoVars...)
1544
1545 var totalFrameSize int64
1546- for _, local := range allLocals {
1547- totalFrameSize += local.Size
1548+ for _, av := range allLocals {
1549+ totalFrameSize += av.Size
1550 }
1551
1552+ var framePtr ir.Value
1553 if totalFrameSize > 0 {
1554 align := int64(ctx.stackAlign)
1555 totalFrameSize = (totalFrameSize + align - 1) &^ (align - 1)
1556- ctx.currentFuncFrame = ctx.newTemp()
1557- allocInstr := ctx.getAllocInstruction()
1558- ctx.out.WriteString(fmt.Sprintf("\t%s =%s %s %d\n", ctx.currentFuncFrame, ctx.wordType, allocInstr, totalFrameSize))
1559+ framePtr = ctx.newTemp()
1560+ ctx.addInstr(&ir.Instruction{
1561+ Op: ir.OpAlloc,
1562+ Typ: ir.GetType(nil, ctx.wordSize),
1563+ Result: framePtr,
1564+ Args: []ir.Value{&ir.Const{Value: totalFrameSize}},
1565+ Align: ctx.stackAlign,
1566+ })
1567 }
1568
1569- ctx.enterScope()
1570- defer ctx.exitScope()
1571-
1572 var currentOffset int64
1573 for i, local := range allLocals {
1574- var sym *symbol
1575 isParam := i < len(paramInfos)
1576
1577- if local.Node.Type == ast.Ident {
1578- p := local.Node
1579- sym = ctx.addSymbol(p.Data.(ast.IdentNode).Name, symVar, nil, false, p)
1580- } else {
1581+ var name string
1582+ var typ *ast.BxType
1583+ var isVec bool
1584+ if local.Node.Type == ast.Ident { // Untyped param
1585+ name = local.Node.Data.(ast.IdentNode).Name
1586+ if d.Name == "main" && isParam {
1587+ originalIndex := -1
1588+ for j, p := range d.Params {
1589+ if p == local.Node {
1590+ originalIndex = j
1591+ break
1592+ }
1593+ }
1594+ if originalIndex == 1 {
1595+ isVec = true
1596+ }
1597+ }
1598+ } else { // Typed param or auto var
1599 varData := local.Node.Data.(ast.VarDeclNode)
1600- sym = ctx.addSymbol(varData.Name, symVar, varData.Type, varData.IsVector, local.Node)
1601+ name, typ, isVec = varData.Name, varData.Type, varData.IsVector
1602 }
1603
1604+ sym := ctx.addSymbol(name, symVar, typ, isVec, local.Node)
1605 sym.StackOffset = currentOffset
1606- ctx.out.WriteString(fmt.Sprintf("\t%s =%s add %s, %d\n", sym.QbeName, ctx.wordType, ctx.currentFuncFrame, sym.StackOffset))
1607+
1608+ addr := ctx.newTemp()
1609+ ctx.addInstr(&ir.Instruction{
1610+ Op: ir.OpAdd,
1611+ Typ: ir.GetType(nil, ctx.wordSize),
1612+ Result: addr,
1613+ Args: []ir.Value{framePtr, &ir.Const{Value: currentOffset}},
1614+ })
1615+ sym.IRVal = addr
1616
1617 if isParam {
1618- paramIndex := len(d.Params) - 1 - i
1619- ctx.genStore(sym.QbeName, fmt.Sprintf("%%p%d", paramIndex), sym.BxType)
1620- } else {
1621- varData := local.Node.Data.(ast.VarDeclNode)
1622- if varData.IsVector && (varData.Type == nil || varData.Type.Kind == ast.TYPE_UNTYPED) {
1623+ var origParamIndex int = -1
1624+ for j, p := range d.Params {
1625+ if p == local.Node {
1626+ origParamIndex = j
1627+ break
1628+ }
1629+ }
1630+
1631+ if origParamIndex != -1 {
1632+ paramVal := fn.Params[origParamIndex].Val
1633+ ctx.genStore(sym.IRVal, paramVal, typ)
1634+ }
1635+ } else { // Is an auto var
1636+ if isVec && (typ == nil || typ.Kind == ast.TYPE_UNTYPED) {
1637 storageAddr := ctx.newTemp()
1638- ctx.out.WriteString(fmt.Sprintf("\t%s =%s add %s, %d\n", storageAddr, ctx.wordType, sym.QbeName, ctx.wordSize))
1639- ctx.out.WriteString(fmt.Sprintf("\tstore%s %s, %s\n", ctx.wordType, storageAddr, sym.QbeName))
1640+ ctx.addInstr(&ir.Instruction{
1641+ Op: ir.OpAdd,
1642+ Typ: ir.GetType(nil, ctx.wordSize),
1643+ Result: storageAddr,
1644+ Args: []ir.Value{addr, &ir.Const{Value: int64(ctx.wordSize)}},
1645+ })
1646+ ctx.genStore(addr, storageAddr, nil)
1647 }
1648 }
1649-
1650 currentOffset += local.Size
1651 }
1652
1653@@ -1535,19 +918,18 @@ func (ctx *Context) codegenFuncDecl(node *ast.Node) {
1654
1655 if !bodyTerminates {
1656 if d.ReturnType != nil && d.ReturnType.Kind == ast.TYPE_VOID {
1657- ctx.out.WriteString("\tret\n")
1658+ ctx.addInstr(&ir.Instruction{Op: ir.OpRet})
1659 } else {
1660- ctx.out.WriteString("\tret 0\n")
1661+ ctx.addInstr(&ir.Instruction{Op: ir.OpRet, Args: []ir.Value{&ir.Const{Value: 0}}})
1662 }
1663 }
1664- ctx.out.WriteString("}\n\n")
1665 }
1666
1667-func (ctx *Context) codegenGlobalConst(node *ast.Node) string {
1668+func (ctx *Context) codegenGlobalConst(node *ast.Node) ir.Value {
1669 folded := ast.FoldConstants(node)
1670 switch folded.Type {
1671 case ast.Number:
1672- return fmt.Sprintf("%d", folded.Data.(ast.NumberNode).Value)
1673+ return &ir.Const{Value: folded.Data.(ast.NumberNode).Value}
1674 case ast.String:
1675 return ctx.addString(folded.Data.(ast.StringNode).Value)
1676 case ast.Ident:
1677@@ -1555,34 +937,25 @@ func (ctx *Context) codegenGlobalConst(node *ast.Node) string {
1678 sym := ctx.findSymbol(name)
1679 if sym == nil {
1680 util.Error(node.Tok, "Undefined symbol '%s' in global initializer.", name)
1681- return ""
1682- }
1683- if sym.IsVector && sym.Node != nil && sym.Node.Type == ast.VarDecl {
1684- d := sym.Node.Data.(ast.VarDeclNode)
1685- if !d.IsBracketed && len(d.InitList) <= 1 && d.Type == nil {
1686- return "$_" + name + "_storage"
1687- }
1688+ return nil
1689 }
1690- return sym.QbeName
1691+ return sym.IRVal
1692 case ast.AddressOf:
1693 lval := folded.Data.(ast.AddressOfNode).LValue
1694 if lval.Type != ast.Ident {
1695 util.Error(lval.Tok, "Global initializer must be the address of a global symbol.")
1696- return ""
1697+ return nil
1698 }
1699 name := lval.Data.(ast.IdentNode).Name
1700 sym := ctx.findSymbol(name)
1701 if sym == nil {
1702 util.Error(lval.Tok, "Undefined symbol '%s' in global initializer.", name)
1703- return ""
1704+ return nil
1705 }
1706- if sym.IsVector {
1707- return "$_" + name + "_storage"
1708- }
1709- return sym.QbeName
1710+ return sym.IRVal
1711 default:
1712 util.Error(node.Tok, "Global initializer must be a constant expression.")
1713- return ""
1714+ return nil
1715 }
1716 }
1717
1718@@ -1611,129 +984,95 @@ func (ctx *Context) codegenLocalVarDecl(d ast.VarDeclNode, sym *symbol) {
1719 }
1720
1721 if d.IsVector || (d.Type != nil && d.Type.Kind == ast.TYPE_ARRAY) {
1722- vectorPtr := ctx.genLoad(sym.QbeName, sym.BxType)
1723+ vectorPtr, _ := ctx.codegenExpr(&ast.Node{Type: ast.Ident, Data: ast.IdentNode{Name: d.Name}, Tok: sym.Node.Tok})
1724
1725 if len(d.InitList) == 1 && d.InitList[0].Type == ast.String {
1726 strVal := d.InitList[0].Data.(ast.StringNode).Value
1727 strLabel := ctx.addString(strVal)
1728 sizeToCopy := len(strVal) + 1
1729- ctx.out.WriteString(fmt.Sprintf("\tblit %s, %s, %d\n", strLabel, vectorPtr, sizeToCopy))
1730+ ctx.addInstr(&ir.Instruction{
1731+ Op: ir.OpBlit,
1732+ Args: []ir.Value{strLabel, vectorPtr, &ir.Const{Value: int64(sizeToCopy)}},
1733+ })
1734 } else {
1735 for i, initExpr := range d.InitList {
1736 offset := int64(i) * int64(ctx.wordSize)
1737 elemAddr := ctx.newTemp()
1738- ctx.out.WriteString(fmt.Sprintf("\t%s =%s add %s, %d\n", elemAddr, ctx.wordType, vectorPtr, offset))
1739- rval, _, _ := ctx.codegenExpr(initExpr)
1740- ctx.out.WriteString(fmt.Sprintf("\tstore%s %s, %s\n", ctx.wordType, rval, elemAddr))
1741+ ctx.addInstr(&ir.Instruction{
1742+ Op: ir.OpAdd,
1743+ Typ: ir.GetType(nil, ctx.wordSize),
1744+ Result: elemAddr,
1745+ Args: []ir.Value{vectorPtr, &ir.Const{Value: offset}},
1746+ })
1747+ rval, _ := ctx.codegenExpr(initExpr)
1748+ ctx.genStore(elemAddr, rval, nil)
1749 }
1750 }
1751 return
1752 }
1753
1754- rval, _, _ := ctx.codegenExpr(d.InitList[0])
1755- ctx.genStore(sym.QbeName, rval, d.Type)
1756+ rval, _ := ctx.codegenExpr(d.InitList[0])
1757+ ctx.genStore(sym.IRVal, rval, d.Type)
1758 }
1759
1760 func (ctx *Context) codegenGlobalVarDecl(d ast.VarDeclNode, sym *symbol) {
1761- ctx.out.WriteString(fmt.Sprintf("data %s = align %d { ", sym.QbeName, ctx.wordSize))
1762+ globalData := &ir.Data{
1763+ Name: sym.IRVal.(*ir.Global).Name,
1764+ Align: ctx.wordSize,
1765+ }
1766
1767- var elemSize int64 = int64(ctx.wordSize)
1768- var elemQbeType string = ctx.wordType
1769+ isUntypedStringVec := d.IsVector && (d.Type == nil || d.Type.Kind == ast.TYPE_UNTYPED) &&
1770+ len(d.InitList) == 1 && d.InitList[0].Type == ast.String
1771
1772- isTypedArray := d.Type != nil && (d.Type.Kind == ast.TYPE_ARRAY || d.Type.Kind == ast.TYPE_POINTER)
1773- if isTypedArray {
1774- if d.Type.Base != nil {
1775- elemSize = ctx.getSizeof(d.Type.Base)
1776- elemQbeType = ctx.getQbeType(d.Type.Base)
1777+ var elemType ir.Type
1778+ if isUntypedStringVec {
1779+ elemType = ir.TypeB
1780+ } else {
1781+ elemType = ir.GetType(d.Type, ctx.wordSize)
1782+ if d.Type != nil && d.Type.Kind == ast.TYPE_ARRAY {
1783+ elemType = ir.GetType(d.Type.Base, ctx.wordSize)
1784 }
1785- } else if d.Type != nil {
1786- elemSize = ctx.getSizeof(d.Type)
1787- elemQbeType = ctx.getQbeType(d.Type)
1788 }
1789
1790- var totalSize int64
1791- if d.Type != nil && d.Type.Kind == ast.TYPE_ARRAY && d.Type.ArraySize != nil {
1792- totalSize = ctx.getSizeof(d.Type)
1793- } else if d.IsVector && d.SizeExpr != nil {
1794- if folded := ast.FoldConstants(d.SizeExpr); folded.Type == ast.Number {
1795- totalSize = folded.Data.(ast.NumberNode).Value * elemSize
1796+ var numElements int64
1797+ var sizeNode *ast.Node
1798+ if d.Type != nil && d.Type.Kind == ast.TYPE_ARRAY {
1799+ sizeNode = d.Type.ArraySize
1800+ } else if d.IsVector {
1801+ sizeNode = d.SizeExpr
1802+ }
1803+
1804+ if sizeNode != nil {
1805+ if val, ok := ctx.evalConstExpr(sizeNode); ok {
1806+ numElements = val
1807+ } else {
1808+ util.Error(sizeNode.Tok, "Global array size must be a constant expression.")
1809 }
1810 } else {
1811- totalSize = int64(len(d.InitList)) * elemSize
1812- if totalSize == 0 {
1813- totalSize = elemSize
1814- }
1815+ numElements = int64(len(d.InitList))
1816+ }
1817+ if numElements == 0 && !d.IsVector && len(d.InitList) == 0 {
1818+ numElements = 1
1819 }
1820
1821 if len(d.InitList) > 0 {
1822- var items []string
1823 for _, init := range d.InitList {
1824 val := ctx.codegenGlobalConst(init)
1825- itemType := elemQbeType
1826- if strings.HasPrefix(val, "$") {
1827- itemType = ctx.wordType
1828+ itemType := elemType
1829+ if _, ok := val.(*ir.Global); ok {
1830+ itemType = ir.TypePtr
1831 }
1832- items = append(items, fmt.Sprintf("%s %s", itemType, val))
1833+ globalData.Items = append(globalData.Items, ir.DataItem{Typ: itemType, Value: val})
1834 }
1835- ctx.out.WriteString(strings.Join(items, ", "))
1836-
1837- initializedBytes := int64(len(d.InitList)) * elemSize
1838- if totalSize > initializedBytes {
1839- ctx.out.WriteString(fmt.Sprintf(", z %d", totalSize-initializedBytes))
1840+ initializedElements := int64(len(d.InitList))
1841+ if numElements > initializedElements {
1842+ globalData.Items = append(globalData.Items, ir.DataItem{Typ: elemType, Count: int(numElements - initializedElements)})
1843 }
1844- } else {
1845- ctx.out.WriteString(fmt.Sprintf("z %d", totalSize))
1846+ } else if numElements > 0 {
1847+ globalData.Items = append(globalData.Items, ir.DataItem{Typ: elemType, Count: int(numElements)})
1848 }
1849
1850- ctx.out.WriteString(" }\n")
1851-}
1852-
1853-func (ctx *Context) codegenSwitch(node *ast.Node) bool {
1854- d := node.Data.(ast.SwitchNode)
1855- switchVal, _, _ := ctx.codegenExpr(d.Expr)
1856- endLabel := ctx.newLabel()
1857-
1858- oldBreakLabel := ctx.breakLabel
1859- ctx.breakLabel = endLabel
1860- defer func() { ctx.breakLabel = oldBreakLabel }()
1861-
1862- defaultTarget := endLabel
1863- if d.DefaultLabelName != "" {
1864- defaultTarget = d.DefaultLabelName
1865- }
1866-
1867- switchType := ctx.getQbeType(d.Expr.Typ)
1868-
1869- for _, caseLabel := range d.CaseLabels {
1870- caseValConst := fmt.Sprintf("%d", caseLabel.Value)
1871- cmpRes := ctx.newTemp()
1872- nextCheckLabel := ctx.newLabel()
1873- cmpInst := ctx.getCmpOpStr(token.EqEq, switchType)
1874- ctx.out.WriteString(fmt.Sprintf("\t%s =%s %s %s, %s\n", cmpRes, ctx.wordType, cmpInst, switchVal, caseValConst))
1875- ctx.out.WriteString(fmt.Sprintf("\tjnz %s, %s, %s\n", cmpRes, caseLabel.LabelName, nextCheckLabel))
1876- ctx.writeLabel(nextCheckLabel)
1877- }
1878- ctx.out.WriteString(fmt.Sprintf("\tjmp %s\n", defaultTarget))
1879-
1880- bodyTerminates := true
1881- if d.Body != nil && d.Body.Type == ast.Block {
1882- codegenStarted := false
1883- allPathsTerminate := true
1884- bodyStmts := d.Body.Data.(ast.BlockNode).Stmts
1885- for _, stmt := range bodyStmts {
1886- isLabel := stmt.Type == ast.Case || stmt.Type == ast.Default || stmt.Type == ast.Label
1887- if !codegenStarted && isLabel {
1888- codegenStarted = true
1889- }
1890- if codegenStarted {
1891- if !ctx.codegenStmt(stmt) {
1892- allPathsTerminate = false
1893- }
1894- }
1895- }
1896- bodyTerminates = allPathsTerminate
1897+ if len(globalData.Items) > 0 {
1898+ ctx.prog.Globals = append(ctx.prog.Globals, globalData)
1899 }
1900-
1901- ctx.writeLabel(endLabel)
1902- return bodyTerminates && d.DefaultLabelName != ""
1903 }
+524,
-0
1@@ -0,0 +1,524 @@
2+package codegen
3+
4+import (
5+ "strings"
6+
7+ "github.com/xplshn/gbc/pkg/ast"
8+ "github.com/xplshn/gbc/pkg/config"
9+ "github.com/xplshn/gbc/pkg/ir"
10+ "github.com/xplshn/gbc/pkg/token"
11+ "github.com/xplshn/gbc/pkg/util"
12+)
13+
14+// Helper functions for codegenExpr
15+
16+func (ctx *Context) codegenIdent(node *ast.Node) (ir.Value, bool) {
17+ name := node.Data.(ast.IdentNode).Name
18+ sym := ctx.findSymbol(name)
19+
20+ if sym == nil {
21+ util.Warn(ctx.cfg, config.WarnImplicitDecl, node.Tok, "Implicit declaration of function '%s'", name)
22+ sym = ctx.addSymbol(name, symFunc, ast.TypeUntyped, false, node)
23+ return sym.IRVal, false
24+ }
25+
26+ switch sym.Type {
27+ case symFunc:
28+ return sym.IRVal, false
29+ case symExtrn:
30+ isCall := node.Parent != nil && node.Parent.Type == ast.FuncCall && node.Parent.Data.(ast.FuncCallNode).FuncExpr == node
31+ if isCall {
32+ return sym.IRVal, false
33+ }
34+ ctx.prog.ExtrnVars[name] = true
35+ res := ctx.newTemp()
36+ ctx.addInstr(&ir.Instruction{Op: ir.OpLoad, Typ: ir.TypePtr, Result: res, Args: []ir.Value{sym.IRVal}})
37+ return res, false
38+ }
39+
40+ isArr := sym.IsVector || (sym.BxType != nil && sym.BxType.Kind == ast.TYPE_ARRAY)
41+ if isArr {
42+ isParam := false
43+ if sym.Node != nil && sym.Node.Parent != nil && sym.Node.Parent.Type == ast.FuncDecl {
44+ funcDecl := sym.Node.Parent.Data.(ast.FuncDeclNode)
45+ for _, p := range funcDecl.Params {
46+ if p == sym.Node {
47+ isParam = true
48+ break
49+ }
50+ }
51+ }
52+ _, isLocal := sym.IRVal.(*ir.Temporary)
53+ if isLocal {
54+ isDopeVector := sym.IsVector && (sym.BxType == nil || sym.BxType.Kind == ast.TYPE_UNTYPED)
55+ if isParam || isDopeVector {
56+ return ctx.genLoad(sym.IRVal, sym.BxType), false
57+ }
58+ }
59+ return sym.IRVal, false
60+ }
61+
62+ return ctx.genLoad(sym.IRVal, sym.BxType), false
63+}
64+
65+func (ctx *Context) codegenAssign(node *ast.Node) (ir.Value, bool) {
66+ d := node.Data.(ast.AssignNode)
67+ lvalAddr := ctx.codegenLvalue(d.Lhs)
68+ var rval ir.Value
69+
70+ if d.Op == token.Eq {
71+ rval, _ = ctx.codegenExpr(d.Rhs)
72+ } else {
73+ currentLvalVal := ctx.genLoad(lvalAddr, d.Lhs.Typ)
74+ rhsVal, _ := ctx.codegenExpr(d.Rhs)
75+ op, typ := getBinaryOpAndType(d.Op, d.Lhs.Typ, ctx.wordSize)
76+ rval = ctx.newTemp()
77+ ctx.addInstr(&ir.Instruction{Op: op, Typ: typ, Result: rval, Args: []ir.Value{currentLvalVal, rhsVal}})
78+ }
79+
80+ ctx.genStore(lvalAddr, rval, d.Lhs.Typ)
81+ return rval, false
82+}
83+
84+func (ctx *Context) codegenBinaryOp(node *ast.Node) (ir.Value, bool) {
85+ d := node.Data.(ast.BinaryOpNode)
86+ if d.Op == token.OrOr || d.Op == token.AndAnd {
87+ res := ctx.newTemp()
88+ trueL, falseL, endL := ctx.newLabel(), ctx.newLabel(), ctx.newLabel()
89+
90+ ctx.codegenLogicalCond(node, trueL, falseL)
91+
92+ ctx.startBlock(trueL)
93+ ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{endL}})
94+
95+ ctx.startBlock(falseL)
96+ ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{endL}})
97+
98+ ctx.startBlock(endL)
99+ wordType := ir.GetType(nil, ctx.wordSize)
100+ ctx.addInstr(&ir.Instruction{
101+ Op: ir.OpPhi,
102+ Typ: wordType,
103+ Result: res,
104+ Args: []ir.Value{
105+ trueL, &ir.Const{Value: 1},
106+ falseL, &ir.Const{Value: 0},
107+ },
108+ })
109+ return res, false
110+ }
111+
112+ l, _ := ctx.codegenExpr(d.Left)
113+ r, _ := ctx.codegenExpr(d.Right)
114+ res := ctx.newTemp()
115+ op, typ := getBinaryOpAndType(d.Op, d.Left.Typ, ctx.wordSize)
116+ ctx.addInstr(&ir.Instruction{Op: op, Typ: typ, Result: res, Args: []ir.Value{l, r}})
117+ return res, false
118+}
119+
120+func (ctx *Context) codegenUnaryOp(node *ast.Node) (ir.Value, bool) {
121+ d := node.Data.(ast.UnaryOpNode)
122+ res := ctx.newTemp()
123+ val, _ := ctx.codegenExpr(d.Expr)
124+ valType := ir.GetType(d.Expr.Typ, ctx.wordSize)
125+
126+ switch d.Op {
127+ case token.Minus:
128+ ctx.addInstr(&ir.Instruction{Op: ir.OpSub, Typ: valType, Result: res, Args: []ir.Value{&ir.Const{Value: 0}, val}})
129+ case token.Plus:
130+ return val, false
131+ case token.Not:
132+ wordType := ir.GetType(nil, ctx.wordSize)
133+ ctx.addInstr(&ir.Instruction{Op: ir.OpCEq, Typ: wordType, Result: res, Args: []ir.Value{val, &ir.Const{Value: 0}}})
134+ case token.Complement:
135+ wordType := ir.GetType(nil, ctx.wordSize)
136+ ctx.addInstr(&ir.Instruction{Op: ir.OpXor, Typ: wordType, Result: res, Args: []ir.Value{val, &ir.Const{Value: -1}}})
137+ case token.Inc, token.Dec: // Prefix
138+ lvalAddr := ctx.codegenLvalue(d.Expr)
139+ op := map[token.Type]ir.Op{token.Inc: ir.OpAdd, token.Dec: ir.OpSub}[d.Op]
140+ currentVal := ctx.genLoad(lvalAddr, d.Expr.Typ)
141+ ctx.addInstr(&ir.Instruction{Op: op, Typ: valType, Result: res, Args: []ir.Value{currentVal, &ir.Const{Value: 1}}})
142+ ctx.genStore(lvalAddr, res, d.Expr.Typ)
143+ default:
144+ util.Error(node.Tok, "Unsupported unary operator")
145+ }
146+ return res, false
147+}
148+
149+func (ctx *Context) codegenPostfixOp(node *ast.Node) (ir.Value, bool) {
150+ d := node.Data.(ast.PostfixOpNode)
151+ lvalAddr := ctx.codegenLvalue(d.Expr)
152+ res := ctx.genLoad(lvalAddr, d.Expr.Typ)
153+
154+ newVal := ctx.newTemp()
155+ op := map[token.Type]ir.Op{token.Inc: ir.OpAdd, token.Dec: ir.OpSub}[d.Op]
156+ valType := ir.GetType(d.Expr.Typ, ctx.wordSize)
157+ ctx.addInstr(&ir.Instruction{Op: op, Typ: valType, Result: newVal, Args: []ir.Value{res, &ir.Const{Value: 1}}})
158+ ctx.genStore(lvalAddr, newVal, d.Expr.Typ)
159+ return res, false
160+}
161+
162+func (ctx *Context) codegenIndirection(node *ast.Node) (ir.Value, bool) {
163+ exprNode := node.Data.(ast.IndirectionNode).Expr
164+ addr, _ := ctx.codegenExpr(exprNode)
165+ loadType := node.Typ
166+ if !ctx.isTypedPass && exprNode.Type == ast.Ident {
167+ if sym := ctx.findSymbol(exprNode.Data.(ast.IdentNode).Name); sym != nil && sym.IsByteArray {
168+ loadType = ast.TypeByte
169+ }
170+ }
171+ return ctx.genLoad(addr, loadType), false
172+}
173+
174+func (ctx *Context) codegenSubscriptAddr(node *ast.Node) ir.Value {
175+ d := node.Data.(ast.SubscriptNode)
176+ arrayPtr, _ := ctx.codegenExpr(d.Array)
177+ indexVal, _ := ctx.codegenExpr(d.Index)
178+
179+ var scale int64 = int64(ctx.wordSize)
180+ if d.Array.Typ != nil {
181+ if d.Array.Typ.Kind == ast.TYPE_POINTER || d.Array.Typ.Kind == ast.TYPE_ARRAY {
182+ if d.Array.Typ.Base != nil {
183+ scale = ctx.getSizeof(d.Array.Typ.Base)
184+ }
185+ }
186+ } else if !ctx.isTypedPass && d.Array.Type == ast.Ident {
187+ if sym := ctx.findSymbol(d.Array.Data.(ast.IdentNode).Name); sym != nil && sym.IsByteArray {
188+ scale = 1
189+ }
190+ }
191+
192+ var scaledIndex ir.Value = indexVal
193+ if scale > 1 {
194+ scaledIndex = ctx.newTemp()
195+ ctx.addInstr(&ir.Instruction{
196+ Op: ir.OpMul,
197+ Typ: ir.GetType(nil, ctx.wordSize),
198+ Result: scaledIndex,
199+ Args: []ir.Value{indexVal, &ir.Const{Value: scale}},
200+ })
201+ }
202+
203+ resultAddr := ctx.newTemp()
204+ ctx.addInstr(&ir.Instruction{
205+ Op: ir.OpAdd,
206+ Typ: ir.GetType(nil, ctx.wordSize),
207+ Result: resultAddr,
208+ Args: []ir.Value{arrayPtr, scaledIndex},
209+ })
210+ return resultAddr
211+}
212+
213+func (ctx *Context) codegenAddressOf(node *ast.Node) (ir.Value, bool) {
214+ lvalNode := node.Data.(ast.AddressOfNode).LValue
215+ if lvalNode.Type == ast.Ident {
216+ name := lvalNode.Data.(ast.IdentNode).Name
217+ if sym := ctx.findSymbol(name); sym != nil {
218+ isTypedArray := sym.BxType != nil && sym.BxType.Kind == ast.TYPE_ARRAY
219+ if sym.Type == symFunc || isTypedArray {
220+ return sym.IRVal, false
221+ }
222+ if sym.IsVector {
223+ res, _ := ctx.codegenExpr(lvalNode)
224+ return res, false
225+ }
226+ }
227+ }
228+ return ctx.codegenLvalue(lvalNode), false
229+}
230+
231+func (ctx *Context) codegenFuncCall(node *ast.Node) (ir.Value, bool) {
232+ d := node.Data.(ast.FuncCallNode)
233+ if d.FuncExpr.Type == ast.Ident {
234+ name := d.FuncExpr.Data.(ast.IdentNode).Name
235+ if sym := ctx.findSymbol(name); sym != nil && sym.Type == symVar && !sym.IsVector {
236+ util.Error(d.FuncExpr.Tok, "'%s' is a variable but is used as a function", name)
237+ }
238+ }
239+
240+ argVals := make([]ir.Value, len(d.Args))
241+ argTypes := make([]ir.Type, len(d.Args))
242+ for i := len(d.Args) - 1; i >= 0; i-- {
243+ argVals[i], _ = ctx.codegenExpr(d.Args[i])
244+ argTypes[i] = ir.GetType(d.Args[i].Typ, ctx.wordSize)
245+ }
246+ funcVal, _ := ctx.codegenExpr(d.FuncExpr)
247+
248+ isStmt := node.Parent != nil && node.Parent.Type == ast.Block
249+ returnType := ir.GetType(node.Typ, ctx.wordSize)
250+
251+ var res ir.Value
252+ if !isStmt && returnType != ir.TypeNone {
253+ res = ctx.newTemp()
254+ }
255+
256+ callArgs := append([]ir.Value{funcVal}, argVals...)
257+ ctx.addInstr(&ir.Instruction{
258+ Op: ir.OpCall,
259+ Typ: returnType,
260+ Result: res,
261+ Args: callArgs,
262+ ArgTypes: argTypes,
263+ })
264+ return res, false
265+}
266+
267+func (ctx *Context) codegenTernary(node *ast.Node) (ir.Value, bool) {
268+ d := node.Data.(ast.TernaryNode)
269+ thenL, elseL, endL := ctx.newLabel(), ctx.newLabel(), ctx.newLabel()
270+ res := ctx.newTemp()
271+ resType := ir.GetType(node.Typ, ctx.wordSize)
272+
273+ ctx.codegenLogicalCond(d.Cond, thenL, elseL)
274+
275+ ctx.startBlock(thenL)
276+ thenVal, thenTerminates := ctx.codegenExpr(d.ThenExpr)
277+ var thenPred *ir.Label
278+ if !thenTerminates {
279+ if ir.GetType(d.ThenExpr.Typ, ctx.wordSize) != resType {
280+ castedVal := ctx.newTemp()
281+ ctx.addInstr(&ir.Instruction{Op: ir.OpCast, Typ: resType, Result: castedVal, Args: []ir.Value{thenVal}})
282+ thenVal = castedVal
283+ }
284+ thenPred = ctx.currentBlock.Label
285+ ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{endL}})
286+ }
287+
288+ ctx.startBlock(elseL)
289+ elseVal, elseTerminates := ctx.codegenExpr(d.ElseExpr)
290+ var elsePred *ir.Label
291+ if !elseTerminates {
292+ if ir.GetType(d.ElseExpr.Typ, ctx.wordSize) != resType {
293+ castedVal := ctx.newTemp()
294+ ctx.addInstr(&ir.Instruction{Op: ir.OpCast, Typ: resType, Result: castedVal, Args: []ir.Value{elseVal}})
295+ elseVal = castedVal
296+ }
297+ elsePred = ctx.currentBlock.Label
298+ ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{endL}})
299+ }
300+
301+ terminates := thenTerminates && elseTerminates
302+ if !terminates {
303+ ctx.startBlock(endL)
304+ phiArgs := []ir.Value{}
305+ if !thenTerminates {
306+ phiArgs = append(phiArgs, thenPred, thenVal)
307+ }
308+ if !elseTerminates {
309+ phiArgs = append(phiArgs, elsePred, elseVal)
310+ }
311+ ctx.addInstr(&ir.Instruction{Op: ir.OpPhi, Typ: resType, Result: res, Args: phiArgs})
312+ }
313+ return res, terminates
314+}
315+
316+func (ctx *Context) codegenAutoAlloc(node *ast.Node) (ir.Value, bool) {
317+ d := node.Data.(ast.AutoAllocNode)
318+ sizeVal, _ := ctx.codegenExpr(d.Size)
319+ res := ctx.newTemp()
320+
321+ sizeInBytes := ctx.newTemp()
322+ wordType := ir.GetType(nil, ctx.wordSize)
323+ ctx.addInstr(&ir.Instruction{
324+ Op: ir.OpMul,
325+ Typ: wordType,
326+ Result: sizeInBytes,
327+ Args: []ir.Value{sizeVal, &ir.Const{Value: int64(ctx.wordSize)}},
328+ })
329+
330+ ctx.addInstr(&ir.Instruction{
331+ Op: ir.OpAlloc,
332+ Typ: wordType,
333+ Result: res,
334+ Args: []ir.Value{sizeInBytes},
335+ Align: ctx.stackAlign,
336+ })
337+ return res, false
338+}
339+
340+// Helper functions for codegenStmt
341+
342+func (ctx *Context) codegenReturn(node *ast.Node) bool {
343+ var retVal ir.Value
344+ d := node.Data.(ast.ReturnNode)
345+ if d.Expr != nil {
346+ retVal, _ = ctx.codegenExpr(d.Expr)
347+ } else if ctx.currentFunc != nil && ctx.currentFunc.ReturnType != ir.TypeNone {
348+ retVal = &ir.Const{Value: 0}
349+ }
350+ ctx.addInstr(&ir.Instruction{Op: ir.OpRet, Args: []ir.Value{retVal}})
351+ ctx.currentBlock = nil
352+ return true
353+}
354+
355+func (ctx *Context) codegenIf(node *ast.Node) bool {
356+ d := node.Data.(ast.IfNode)
357+ thenL, endL := ctx.newLabel(), ctx.newLabel()
358+ elseL := endL
359+ if d.ElseBody != nil {
360+ elseL = ctx.newLabel()
361+ }
362+
363+ ctx.codegenLogicalCond(d.Cond, thenL, elseL)
364+
365+ ctx.startBlock(thenL)
366+ thenTerminates := ctx.codegenStmt(d.ThenBody)
367+ if !thenTerminates {
368+ ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{endL}})
369+ }
370+
371+ var elseTerminates bool
372+ if d.ElseBody != nil {
373+ ctx.startBlock(elseL)
374+ elseTerminates = ctx.codegenStmt(d.ElseBody)
375+ if !elseTerminates {
376+ ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{endL}})
377+ }
378+ }
379+
380+ if !thenTerminates || !elseTerminates {
381+ ctx.startBlock(endL)
382+ }
383+ return thenTerminates && (d.ElseBody != nil && elseTerminates)
384+}
385+
386+func (ctx *Context) codegenWhile(node *ast.Node) bool {
387+ d := node.Data.(ast.WhileNode)
388+ startL, bodyL, endL := ctx.newLabel(), ctx.newLabel(), ctx.newLabel()
389+
390+ oldBreak, oldContinue := ctx.breakLabel, ctx.continueLabel
391+ ctx.breakLabel, ctx.continueLabel = endL, startL
392+ defer func() { ctx.breakLabel, ctx.continueLabel = oldBreak, oldContinue }()
393+
394+ ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{startL}})
395+ ctx.startBlock(startL)
396+ ctx.codegenLogicalCond(d.Cond, bodyL, endL)
397+
398+ ctx.startBlock(bodyL)
399+ bodyTerminates := ctx.codegenStmt(d.Body)
400+ if !bodyTerminates {
401+ ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{startL}})
402+ }
403+
404+ ctx.startBlock(endL)
405+ return false
406+}
407+
408+func (ctx *Context) codegenSwitch(node *ast.Node) bool {
409+ d := node.Data.(ast.SwitchNode)
410+ switchVal, _ := ctx.codegenExpr(d.Expr)
411+ endLabel := ctx.newLabel()
412+
413+ oldBreak := ctx.breakLabel
414+ ctx.breakLabel = endLabel
415+ defer func() { ctx.breakLabel = oldBreak }()
416+
417+ ctx.switchStack = append(ctx.switchStack, &switchContext{Node: &d, CaseIndex: 0})
418+ defer func() { ctx.switchStack = ctx.switchStack[:len(ctx.switchStack)-1] }()
419+
420+ var defaultTarget *ir.Label
421+ if d.DefaultLabelName != "" {
422+ labelName := strings.TrimLeft(d.DefaultLabelName, "@")
423+ defaultTarget = &ir.Label{Name: labelName}
424+ } else {
425+ defaultTarget = endLabel
426+ }
427+
428+ wordType := ir.GetType(nil, ctx.wordSize)
429+ for _, caseLabelInfo := range d.CaseLabels {
430+ caseValConst := &ir.Const{Value: caseLabelInfo.Value}
431+ cmpRes := ctx.newTemp()
432+ nextCheckLabel := ctx.newLabel()
433+ labelName := strings.TrimLeft(caseLabelInfo.LabelName, "@")
434+ caseTargetLabel := &ir.Label{Name: labelName}
435+
436+ ctx.addInstr(&ir.Instruction{Op: ir.OpCEq, Typ: wordType, Result: cmpRes, Args: []ir.Value{switchVal, caseValConst}})
437+ ctx.addInstr(&ir.Instruction{Op: ir.OpJnz, Args: []ir.Value{cmpRes, caseTargetLabel, nextCheckLabel}})
438+ ctx.startBlock(nextCheckLabel)
439+ }
440+ ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{defaultTarget}})
441+ ctx.currentBlock = nil
442+
443+ bodyTerminates := ctx.codegenStmt(d.Body)
444+
445+ if ctx.currentBlock != nil {
446+ ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{endLabel}})
447+ }
448+
449+ ctx.startBlock(endLabel)
450+ return bodyTerminates && d.DefaultLabelName != ""
451+}
452+
453+func (ctx *Context) codegenCaseOrDefault(node *ast.Node) bool {
454+ var body *ast.Node
455+ var labelName string
456+
457+ if node.Type == ast.Case {
458+ d := node.Data.(ast.CaseNode)
459+ body = d.Body
460+ if len(ctx.switchStack) > 0 {
461+ info := ctx.switchStack[len(ctx.switchStack)-1]
462+ if info.CaseIndex < len(info.Node.CaseLabels) {
463+ labelName = info.Node.CaseLabels[info.CaseIndex].LabelName
464+ info.CaseIndex++
465+ }
466+ }
467+ } else { // Default
468+ d := node.Data.(ast.DefaultNode)
469+ body = d.Body
470+ if len(ctx.switchStack) > 0 {
471+ info := ctx.switchStack[len(ctx.switchStack)-1]
472+ labelName = info.Node.DefaultLabelName
473+ }
474+ }
475+
476+ if labelName != "" {
477+ label := &ir.Label{Name: strings.TrimLeft(labelName, "@")}
478+ if ctx.currentBlock != nil {
479+ ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{label}})
480+ }
481+ ctx.startBlock(label)
482+ }
483+
484+ return ctx.codegenStmt(body)
485+}
486+
487+// Helper for op mapping
488+func getBinaryOpAndType(op token.Type, astType *ast.BxType, wordSize int) (ir.Op, ir.Type) {
489+ typ := ir.GetType(astType, wordSize)
490+ switch op {
491+ case token.Plus, token.PlusEq, token.EqPlus:
492+ return ir.OpAdd, typ
493+ case token.Minus, token.MinusEq, token.EqMinus:
494+ return ir.OpSub, typ
495+ case token.Star, token.StarEq, token.EqStar:
496+ return ir.OpMul, typ
497+ case token.Slash, token.SlashEq, token.EqSlash:
498+ return ir.OpDiv, typ
499+ case token.Rem, token.RemEq, token.EqRem:
500+ return ir.OpRem, typ
501+ case token.And, token.AndEq, token.EqAnd:
502+ return ir.OpAnd, typ
503+ case token.Or, token.OrEq, token.EqOr:
504+ return ir.OpOr, typ
505+ case token.Xor, token.XorEq, token.EqXor:
506+ return ir.OpXor, typ
507+ case token.Shl, token.ShlEq, token.EqShl:
508+ return ir.OpShl, typ
509+ case token.Shr, token.ShrEq, token.EqShr:
510+ return ir.OpShr, typ
511+ case token.EqEq:
512+ return ir.OpCEq, typ
513+ case token.Neq:
514+ return ir.OpCNeq, typ
515+ case token.Lt:
516+ return ir.OpCLt, typ
517+ case token.Gt:
518+ return ir.OpCGt, typ
519+ case token.Lte:
520+ return ir.OpCLe, typ
521+ case token.Gte:
522+ return ir.OpCGe, typ
523+ }
524+ return -1, -1
525+}
+832,
-0
1@@ -0,0 +1,832 @@
2+package codegen
3+
4+import (
5+ "bytes"
6+ "fmt"
7+ "os"
8+ "os/exec"
9+ "sort"
10+ "strconv"
11+ "strings"
12+
13+ "github.com/xplshn/gbc/pkg/config"
14+ "github.com/xplshn/gbc/pkg/ir"
15+)
16+
17+// llvmBackend implements the Backend interface for LLVM IR.
18+type llvmBackend struct {
19+ out *strings.Builder
20+ prog *ir.Program
21+ cfg *config.Config
22+ wordType string
23+ tempTypes map[string]string // Maps temporary/global name to its LLVM type string
24+ funcSigs map[string]string // Caches function signatures
25+ currentFn *ir.Func
26+}
27+
28+// NewLLVMBackend creates a new instance of the LLVM backend.
29+func NewLLVMBackend() Backend {
30+ return &llvmBackend{}
31+}
32+
33+// Generate translates a generic IR program into final assembly using the LLVM toolchain.
34+func (b *llvmBackend) Generate(prog *ir.Program, cfg *config.Config) (*bytes.Buffer, error) {
35+ var llvmIRBuilder strings.Builder
36+ b.out = &llvmIRBuilder
37+ b.prog = prog
38+ b.cfg = cfg
39+ b.wordType = fmt.Sprintf("i%d", cfg.WordSize*8)
40+ b.tempTypes = make(map[string]string)
41+ b.funcSigs = make(map[string]string)
42+
43+ b.gen()
44+
45+ llvmIR := llvmIRBuilder.String()
46+ asm, err := b.compileLLVMIR(llvmIR)
47+ if err != nil {
48+ return nil, err
49+ }
50+ return bytes.NewBufferString(asm), nil
51+}
52+
53+// compileLLVMIR invokes the 'llc' tool to compile LLVM IR into assembly.
54+func (b *llvmBackend) compileLLVMIR(llvmIR string) (string, error) {
55+ llFile, err := os.CreateTemp("", "gbc-main-*.ll")
56+ if err != nil {
57+ return "", fmt.Errorf("failed to create temp file for LLVM IR: %w", err)
58+ }
59+ defer os.Remove(llFile.Name())
60+ if _, err := llFile.WriteString(llvmIR); err != nil {
61+ return "", fmt.Errorf("failed to write to temp file for LLVM IR: %w", err)
62+ }
63+ llFile.Close()
64+
65+ asmFile, err := os.CreateTemp("", "gbc-main-*.s")
66+ if err != nil {
67+ return "", fmt.Errorf("failed to create temp file for assembly: %w", err)
68+ }
69+ asmFile.Close()
70+ defer os.Remove(asmFile.Name())
71+
72+ cmd := exec.Command("llc", "-O3", "-o", asmFile.Name(), llFile.Name())
73+ if output, err := cmd.CombinedOutput(); err != nil {
74+ return "", fmt.Errorf("llc command failed: %w\n--- LLVM IR ---\n%s\n--- Output ---\n%s", err, llvmIR, string(output))
75+ }
76+
77+ asmBytes, err := os.ReadFile(asmFile.Name())
78+ if err != nil {
79+ return "", fmt.Errorf("failed to read temporary assembly file: %w", err)
80+ }
81+ return string(asmBytes), nil
82+}
83+
84+func (b *llvmBackend) gen() {
85+ fmt.Fprintf(b.out, "; Generated by gbc\n")
86+ fmt.Fprintf(b.out, "target triple = \"%s\"\n\n", b.cfg.BackendTarget)
87+
88+ b.genDeclarations()
89+ b.genStrings()
90+ b.genGlobals()
91+
92+ for _, fn := range b.prog.Funcs {
93+ b.genFunc(fn)
94+ }
95+}
96+
97+func (b *llvmBackend) getFuncSig(name string) (retType string) {
98+ switch name {
99+ case "printf", "fprintf", "sprintf", "atoi", "usleep":
100+ return "i32"
101+ case "malloc", "realloc", "memset":
102+ return "i8*"
103+ case "sin", "cos", "sqrt", "fabs":
104+ return "double"
105+ case "free", "exit":
106+ return "void"
107+ default:
108+ return b.wordType
109+ }
110+}
111+
112+func (b *llvmBackend) genDeclarations() {
113+ knownExternals := make(map[string]bool)
114+
115+ if len(b.prog.ExtrnVars) > 0 {
116+ b.out.WriteString("; --- External Variables ---\n")
117+ for name := range b.prog.ExtrnVars {
118+ if knownExternals[name] {
119+ continue
120+ }
121+ ptrType := "i8*"
122+ fmt.Fprintf(b.out, "@%s = external global %s\n", name, ptrType)
123+ b.tempTypes["@"+name] = ptrType + "*"
124+ knownExternals[name] = true
125+ }
126+ b.out.WriteString("\n")
127+ }
128+
129+ potentialFuncs := make(map[string]bool)
130+ for _, name := range b.prog.ExtrnFuncs {
131+ potentialFuncs[name] = true
132+ }
133+ for _, fn := range b.prog.Funcs {
134+ for _, block := range fn.Blocks {
135+ for _, instr := range block.Instructions {
136+ if instr.Op == ir.OpCall {
137+ if g, ok := instr.Args[0].(*ir.Global); ok {
138+ potentialFuncs[g.Name] = true
139+ }
140+ }
141+ }
142+ }
143+ }
144+
145+ for _, fn := range b.prog.Funcs {
146+ delete(potentialFuncs, fn.Name)
147+ }
148+
149+ var funcsToDeclare []string
150+ for name := range potentialFuncs {
151+ if !knownExternals[name] {
152+ funcsToDeclare = append(funcsToDeclare, name)
153+ }
154+ }
155+
156+ if len(funcsToDeclare) > 0 {
157+ b.out.WriteString("; --- External Functions ---\n")
158+ sort.Strings(funcsToDeclare)
159+ for _, name := range funcsToDeclare {
160+ retType := b.getFuncSig(name)
161+ sig := fmt.Sprintf("declare %s @%s(...)\n", retType, name)
162+ b.out.WriteString(sig)
163+ b.funcSigs[name] = sig
164+ }
165+ b.out.WriteString("\n")
166+ }
167+}
168+
169+func (b *llvmBackend) genStrings() {
170+ if len(b.prog.Strings) == 0 {
171+ return
172+ }
173+ b.out.WriteString("; --- String Literals ---\n")
174+ for s, label := range b.prog.Strings {
175+ strLen := len(s) + 1
176+ escaped := b.escapeString(s)
177+ typeStr := fmt.Sprintf("[%d x i8]", strLen)
178+ fmt.Fprintf(b.out, "@%s = private unnamed_addr constant %s c\"%s\\00\"\n", label, typeStr, escaped)
179+ b.tempTypes["@"+label] = typeStr + "*"
180+ }
181+ b.out.WriteString("\n")
182+}
183+
184+func (b *llvmBackend) genGlobals() {
185+ if len(b.prog.Globals) == 0 {
186+ return
187+ }
188+ b.out.WriteString("; --- Global Variables ---\n")
189+ for _, g := range b.prog.Globals {
190+ hasInitializer := false
191+ totalItemCount := 0
192+ var firstItemType ir.Type = -1
193+
194+ for _, item := range g.Items {
195+ if item.Count > 0 {
196+ totalItemCount += item.Count
197+ } else {
198+ totalItemCount++
199+ hasInitializer = true
200+ }
201+ if firstItemType == -1 {
202+ firstItemType = item.Typ
203+ }
204+ }
205+
206+ var globalType string
207+ elemType := b.formatType(firstItemType)
208+ if firstItemType == -1 {
209+ elemType = b.wordType
210+ }
211+
212+ if totalItemCount > 1 {
213+ globalType = fmt.Sprintf("[%d x %s]", totalItemCount, elemType)
214+ } else if totalItemCount == 1 {
215+ globalType = elemType
216+ } else {
217+ continue
218+ }
219+
220+ initializer := "zeroinitializer"
221+ if hasInitializer {
222+ if strings.HasPrefix(globalType, "[") {
223+ var typedItems []string
224+ for _, item := range g.Items {
225+ if item.Count > 0 {
226+ for i := 0; i < item.Count; i++ {
227+ typedItems = append(typedItems, fmt.Sprintf("%s 0", elemType))
228+ }
229+ } else {
230+ itemTypeStr := b.formatType(item.Typ)
231+ valStr := b.formatGlobalInitializerValue(item.Value, itemTypeStr)
232+ typedItems = append(typedItems, fmt.Sprintf("%s %s", itemTypeStr, valStr))
233+ }
234+ }
235+ initializer = fmt.Sprintf("[ %s ]", strings.Join(typedItems, ", "))
236+ } else { // Scalar
237+ initializer = b.formatGlobalInitializerValue(g.Items[0].Value, globalType)
238+ }
239+ }
240+
241+ fmt.Fprintf(b.out, "@%s = global %s %s, align %d\n", g.Name, globalType, initializer, g.Align)
242+ b.tempTypes["@"+g.Name] = globalType + "*"
243+ }
244+ b.out.WriteString("\n")
245+}
246+
247+func (b *llvmBackend) formatGlobalInitializerValue(v ir.Value, targetType string) string {
248+ switch val := v.(type) {
249+ case *ir.Const:
250+ return fmt.Sprintf("%d", val.Value)
251+ case *ir.Global:
252+ strContent, isString := b.prog.IsStringLabel(val.Name)
253+ if isString {
254+ strType := fmt.Sprintf("[%d x i8]", len(strContent)+1)
255+ gep := fmt.Sprintf("getelementptr inbounds (%s, %s* @%s, i64 0, i64 0)", strType, strType, val.Name)
256+ if targetType != "i8*" {
257+ return fmt.Sprintf("ptrtoint (i8* %s to %s)", gep, targetType)
258+ }
259+ return gep
260+ }
261+ sourceType := b.getType(val)
262+ if !strings.HasSuffix(sourceType, "*") {
263+ sourceType += "*"
264+ }
265+ return fmt.Sprintf("bitcast (%s @%s to %s)", sourceType, val.Name, targetType)
266+ default:
267+ return "0"
268+ }
269+}
270+
271+func (b *llvmBackend) genFunc(fn *ir.Func) {
272+ b.currentFn = fn
273+ globalTypes := make(map[string]string)
274+ for k, v := range b.tempTypes {
275+ if strings.HasPrefix(k, "@") {
276+ globalTypes[k] = v
277+ }
278+ }
279+ b.tempTypes = globalTypes
280+
281+ retTypeStr := b.formatType(fn.ReturnType)
282+ var params []string
283+ for _, p := range fn.Params {
284+ pName := b.formatValue(p.Val)
285+ pType := b.formatType(p.Typ)
286+ if fn.Name == "main" && p.Name == "argv" {
287+ pType = "i8**"
288+ }
289+ params = append(params, fmt.Sprintf("%s %s", pType, pName))
290+ b.tempTypes[pName] = pType
291+ }
292+ paramStr := strings.Join(params, ", ")
293+ if fn.HasVarargs {
294+ if len(params) > 0 {
295+ paramStr += ", "
296+ }
297+ paramStr += "..."
298+ }
299+
300+ fmt.Fprintf(b.out, "define %s @%s(%s) {\n", retTypeStr, fn.Name, paramStr)
301+ for i, block := range fn.Blocks {
302+ labelName := block.Label.Name
303+ if i == 0 {
304+ labelName = "entry"
305+ }
306+ fmt.Fprintf(b.out, "%s:\n", labelName)
307+ b.genBlock(block)
308+ }
309+ b.out.WriteString("}\n")
310+}
311+
312+func (b *llvmBackend) genBlock(block *ir.BasicBlock) {
313+ var deferredCasts []string
314+ phiEndIndex := 0
315+
316+ for i, instr := range block.Instructions {
317+ if instr.Op != ir.OpPhi {
318+ phiEndIndex = i
319+ break
320+ }
321+ }
322+ if phiEndIndex == 0 && len(block.Instructions) > 0 && block.Instructions[0].Op == ir.OpPhi {
323+ phiEndIndex = len(block.Instructions)
324+ }
325+
326+ for _, instr := range block.Instructions[:phiEndIndex] {
327+ if instr.Op == ir.OpPhi {
328+ cast := b.genPhi(instr)
329+ if cast != "" {
330+ deferredCasts = append(deferredCasts, cast)
331+ }
332+ }
333+ }
334+
335+ for _, cast := range deferredCasts {
336+ b.out.WriteString(cast)
337+ }
338+
339+ for _, instr := range block.Instructions[phiEndIndex:] {
340+ b.genInstr(instr)
341+ }
342+}
343+
344+func (b *llvmBackend) genInstr(instr *ir.Instruction) {
345+ if instr.Op == ir.OpPhi {
346+ return
347+ }
348+
349+ resultName := ""
350+ if instr.Result != nil {
351+ resultName = b.formatValue(instr.Result)
352+ }
353+
354+ b.out.WriteString("\t")
355+
356+ switch instr.Op {
357+ case ir.OpAlloc:
358+ align := instr.Align
359+ if align == 0 {
360+ align = b.cfg.StackAlignment
361+ }
362+ sizeVal := b.prepareArg(instr.Args[0], b.wordType)
363+ fmt.Fprintf(b.out, "%s = alloca i8, %s %s, align %d\n", resultName, b.wordType, sizeVal, align)
364+ b.tempTypes[resultName] = "i8*"
365+
366+ case ir.OpLoad:
367+ valType := b.formatType(instr.Typ)
368+ ptrType := valType + "*"
369+ ptrVal := b.prepareArg(instr.Args[0], ptrType)
370+ fmt.Fprintf(b.out, "%s = load %s, %s %s, align %d\n", resultName, valType, ptrType, ptrVal, ir.SizeOfType(instr.Typ, b.cfg.WordSize))
371+ b.tempTypes[resultName] = valType
372+
373+ case ir.OpStore:
374+ valType := b.formatType(instr.Typ)
375+ ptrType := valType + "*"
376+ ptrVal := b.prepareArg(instr.Args[1], ptrType)
377+ valStr := b.prepareArg(instr.Args[0], valType)
378+ fmt.Fprintf(b.out, "store %s %s, %s %s, align %d\n", valType, valStr, ptrType, ptrVal, ir.SizeOfType(instr.Typ, b.cfg.WordSize))
379+
380+ case ir.OpAdd:
381+ b.genAdd(instr)
382+
383+ case ir.OpCast:
384+ targetType := b.formatType(instr.Typ)
385+ sourceValStr := b.prepareArg(instr.Args[0], targetType)
386+ if sourceValStr != resultName {
387+ sourceType := b.getType(instr.Args[0])
388+ if strings.HasSuffix(targetType, "*") {
389+ fmt.Fprintf(b.out, "%s = bitcast %s %s to %s\n", resultName, sourceType, sourceValStr, targetType)
390+ } else {
391+ fmt.Fprintf(b.out, "%s = add %s %s, 0\n", resultName, targetType, sourceValStr)
392+ }
393+ }
394+ b.tempTypes[resultName] = targetType
395+
396+ case ir.OpCall:
397+ b.genCall(instr)
398+
399+ case ir.OpJmp:
400+ fmt.Fprintf(b.out, "br label %%%s\n", instr.Args[0].String())
401+
402+ case ir.OpJnz:
403+ condVal := b.prepareArg(instr.Args[0], "i1")
404+ fmt.Fprintf(b.out, "br i1 %s, label %%%s, label %%%s\n", condVal, instr.Args[1].String(), instr.Args[2].String())
405+
406+ case ir.OpRet:
407+ if len(instr.Args) > 0 && instr.Args[0] != nil {
408+ retType := b.formatType(b.currentFn.ReturnType)
409+ var retVal string
410+ if c, ok := instr.Args[0].(*ir.Const); ok && c.Value == 0 && strings.HasSuffix(retType, "*") {
411+ retVal = "null"
412+ } else {
413+ retVal = b.prepareArg(instr.Args[0], retType)
414+ }
415+ fmt.Fprintf(b.out, "ret %s %s\n", retType, retVal)
416+ } else {
417+ fmt.Fprintf(b.out, "ret void\n")
418+ }
419+
420+ case ir.OpCEq, ir.OpCNeq, ir.OpCLt, ir.OpCGt, ir.OpCLe, ir.OpCGe:
421+ opStr, predicate := b.formatOp(instr.Op)
422+ lhsType := b.getType(instr.Args[0])
423+ rhsType := b.getType(instr.Args[1])
424+ valType := lhsType
425+ if _, ok := instr.Args[0].(*ir.Const); ok {
426+ valType = rhsType
427+ }
428+
429+ lhs := b.prepareArg(instr.Args[0], valType)
430+ var rhs string
431+
432+ isPtrComparison := strings.HasSuffix(valType, "*")
433+ if c, ok := instr.Args[1].(*ir.Const); ok && c.Value == 0 && isPtrComparison {
434+ rhs = "null"
435+ } else if c, ok := instr.Args[0].(*ir.Const); ok && c.Value == 0 && strings.HasSuffix(rhsType, "*") {
436+ valType = rhsType
437+ lhs = b.prepareArg(instr.Args[0], valType)
438+ rhs = "null"
439+ } else {
440+ rhs = b.prepareArg(instr.Args[1], valType)
441+ }
442+
443+ i1Temp := b.newBackendTemp()
444+ fmt.Fprintf(b.out, "%s = %s %s %s %s, %s\n", i1Temp, opStr, predicate, valType, lhs, rhs)
445+ b.tempTypes[i1Temp] = "i1"
446+ fmt.Fprintf(b.out, "\t%s = zext i1 %s to %s\n", resultName, i1Temp, b.wordType)
447+ b.tempTypes[resultName] = b.wordType
448+
449+ default: // Other Binary ops
450+ opStr, _ := b.formatOp(instr.Op)
451+ valType := b.formatType(instr.Typ)
452+ lhs := b.prepareArg(instr.Args[0], valType)
453+ rhs := b.prepareArg(instr.Args[1], valType)
454+ fmt.Fprintf(b.out, "%s = %s %s %s, %s\n", resultName, opStr, valType, lhs, rhs)
455+ b.tempTypes[resultName] = valType
456+ }
457+}
458+
459+func (b *llvmBackend) genPhi(instr *ir.Instruction) string {
460+ resultName := b.formatValue(instr.Result)
461+ originalResultType := b.formatType(instr.Typ)
462+ phiType := originalResultType
463+
464+ hasPtrInput, hasIntInput := false, false
465+ for i := 1; i < len(instr.Args); i += 2 {
466+ argType := b.getType(instr.Args[i])
467+ if strings.HasSuffix(argType, "*") {
468+ hasPtrInput = true
469+ } else if strings.HasPrefix(argType, "i") {
470+ hasIntInput = true
471+ }
472+ }
473+
474+ if hasPtrInput && hasIntInput {
475+ phiType = "i8*"
476+ }
477+
478+ var pairs []string
479+ for i := 0; i < len(instr.Args); i += 2 {
480+ labelName := instr.Args[i].String()
481+ if labelName == "start" {
482+ labelName = "entry"
483+ }
484+ val := b.prepareArgForPhi(instr.Args[i+1], phiType)
485+ pairs = append(pairs, fmt.Sprintf("[ %s, %%%s ]", val, labelName))
486+ }
487+
488+ phiResultName := resultName
489+ if phiType != originalResultType {
490+ phiResultName = b.newBackendTemp()
491+ }
492+
493+ fmt.Fprintf(b.out, "\t%s = phi %s %s\n", phiResultName, phiType, strings.Join(pairs, ", "))
494+ b.tempTypes[phiResultName] = phiType
495+
496+ if phiResultName != resultName {
497+ b.tempTypes[resultName] = originalResultType
498+ return fmt.Sprintf("\t%s\n", b.formatCast(phiResultName, resultName, phiType, originalResultType))
499+ }
500+ return ""
501+}
502+
503+func (b *llvmBackend) prepareArgForPhi(v ir.Value, targetType string) string {
504+ valStr := b.formatValue(v)
505+ currentType := b.getType(v)
506+
507+ if currentType == targetType || currentType == "unknown" {
508+ return valStr
509+ }
510+
511+ if c, isConst := v.(*ir.Const); isConst {
512+ if strings.HasSuffix(targetType, "*") && c.Value == 0 {
513+ return "null"
514+ }
515+ if strings.HasSuffix(targetType, "*") {
516+ return fmt.Sprintf("inttoptr (%s %s to %s)", currentType, valStr, targetType)
517+ }
518+ }
519+
520+ if _, isGlobal := v.(*ir.Global); isGlobal {
521+ return fmt.Sprintf("bitcast (%s %s to %s)", currentType, valStr, targetType)
522+ }
523+ return valStr
524+}
525+
526+func (b *llvmBackend) genAdd(instr *ir.Instruction) {
527+ resultName := b.formatValue(instr.Result)
528+ lhs, rhs := instr.Args[0], instr.Args[1]
529+
530+ _, isLhsGlobal := lhs.(*ir.Global)
531+ _, isRhsGlobal := rhs.(*ir.Global)
532+ isLhsFunc := isLhsGlobal && (b.prog.FindFunc(lhs.String()) != nil || b.funcSigs[lhs.String()] != "")
533+ isRhsFunc := isRhsGlobal && (b.prog.FindFunc(rhs.String()) != nil || b.funcSigs[rhs.String()] != "")
534+
535+ lhsType, rhsType := b.getType(lhs), b.getType(rhs)
536+ isLhsPtr := strings.HasSuffix(lhsType, "*") || (isLhsGlobal && !isLhsFunc)
537+ isRhsPtr := strings.HasSuffix(rhsType, "*") || (isRhsGlobal && !isRhsFunc)
538+
539+ // Case 1: Pointer + Integer arithmetic
540+ if (isLhsPtr && !isRhsPtr) || (!isLhsPtr && isRhsPtr) {
541+ var ptr ir.Value
542+ var ptrType string
543+ var offset ir.Value
544+ if isLhsPtr {
545+ ptr, ptrType, offset = lhs, lhsType, rhs
546+ } else {
547+ ptr, ptrType, offset = rhs, rhsType, lhs
548+ }
549+
550+ if ptrType == "unknown" {
551+ ptrType = "i8*"
552+ }
553+
554+ i8PtrVal := b.prepareArg(ptr, "i8*")
555+ offsetVal := b.prepareArg(offset, b.wordType)
556+
557+ gepResultTemp := b.newBackendTemp()
558+ fmt.Fprintf(b.out, "%s = getelementptr i8, i8* %s, %s %s\n", gepResultTemp, i8PtrVal, b.wordType, offsetVal)
559+ b.tempTypes[gepResultTemp] = "i8*"
560+
561+ if ptrType != "i8*" {
562+ fmt.Fprintf(b.out, "\t%s = bitcast i8* %s to %s\n", resultName, gepResultTemp, ptrType)
563+ } else {
564+ fmt.Fprintf(b.out, "\t%s = bitcast i8* %s to i8*\n", resultName, gepResultTemp)
565+ }
566+ b.tempTypes[resultName] = ptrType
567+ return
568+ }
569+
570+ // Case 2: Pointer + Pointer (B-specific, treat as integer addition)
571+ if isLhsPtr && isRhsPtr {
572+ lhsInt := b.prepareArg(lhs, b.wordType)
573+ rhsInt := b.prepareArg(rhs, b.wordType)
574+ resultInt := b.newBackendTemp()
575+
576+ fmt.Fprintf(b.out, "%s = add %s %s, %s\n", resultInt, b.wordType, lhsInt, rhsInt)
577+ b.tempTypes[resultInt] = b.wordType
578+
579+ fmt.Fprintf(b.out, "\t%s = inttoptr %s %s to %s\n", resultName, b.wordType, resultInt, lhsType)
580+ b.tempTypes[resultName] = lhsType
581+ return
582+ }
583+
584+ // Case 3: Operands are not pointers.
585+ resultType := b.formatType(instr.Typ)
586+ if strings.HasSuffix(resultType, "*") {
587+ lhsInt := b.prepareArg(lhs, b.wordType)
588+ rhsInt := b.prepareArg(rhs, b.wordType)
589+ resultInt := b.newBackendTemp()
590+
591+ fmt.Fprintf(b.out, "%s = add %s %s, %s\n", resultInt, b.wordType, lhsInt, rhsInt)
592+ b.tempTypes[resultInt] = b.wordType
593+
594+ fmt.Fprintf(b.out, "\t%s = inttoptr %s %s to %s\n", resultName, b.wordType, resultInt, resultType)
595+ b.tempTypes[resultName] = resultType
596+ } else {
597+ lhsVals := b.prepareArg(lhs, resultType)
598+ rhsVals := b.prepareArg(rhs, resultType)
599+ fmt.Fprintf(b.out, "%s = add %s %s, %s\n", resultName, resultType, lhsVals, rhsVals)
600+ b.tempTypes[resultName] = resultType
601+ }
602+}
603+
604+func (b *llvmBackend) genCall(instr *ir.Instruction) {
605+ resultName := ""
606+ if instr.Result != nil {
607+ resultName = b.formatValue(instr.Result)
608+ }
609+
610+ callee := instr.Args[0]
611+ calleeStr := b.formatValue(callee)
612+ retType := b.getFuncSig(callee.String())
613+
614+ var argParts []string
615+ for i, arg := range instr.Args[1:] {
616+ targetType := b.wordType
617+ if instr.ArgTypes != nil && i < len(instr.ArgTypes) {
618+ targetType = b.formatType(instr.ArgTypes[i])
619+ } else if g, ok := arg.(*ir.Global); ok {
620+ if _, isString := b.prog.IsStringLabel(g.Name); isString {
621+ targetType = "i8*"
622+ }
623+ }
624+ valStr := b.prepareArg(arg, targetType)
625+ argParts = append(argParts, fmt.Sprintf("%s %s", targetType, valStr))
626+ }
627+
628+ if _, isGlobal := callee.(*ir.Global); !isGlobal {
629+ funcPtrType := fmt.Sprintf("%s (...)*", retType)
630+ calleeStr = b.prepareArg(callee, funcPtrType)
631+ }
632+
633+ callStr := fmt.Sprintf("call %s %s(%s)", retType, calleeStr, strings.Join(argParts, ", "))
634+
635+ if resultName != "" && retType != "void" {
636+ fmt.Fprintf(b.out, "%s = %s\n", resultName, callStr)
637+ b.tempTypes[resultName] = retType
638+ } else {
639+ fmt.Fprintf(b.out, "%s\n", callStr)
640+ }
641+}
642+
643+func (b *llvmBackend) prepareArg(v ir.Value, targetType string) string {
644+ valStr := b.formatValue(v)
645+ if g, ok := v.(*ir.Global); ok {
646+ isFunc := b.prog.FindFunc(g.Name) != nil || b.funcSigs[g.Name] != ""
647+ if isFunc {
648+ if strings.HasPrefix(targetType, "i") && !strings.HasSuffix(targetType, "*") {
649+ funcSig := b.getFuncSig(g.Name) + " (...)*"
650+ castTemp := b.newBackendTemp()
651+ fmt.Fprintf(b.out, "\t%s = ptrtoint %s @%s to %s\n", castTemp, funcSig, g.Name, targetType)
652+ b.tempTypes[castTemp] = targetType
653+ return castTemp
654+ }
655+ return "@" + g.Name
656+ }
657+ }
658+
659+ if _, ok := v.(*ir.Const); ok {
660+ return valStr
661+ }
662+
663+ currentType := b.getType(v)
664+ if currentType == targetType || currentType == "unknown" {
665+ return valStr
666+ }
667+
668+ castTemp := b.newBackendTemp()
669+ b.out.WriteString("\t")
670+ b.out.WriteString(b.formatCast(valStr, castTemp, currentType, targetType))
671+ b.out.WriteString("\n")
672+ b.tempTypes[castTemp] = targetType
673+ return castTemp
674+}
675+
676+func (b *llvmBackend) formatCast(sourceName, targetName, sourceType, targetType string) string {
677+ isSourcePtr, isTargetPtr := strings.HasSuffix(sourceType, "*"), strings.HasSuffix(targetType, "*")
678+ isSourceInt, isTargetInt := strings.HasPrefix(sourceType, "i") && !isSourcePtr, strings.HasPrefix(targetType, "i") && !isTargetPtr
679+ isSourceFloat, isTargetFloat := sourceType == "float" || sourceType == "double", targetType == "float" || targetType == "double"
680+
681+ var castOp string
682+ switch {
683+ case sourceType == "i1" && isTargetInt:
684+ castOp = "zext"
685+ case isSourceInt && targetType == "i1":
686+ return fmt.Sprintf("%s = icmp ne %s %s, 0", targetName, sourceType, sourceName)
687+ case isSourceInt && isTargetPtr:
688+ castOp = "inttoptr"
689+ case isSourcePtr && isTargetInt:
690+ castOp = "ptrtoint"
691+ case isSourcePtr && isTargetPtr:
692+ castOp = "bitcast"
693+ case isSourceInt && isTargetInt:
694+ sourceBits, _ := strconv.Atoi(strings.TrimPrefix(sourceType, "i"))
695+ targetBits, _ := strconv.Atoi(strings.TrimPrefix(targetType, "i"))
696+ castOp = "sext"
697+ if sourceBits > targetBits {
698+ castOp = "trunc"
699+ }
700+ case isSourceInt && isTargetFloat:
701+ castOp = "sitofp"
702+ case isSourceFloat && isTargetInt:
703+ castOp = "fptosi"
704+ case isSourceFloat && isTargetFloat:
705+ castOp = "fpext"
706+ if sourceType == "double" {
707+ castOp = "fptrunc"
708+ }
709+ default:
710+ castOp = "bitcast"
711+ }
712+ return fmt.Sprintf("%s = %s %s %s to %s", targetName, castOp, sourceType, sourceName, targetType)
713+}
714+
715+func (b *llvmBackend) getType(v ir.Value) string {
716+ valStr := b.formatValue(v)
717+ if t, ok := b.tempTypes[valStr]; ok {
718+ return t
719+ }
720+ if _, ok := v.(*ir.Const); ok {
721+ return b.wordType
722+ }
723+ if g, ok := v.(*ir.Global); ok {
724+ if _, isString := b.prog.IsStringLabel(g.Name); isString {
725+ return "i8*"
726+ }
727+ }
728+ return "unknown"
729+}
730+
731+func (b *llvmBackend) newBackendTemp() string {
732+ name := fmt.Sprintf("%%.b%d", b.prog.GetBackendTempCount())
733+ b.prog.IncBackendTempCount()
734+ return name
735+}
736+
737+func (b *llvmBackend) formatValue(v ir.Value) string {
738+ if v == nil {
739+ return "void"
740+ }
741+ switch val := v.(type) {
742+ case *ir.Const:
743+ return fmt.Sprintf("%d", val.Value)
744+ case *ir.Global:
745+ return "@" + val.Name
746+ case *ir.Temporary:
747+ safeName := strings.NewReplacer(".", "_", "[", "_", "]", "_").Replace(val.Name)
748+ if safeName != "" {
749+ return fmt.Sprintf("%%.%s_%d", safeName, val.ID)
750+ }
751+ return fmt.Sprintf("%%t%d", val.ID)
752+ case *ir.Label:
753+ return "%" + val.Name
754+ case *ir.CastValue:
755+ return b.formatValue(val.Value)
756+ default:
757+ return ""
758+ }
759+}
760+
761+func (b *llvmBackend) formatType(t ir.Type) string {
762+ switch t {
763+ case ir.TypeB:
764+ return "i8"
765+ case ir.TypeH:
766+ return "i16"
767+ case ir.TypeW:
768+ return "i32"
769+ case ir.TypeL:
770+ return "i64"
771+ case ir.TypeS:
772+ return "float"
773+ case ir.TypeD:
774+ return "double"
775+ case ir.TypeNone:
776+ return "void"
777+ case ir.TypePtr:
778+ return "i8*"
779+ default:
780+ return b.wordType
781+ }
782+}
783+
784+func (b *llvmBackend) formatOp(op ir.Op) (string, string) {
785+ switch op {
786+ case ir.OpAdd:
787+ return "add", ""
788+ case ir.OpSub:
789+ return "sub", ""
790+ case ir.OpMul:
791+ return "mul", ""
792+ case ir.OpDiv:
793+ return "sdiv", ""
794+ case ir.OpRem:
795+ return "srem", ""
796+ case ir.OpAnd:
797+ return "and", ""
798+ case ir.OpOr:
799+ return "or", ""
800+ case ir.OpXor:
801+ return "xor", ""
802+ case ir.OpShl:
803+ return "shl", ""
804+ case ir.OpShr:
805+ return "ashr", ""
806+ case ir.OpCEq:
807+ return "icmp", "eq"
808+ case ir.OpCNeq:
809+ return "icmp", "ne"
810+ case ir.OpCLt:
811+ return "icmp", "slt"
812+ case ir.OpCGt:
813+ return "icmp", "sgt"
814+ case ir.OpCLe:
815+ return "icmp", "sle"
816+ case ir.OpCGe:
817+ return "icmp", "sge"
818+ default:
819+ return "unknown_op", ""
820+ }
821+}
822+
823+func (b *llvmBackend) escapeString(s string) string {
824+ var sb strings.Builder
825+ for _, byteVal := range []byte(s) {
826+ if byteVal < 32 || byteVal > 126 || byteVal == '"' || byteVal == '\\' {
827+ sb.WriteString(fmt.Sprintf("\\%02X", byteVal))
828+ } else {
829+ sb.WriteByte(byteVal)
830+ }
831+ }
832+ return sb.String()
833+}
+307,
-0
1@@ -0,0 +1,307 @@
2+package codegen
3+
4+import (
5+ "bytes"
6+ "fmt"
7+ "strconv"
8+ "strings"
9+
10+ "github.com/xplshn/gbc/pkg/config"
11+ "github.com/xplshn/gbc/pkg/ir"
12+ "modernc.org/libqbe"
13+)
14+
15+// qbeBackend implements the Backend interface for the QBE intermediate language.
16+type qbeBackend struct {
17+ out *strings.Builder
18+ prog *ir.Program
19+}
20+
21+// NewQBEBackend creates a new instance of the QBE backend.
22+func NewQBEBackend() Backend {
23+ return &qbeBackend{}
24+}
25+
26+// Generate translates a generic IR program into QBE intermediate language text.
27+func (b *qbeBackend) Generate(prog *ir.Program, cfg *config.Config) (*bytes.Buffer, error) {
28+ var qbeIRBuilder strings.Builder
29+ b.out = &qbeIRBuilder
30+ b.prog = prog
31+
32+ b.gen()
33+
34+ qbeIR := qbeIRBuilder.String()
35+ var asmBuf bytes.Buffer
36+ if err := libqbe.Main(cfg.BackendTarget, "input.ssa", strings.NewReader(qbeIR), &asmBuf, nil); err != nil {
37+ return nil, fmt.Errorf("\n--- QBE Compilation Failed ---\nGenerated IR:\n%s\n\nlibqbe error: %w", qbeIR, err)
38+ }
39+ return &asmBuf, nil
40+}
41+
42+func (b *qbeBackend) gen() {
43+ for _, g := range b.prog.Globals {
44+ b.genGlobal(g)
45+ }
46+
47+ if len(b.prog.Strings) > 0 {
48+ b.out.WriteString("\n# --- String Literals ---\n")
49+ for s, label := range b.prog.Strings {
50+ escaped := strconv.Quote(s)
51+ fmt.Fprintf(b.out, "data $%s = { b %s, b 0 }\n", label, escaped)
52+ }
53+ }
54+
55+ for _, fn := range b.prog.Funcs {
56+ b.genFunc(fn)
57+ }
58+}
59+
60+func (b *qbeBackend) genGlobal(g *ir.Data) {
61+ fmt.Fprintf(b.out, "data $%s = align %d { ", g.Name, g.Align)
62+ for i, item := range g.Items {
63+ if item.Count > 0 { // Zero-initialized
64+ fmt.Fprintf(b.out, "z %d", item.Count*int(ir.SizeOfType(item.Typ, b.prog.WordSize)))
65+ } else {
66+ fmt.Fprintf(b.out, "%s %s", b.formatType(item.Typ), b.formatValue(item.Value))
67+ }
68+ if i < len(g.Items)-1 {
69+ b.out.WriteString(", ")
70+ }
71+ }
72+ b.out.WriteString(" }\n")
73+}
74+
75+func (b *qbeBackend) genFunc(fn *ir.Func) {
76+ retTypeStr := b.formatType(fn.ReturnType)
77+ if retTypeStr != "" {
78+ retTypeStr = " " + retTypeStr
79+ }
80+
81+ fmt.Fprintf(b.out, "\nexport function%s $%s(", retTypeStr, fn.Name)
82+
83+ for i, p := range fn.Params {
84+ fmt.Fprintf(b.out, "%s %s", b.formatType(p.Typ), b.formatValue(p.Val))
85+ if i < len(fn.Params)-1 {
86+ b.out.WriteString(", ")
87+ }
88+ }
89+
90+ if fn.HasVarargs {
91+ if len(fn.Params) > 0 {
92+ b.out.WriteString(", ")
93+ }
94+ b.out.WriteString("...")
95+ }
96+ b.out.WriteString(") {\n")
97+
98+ for _, block := range fn.Blocks {
99+ b.genBlock(block)
100+ }
101+
102+ b.out.WriteString("}\n")
103+}
104+
105+func (b *qbeBackend) genBlock(block *ir.BasicBlock) {
106+ fmt.Fprintf(b.out, "@%s\n", block.Label.Name)
107+ for _, instr := range block.Instructions {
108+ b.genInstr(instr)
109+ }
110+}
111+
112+func (b *qbeBackend) genInstr(instr *ir.Instruction) {
113+ b.out.WriteString("\t")
114+ if instr.Result != nil {
115+ resultType := instr.Typ
116+ isComparison := false
117+ switch instr.Op {
118+ case ir.OpCEq, ir.OpCNeq, ir.OpCLt, ir.OpCGt, ir.OpCLe, ir.OpCGe:
119+ isComparison = true
120+ }
121+
122+ if isComparison {
123+ resultType = ir.GetType(nil, b.prog.WordSize)
124+ }
125+
126+ if instr.Op == ir.OpLoad && (instr.Typ == ir.TypeB || instr.Typ == ir.TypeH) {
127+ resultType = ir.GetType(nil, b.prog.WordSize)
128+ }
129+
130+ fmt.Fprintf(b.out, "%s =%s ", b.formatValue(instr.Result), b.formatType(resultType))
131+ }
132+
133+ opStr, isCall := b.formatOp(instr)
134+ b.out.WriteString(opStr)
135+
136+ if isCall {
137+ fmt.Fprintf(b.out, " %s(", b.formatValue(instr.Args[0]))
138+ for i, arg := range instr.Args[1:] {
139+ argType := ir.GetType(nil, b.prog.WordSize)
140+ if instr.ArgTypes != nil && i < len(instr.ArgTypes) {
141+ argType = instr.ArgTypes[i]
142+ }
143+ if argType == ir.TypeB || argType == ir.TypeH {
144+ argType = ir.GetType(nil, b.prog.WordSize)
145+ }
146+
147+ fmt.Fprintf(b.out, "%s %s", b.formatType(argType), b.formatValue(arg))
148+ if i < len(instr.Args)-2 {
149+ b.out.WriteString(", ")
150+ }
151+ }
152+ b.out.WriteString(")\n")
153+ return
154+ }
155+
156+ if instr.Op == ir.OpPhi {
157+ for i := 0; i < len(instr.Args); i += 2 {
158+ fmt.Fprintf(b.out, " @%s %s", instr.Args[i].String(), b.formatValue(instr.Args[i+1]))
159+ if i+2 < len(instr.Args) {
160+ b.out.WriteString(",")
161+ }
162+ }
163+ } else {
164+ for i, arg := range instr.Args {
165+ b.out.WriteString(" ")
166+ if arg != nil {
167+ b.out.WriteString(b.formatValue(arg))
168+ }
169+ if i < len(instr.Args)-1 {
170+ b.out.WriteString(",")
171+ }
172+ }
173+ }
174+ b.out.WriteString("\n")
175+}
176+
177+func (b *qbeBackend) formatValue(v ir.Value) string {
178+ if v == nil {
179+ return ""
180+ }
181+ switch val := v.(type) {
182+ case *ir.Const:
183+ return fmt.Sprintf("%d", val.Value)
184+ case *ir.FloatConst:
185+ return fmt.Sprintf("%s_%f", b.formatType(val.Typ), val.Value)
186+ case *ir.Global:
187+ return "$" + val.Name
188+ case *ir.Temporary:
189+ safeName := strings.NewReplacer(".", "_", "[", "_", "]", "_").Replace(val.Name)
190+ if safeName != "" {
191+ return fmt.Sprintf("%%.%s_%d", safeName, val.ID)
192+ }
193+ return fmt.Sprintf("%%t%d", val.ID)
194+ case *ir.Label:
195+ return "@" + val.Name
196+ default:
197+ return ""
198+ }
199+}
200+
201+func (b *qbeBackend) formatType(t ir.Type) string {
202+ switch t {
203+ case ir.TypeB:
204+ return "b"
205+ case ir.TypeH:
206+ return "h"
207+ case ir.TypeW:
208+ return "w"
209+ case ir.TypeL:
210+ return "l"
211+ case ir.TypeS:
212+ return "s"
213+ case ir.TypeD:
214+ return "d"
215+ case ir.TypePtr:
216+ return b.formatType(ir.GetType(nil, b.prog.WordSize))
217+ default:
218+ return ""
219+ }
220+}
221+
222+func (b *qbeBackend) getCmpInstType(t ir.Type) string {
223+ if t == ir.TypeB || t == ir.TypeH {
224+ return b.formatType(ir.GetType(nil, b.prog.WordSize))
225+ }
226+ return b.formatType(t)
227+}
228+
229+func (b *qbeBackend) formatOp(instr *ir.Instruction) (opStr string, isCall bool) {
230+ typ := instr.Typ
231+ typeStr := b.formatType(typ)
232+ switch instr.Op {
233+ case ir.OpAlloc:
234+ // QBE's stack alloc instruction is based on word size, not arbitrary alignment.
235+ return "alloc" + strconv.Itoa(b.prog.WordSize), false
236+ case ir.OpLoad:
237+ switch typ {
238+ case ir.TypeB:
239+ return "loadub", false
240+ case ir.TypeH:
241+ return "loaduh", false
242+ case ir.TypePtr:
243+ return "load" + b.formatType(ir.GetType(nil, b.prog.WordSize)), false
244+ default:
245+ return "load" + typeStr, false
246+ }
247+ case ir.OpStore:
248+ return "store" + typeStr, false
249+ case ir.OpBlit:
250+ return "blit", false
251+ case ir.OpAdd:
252+ return "add", false
253+ case ir.OpSub:
254+ return "sub", false
255+ case ir.OpMul:
256+ return "mul", false
257+ case ir.OpDiv:
258+ return "div", false
259+ case ir.OpRem:
260+ return "rem", false
261+ case ir.OpAnd:
262+ return "and", false
263+ case ir.OpOr:
264+ return "or", false
265+ case ir.OpXor:
266+ return "xor", false
267+ case ir.OpShl:
268+ return "shl", false
269+ case ir.OpShr:
270+ return "shr", false
271+ case ir.OpCEq:
272+ return "ceq" + b.getCmpInstType(typ), false
273+ case ir.OpCNeq:
274+ return "cne" + b.getCmpInstType(typ), false
275+ case ir.OpCLt:
276+ if typ == ir.TypeS || typ == ir.TypeD {
277+ return "clt" + typeStr, false
278+ }
279+ return "cslt" + b.getCmpInstType(typ), false
280+ case ir.OpCGt:
281+ if typ == ir.TypeS || typ == ir.TypeD {
282+ return "cgt" + typeStr, false
283+ }
284+ return "csgt" + b.getCmpInstType(typ), false
285+ case ir.OpCLe:
286+ if typ == ir.TypeS || typ == ir.TypeD {
287+ return "cle" + typeStr, false
288+ }
289+ return "csle" + b.getCmpInstType(typ), false
290+ case ir.OpCGe:
291+ if typ == ir.TypeS || typ == ir.TypeD {
292+ return "cge" + typeStr, false
293+ }
294+ return "csge" + b.getCmpInstType(typ), false
295+ case ir.OpJmp:
296+ return "jmp", false
297+ case ir.OpJnz:
298+ return "jnz", false
299+ case ir.OpRet:
300+ return "ret", false
301+ case ir.OpCall:
302+ return "call", true
303+ case ir.OpPhi:
304+ return "phi", false
305+ default:
306+ return "unknown_op", false
307+ }
308+}
+141,
-75
1@@ -5,6 +5,7 @@ import (
2 "os"
3 "strings"
4
5+ "github.com/xplshn/gbc/pkg/cli"
6 "modernc.org/libqbe"
7 )
8
9@@ -54,17 +55,48 @@ type Info struct {
10 Description string
11 }
12
13-type Config struct {
14- Features map[Feature]Info
15- Warnings map[Warning]Info
16- FeatureMap map[string]Feature
17- WarningMap map[string]Warning
18- StdName string
19- TargetArch string
20- QbeTarget string
21+// Backend-specific properties
22+type Target struct {
23+ GOOS string
24+ GOARCH string
25+ BackendName string // "qbe", "llvm"
26+ BackendTarget string // "amd64_sysv", "x86_64-unknown-linux-musl"
27+ WordSize int
28+ WordType string // QBE type char
29+ StackAlignment int
30+}
31+
32+var archTranslations = map[string]string{
33+ "amd64": "x86_64",
34+ "386": "i686",
35+ "arm64": "aarch64",
36+ "arm": "arm",
37+ "riscv64": "riscv64",
38+ "x86_64": "amd64",
39+ "i386": "386",
40+ "i686": "386",
41+ "aarch64": "arm64",
42+}
43+
44+var archProperties = map[string]struct {
45 WordSize int
46 WordType string
47 StackAlignment int
48+}{
49+ "amd64": {WordSize: 8, WordType: "l", StackAlignment: 16},
50+ "arm64": {WordSize: 8, WordType: "l", StackAlignment: 16},
51+ "386": {WordSize: 4, WordType: "w", StackAlignment: 8},
52+ "arm": {WordSize: 4, WordType: "w", StackAlignment: 8},
53+ "riscv64": {WordSize: 8, WordType: "l", StackAlignment: 16},
54+}
55+
56+type Config struct {
57+ Features map[Feature]Info
58+ Warnings map[Warning]Info
59+ FeatureMap map[string]Feature
60+ WarningMap map[string]Warning
61+ StdName string
62+ Target
63 }
64
65 func NewConfig() *Config {
66@@ -120,28 +152,70 @@ func NewConfig() *Config {
67 return cfg
68 }
69
70-// SetTarget configures the compiler for a specific architecture and QBE target.
71-func (c *Config) SetTarget(goos, goarch, qbeTarget string) {
72- if qbeTarget == "" {
73- c.QbeTarget = libqbe.DefaultTarget(goos, goarch)
74- fmt.Fprintf(os.Stderr, "gbc: info: no target specified, defaulting to host target '%s'\n", c.QbeTarget)
75- } else {
76- c.QbeTarget = qbeTarget
77- fmt.Fprintf(os.Stderr, "gbc: info: using specified target '%s'\n", c.QbeTarget)
78+// SetTarget configures the compiler for a specific architecture and backend target
79+func (c *Config) SetTarget(hostOS, hostArch, targetFlag string) {
80+ // Init with host defaults
81+ c.GOOS, c.GOARCH, c.BackendName = hostOS, hostArch, "qbe"
82+
83+ // Parse target flag: <backend>/<target_string>
84+ if targetFlag != "" {
85+ parts := strings.SplitN(targetFlag, "/", 2)
86+ c.BackendName = parts[0]
87+ if len(parts) > 1 {
88+ c.BackendTarget = parts[1]
89+ }
90 }
91
92- c.TargetArch = goarch
93+ // Valid QBE targets |https://pkg.go.dev/modernc.org/libqbe#hdr-Supported_targets|
94+ validQBETargets := map[string]string{
95+ "amd64_apple": "amd64",
96+ "amd64_sysv": "amd64",
97+ "arm64": "arm64",
98+ "arm64_apple": "arm64",
99+ "rv64": "riscv64",
100+ }
101
102- switch c.QbeTarget {
103- case "amd64_sysv", "amd64_apple", "arm64", "arm64_apple", "rv64":
104- c.WordSize, c.WordType, c.StackAlignment = 8, "l", 16
105- case "arm", "rv32":
106- c.WordSize, c.WordType, c.StackAlignment = 4, "w", 8
107- default:
108- fmt.Fprintf(os.Stderr, "gbc: warning: unrecognized or unsupported QBE target '%s'.\n", c.QbeTarget)
109+ if c.BackendName == "qbe" {
110+ if c.BackendTarget == "" {
111+ c.BackendTarget = libqbe.DefaultTarget(hostOS, hostArch)
112+ fmt.Fprintf(os.Stderr, "gbc: info: no target specified, defaulting to host target '%s' for backend '%s'\n", c.BackendTarget, c.BackendName)
113+ }
114+ if goArch, ok := validQBETargets[c.BackendTarget]; ok {
115+ c.GOARCH = goArch
116+ } else {
117+ fmt.Fprintf(os.Stderr, "gbc: warning: unsupported QBE target '%s', defaulting to GOARCH '%s'\n", c.BackendTarget, c.GOARCH)
118+ }
119+ } else { // llvm
120+ if c.BackendTarget == "" {
121+ tradArch := archTranslations[hostArch]
122+ if tradArch == "" { tradArch = hostArch } // No target architecture specified
123+ // TODO: ? Infer env ("musl", "gnu", etc..?)
124+ c.BackendTarget = fmt.Sprintf("%s-unknown-%s-unknown", tradArch, hostOS)
125+ fmt.Fprintf(os.Stderr, "gbc: info: no target specified, defaulting to host target '%s' for backend '%s'\n", c.BackendTarget, c.BackendName)
126+ }
127+ parts := strings.Split(c.BackendTarget, "-")
128+ if len(parts) > 0 {
129+ if goArch, ok := archTranslations[parts[0]]; ok {
130+ c.GOARCH = goArch
131+ } else {
132+ c.GOARCH = parts[0]
133+ }
134+ }
135+ if len(parts) > 2 && parts[2] != "unknown" {
136+ c.GOOS = parts[2]
137+ }
138+ }
139+
140+ // Set architecture-specific properties
141+ if props, ok := archProperties[c.GOARCH]; ok {
142+ c.WordSize, c.WordType, c.StackAlignment = props.WordSize, props.WordType, props.StackAlignment
143+ } else {
144+ fmt.Fprintf(os.Stderr, "gbc: warning: unrecognized architecture '%s'.\n", c.GOARCH)
145 fmt.Fprintf(os.Stderr, "gbc: warning: defaulting to 64-bit properties. Compilation may fail.\n")
146 c.WordSize, c.WordType, c.StackAlignment = 8, "l", 16
147 }
148+
149+ fmt.Fprintf(os.Stderr, "gbc: info: using backend '%s' with target '%s' (GOOS=%s, GOARCH=%s)\n", c.BackendName, c.BackendTarget, c.GOOS, c.GOARCH)
150 }
151
152 func (c *Config) SetFeature(ft Feature, enabled bool) {
153@@ -212,71 +286,63 @@ func (c *Config) ApplyStd(stdName string) error {
154 return nil
155 }
156
157-func (c *Config) applyFlag(flag string) {
158- trimmed := strings.TrimPrefix(flag, "-")
159- isNo := strings.HasPrefix(trimmed, "Wno-") || strings.HasPrefix(trimmed, "Fno-")
160- enable := !isNo
161-
162- var name string
163- var isWarning bool
164+// ProcessDirectiveFlags parses flags from a directive string using a temporary FlagSet.
165+func (c *Config) ProcessDirectiveFlags(flagStr string) {
166+ fs := cli.NewFlagSet("directive")
167+ var warningFlags, featureFlags []cli.FlagGroupEntry
168+
169+ // Build warning flags for the directive parser.
170+ for i := Warning(0); i < WarnCount; i++ {
171+ pEnable := new(bool)
172+ pDisable := new(bool)
173+ entry := cli.FlagGroupEntry{Name: c.Warnings[i].Name, Prefix: "W", Enabled: pEnable, Disabled: pDisable}
174+ warningFlags = append(warningFlags, entry)
175+ fs.Bool(entry.Enabled, entry.Prefix+entry.Name, "", false, "")
176+ fs.Bool(entry.Disabled, entry.Prefix+"no-"+entry.Name, "", false, "")
177+ }
178
179- switch {
180- case strings.HasPrefix(trimmed, "W"):
181- name = strings.TrimPrefix(trimmed, "W")
182- if isNo {
183- name = strings.TrimPrefix(name, "no-")
184- }
185- isWarning = true
186- case strings.HasPrefix(trimmed, "F"):
187- name = strings.TrimPrefix(trimmed, "F")
188- if isNo {
189- name = strings.TrimPrefix(name, "no-")
190- }
191- default:
192- name = trimmed
193- isWarning = true
194+ // Build feature flags for the directive parser.
195+ for i := Feature(0); i < FeatCount; i++ {
196+ pEnable := new(bool)
197+ pDisable := new(bool)
198+ entry := cli.FlagGroupEntry{Name: c.Features[i].Name, Prefix: "F", Enabled: pEnable, Disabled: pDisable}
199+ featureFlags = append(featureFlags, entry)
200+ fs.Bool(entry.Enabled, entry.Prefix+entry.Name, "", false, "")
201+ fs.Bool(entry.Disabled, entry.Prefix+"no-"+entry.Name, "", false, "")
202 }
203
204- if name == "all" && isWarning {
205- for i := Warning(0); i < WarnCount; i++ {
206- if i != WarnPedantic {
207- c.SetWarning(i, enable)
208- }
209+ // The cli parser expects arguments to start with '-'.
210+ args := strings.Fields(flagStr)
211+ processedArgs := make([]string, len(args))
212+ for i, arg := range args {
213+ if !strings.HasPrefix(arg, "-") {
214+ processedArgs[i] = "-" + arg
215+ } else {
216+ processedArgs[i] = arg
217 }
218- return
219 }
220
221- if name == "pedantic" && isWarning {
222- c.SetWarning(WarnPedantic, true)
223+ if err := fs.Parse(processedArgs); err != nil {
224+ // Silently ignore errors in directives, as they shouldn't halt compilation.
225 return
226 }
227
228- if isWarning {
229- if w, ok := c.WarningMap[name]; ok {
230- c.SetWarning(w, enable)
231+ // Apply parsed directive flags to the configuration.
232+ for i, entry := range warningFlags {
233+ if *entry.Enabled {
234+ c.SetWarning(Warning(i), true)
235 }
236- } else {
237- if f, ok := c.FeatureMap[name]; ok {
238- c.SetFeature(f, enable)
239+ if *entry.Disabled {
240+ c.SetWarning(Warning(i), false)
241 }
242 }
243-}
244
245-func (c *Config) ProcessFlags(visitFlag func(fn func(name string))) {
246- visitFlag(func(name string) {
247- if name == "Wall" || name == "Wno-all" || name == "pedantic" {
248- c.applyFlag("-" + name)
249+ for i, entry := range featureFlags {
250+ if *entry.Enabled {
251+ c.SetFeature(Feature(i), true)
252 }
253- })
254- visitFlag(func(name string) {
255- if name != "Wall" && name != "Wno-all" && name != "pedantic" {
256- c.applyFlag("-" + name)
257+ if *entry.Disabled {
258+ c.SetFeature(Feature(i), false)
259 }
260- })
261-}
262-
263-func (c *Config) ProcessDirectiveFlags(flagStr string) {
264- for _, flag := range strings.Fields(flagStr) {
265- c.applyFlag(flag)
266 }
267 }
+299,
-0
1@@ -0,0 +1,299 @@
2+// package ir defines a lower-level representation of our program, that is independent of any specific backend (QBE, LLVM, ... etc)
3+package ir
4+
5+import (
6+ "github.com/xplshn/gbc/pkg/ast"
7+)
8+
9+// Op represents an operation code for an instruction.
10+type Op int
11+
12+const (
13+ // Memory Operations
14+ OpAlloc Op = iota // Allocate stack memory
15+ OpLoad
16+ OpStore
17+ OpBlit // Memory copy
18+
19+ // Integer Arithmetic/Bitwise Operations
20+ OpAdd
21+ OpSub
22+ OpMul
23+ OpDiv
24+ OpRem
25+ OpAnd
26+ OpOr
27+ OpXor
28+ OpShl
29+ OpShr
30+
31+ // Floating-Point Operations
32+ OpAddF
33+ OpSubF
34+ OpMulF
35+ OpDivF
36+ OpRemF
37+ OpNegF
38+
39+ // Comparison Operations
40+ OpCEq
41+ OpCNeq
42+ OpCLt
43+ OpCGt
44+ OpCLe
45+ OpCGe
46+
47+ // Type Conversion/Extension Operations
48+ OpExtSB
49+ OpExtUB
50+ OpExtSH
51+ OpExtUH
52+ OpExtSW
53+ OpExtUW
54+ OpTrunc
55+ OpCast
56+ OpFToI
57+ OpIToF
58+ OpFToF
59+
60+ // Control Flow
61+ OpJmp
62+ OpJnz
63+ OpRet
64+
65+ // Function Call
66+ OpCall
67+
68+ // Special
69+ OpPhi
70+)
71+
72+// Type represents a data type in the IR.
73+type Type int
74+
75+const (
76+ TypeNone Type = iota
77+ TypeB // byte (1)
78+ TypeH // half-word (2)
79+ TypeW // word (4)
80+ TypeL // long (8)
81+ TypeS // single-precision float (4)
82+ TypeD // double-precision float (8)
83+ TypePtr // pointer (word size)
84+)
85+
86+// Value represents an operand for an instruction. It can be a constant,
87+// a temporary register, a global symbol, or a label
88+type Value interface {
89+ isValue()
90+ String() string
91+}
92+
93+// Const represents a constant integer value
94+type Const struct {
95+ Value int64
96+ Typ Type
97+}
98+
99+// FloatConst represents a constant floating-point value
100+type FloatConst struct {
101+ Value float64
102+ Typ Type
103+}
104+
105+// Global represents a global symbol (function or data)
106+type Global struct {
107+ Name string
108+}
109+
110+// Temporary represents a temporary, virtual register
111+type Temporary struct {
112+ Name string
113+ ID int
114+}
115+
116+// Label represents a basic block label
117+type Label struct {
118+ Name string
119+}
120+
121+// CastValue is a wrapper to signal an explicit cast in the backend
122+type CastValue struct {
123+ Value
124+ TargetType string
125+}
126+
127+// Func represents a function in the IR
128+type Func struct {
129+ Name string
130+ Params []*Param
131+ ReturnType Type
132+ HasVarargs bool
133+ Blocks []*BasicBlock
134+}
135+
136+// Param represents a function parameter
137+type Param struct {
138+ Name string
139+ Typ Type
140+ Val Value
141+}
142+
143+// BasicBlock represents a sequence of instructions ending with a terminator
144+type BasicBlock struct {
145+ Label *Label
146+ Instructions []*Instruction
147+}
148+
149+// Instruction represents a single operation with its operands
150+type Instruction struct {
151+ Op Op
152+ Typ Type // The type of the operation/result
153+ Result Value
154+ Args []Value
155+ ArgTypes []Type // Used for OpCall
156+ Align int // Used for OpAlloc
157+}
158+
159+// Program is the top-level container for the entire IR
160+type Program struct {
161+ Globals []*Data
162+ Strings map[string]string // Maps string content to its label
163+ Funcs []*Func
164+ ExtrnFuncs []string
165+ ExtrnVars map[string]bool
166+ WordSize int
167+ BackendTempCount int
168+}
169+
170+// Data represents a global data variable
171+type Data struct {
172+ Name string
173+ Align int
174+ Items []DataItem
175+}
176+
177+// DataItem represents an item within a global data definition
178+type DataItem struct {
179+ Typ Type
180+ Value Value // Can be Const or Global
181+ Count int // For zero-initialization (z)
182+}
183+
184+// isValue implementations to satisfy the Value interface
185+func (c *Const) isValue() {}
186+func (f *FloatConst) isValue() {}
187+func (g *Global) isValue() {}
188+func (t *Temporary) isValue() {}
189+func (l *Label) isValue() {}
190+func (c *CastValue) isValue() {}
191+
192+// String representations for Value types.
193+func (c *Const) String() string { return "" } // Handled by backend
194+func (f *FloatConst) String() string { return "" } // Handled by backend
195+func (g *Global) String() string { return g.Name }
196+func (t *Temporary) String() string { return t.Name }
197+func (l *Label) String() string { return l.Name }
198+func (c *CastValue) String() string { return c.Value.String() }
199+
200+// GetType converts an AST type to an IR type
201+func GetType(typ *ast.BxType, wordSize int) Type {
202+ if typ == nil || typ.Kind == ast.TYPE_UNTYPED {
203+ return wordTypeFromSize(wordSize)
204+ }
205+ switch typ.Kind {
206+ case ast.TYPE_VOID:
207+ return TypeNone
208+ case ast.TYPE_POINTER, ast.TYPE_ARRAY:
209+ return TypePtr
210+ case ast.TYPE_FLOAT:
211+ switch typ.Name {
212+ case "float", "float32":
213+ return TypeS
214+ case "float64":
215+ return TypeD
216+ default:
217+ return TypeS
218+ }
219+ case ast.TYPE_PRIMITIVE:
220+ switch typ.Name {
221+ case "int", "uint", "string":
222+ return wordTypeFromSize(wordSize)
223+ case "int64", "uint64":
224+ return TypeL
225+ case "int32", "uint32":
226+ return TypeW
227+ case "int16", "uint16":
228+ return TypeH
229+ case "byte", "bool", "int8", "uint8":
230+ return TypeB
231+ default:
232+ return wordTypeFromSize(wordSize)
233+ }
234+ case ast.TYPE_STRUCT:
235+ return wordTypeFromSize(wordSize)
236+ }
237+ return wordTypeFromSize(wordSize)
238+}
239+
240+func wordTypeFromSize(size int) Type {
241+ switch size {
242+ case 8:
243+ return TypeL
244+ case 4:
245+ return TypeW
246+ case 2:
247+ return TypeH
248+ case 1:
249+ return TypeB
250+ default:
251+ // Default to the largest supported integer size if word size is unusual
252+ return TypeL
253+ }
254+}
255+
256+func SizeOfType(t Type, wordSize int) int64 {
257+ switch t {
258+ case TypeB:
259+ return 1
260+ case TypeH:
261+ return 2
262+ case TypeW:
263+ return 4
264+ case TypeL:
265+ return 8
266+ case TypeS:
267+ return 4
268+ case TypeD:
269+ return 8
270+ case TypePtr:
271+ return int64(wordSize)
272+ default:
273+ return int64(wordSize)
274+ }
275+}
276+
277+// GetBackendTempCount returns the current backend temporary count
278+func (p *Program) GetBackendTempCount() int { return p.BackendTempCount }
279+
280+// IncBackendTempCount increments and returns the new backend temporary count
281+func (p *Program) IncBackendTempCount() int {
282+ p.BackendTempCount++
283+ return p.BackendTempCount
284+}
285+
286+// IsStringLabel checks if a global name corresponds to a string literal
287+func (p *Program) IsStringLabel(name string) (string, bool) {
288+ for s, label := range p.Strings {
289+ if label == name { return s, true }
290+ }
291+ return "", false
292+}
293+
294+// FindFunc finds a function by name in the program.
295+func (p *Program) FindFunc(name string) *Func {
296+ for _, f := range p.Funcs {
297+ if f.Name == name { return f }
298+ }
299+ return nil
300+}
+21,
-2
1@@ -363,6 +363,18 @@ func (tc *TypeChecker) checkExpr(node *ast.Node) *ast.BxType {
2 switch d := node.Data.(type) {
3 case ast.AssignNode:
4 lhsType, rhsType := tc.checkExpr(d.Lhs), tc.checkExpr(d.Rhs)
5+
6+ // Handle type promotion on assignment (e.g., int var = ptr_val).
7+ // This is common in B where variables can change type implicitly.
8+ isLhsScalar := tc.isScalarType(lhsType) && lhsType.Kind != ast.TYPE_POINTER
9+ isRhsPtr := rhsType != nil && rhsType.Kind == ast.TYPE_POINTER
10+ if isLhsScalar && isRhsPtr && d.Lhs.Type == ast.Ident {
11+ if sym := tc.findSymbol(d.Lhs.Data.(ast.IdentNode).Name, false); sym != nil {
12+ sym.Type = rhsType // Promote the variable's type to the pointer type
13+ lhsType = rhsType
14+ }
15+ }
16+
17 if d.Lhs.Type == ast.Subscript {
18 subscript := d.Lhs.Data.(ast.SubscriptNode)
19 arrayExpr := subscript.Array
20@@ -423,10 +435,17 @@ func (tc *TypeChecker) checkExpr(node *ast.Node) *ast.BxType {
21 case ast.TernaryNode:
22 tc.checkExprAsCondition(d.Cond)
23 thenType, elseType := tc.checkExpr(d.ThenExpr), tc.checkExpr(d.ElseExpr)
24- if !tc.areTypesCompatible(thenType, elseType, nil) {
25+ if !tc.areTypesCompatible(thenType, elseType, d.ElseExpr) {
26 util.Warn(tc.cfg, config.WarnType, node.Tok, "Type mismatch in ternary expression branches ('%s' vs '%s')", typeToString(thenType), typeToString(elseType))
27 }
28- typ = thenType
29+ // Type promotion rules for ternary operator: pointer types take precedence.
30+ if thenType != nil && thenType.Kind == ast.TYPE_POINTER {
31+ typ = thenType
32+ } else if elseType != nil && elseType.Kind == ast.TYPE_POINTER {
33+ typ = elseType
34+ } else {
35+ typ = thenType // Default to 'then' type if no pointers involved
36+ }
37 case ast.SubscriptNode:
38 arrayType, indexType := tc.checkExpr(d.Array), tc.checkExpr(d.Index)
39 if !tc.isIntegerType(indexType) && indexType.Kind != ast.TYPE_UNTYPED {