repos / gbc

GBC - Go B Compiler
git clone https://github.com/xplshn/gbc.git

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]>
14 files changed,  +3452, -1234
M go.mod
M go.sum
A cmd/gbc/main.go
+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+}
M cmd/gtest/main.go
+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=
M pkg/ast/ast.go
+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 }
A pkg/cli/cli.go
+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+}
A pkg/codegen/backend.go
+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+}
M pkg/codegen/codegen.go
+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 }
A pkg/codegen/codegen_helpers.go
+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+}
A pkg/codegen/llvm_backend.go
+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+}
A pkg/codegen/qbe_backend.go
+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+}
M pkg/config/config.go
+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 }
A pkg/ir/ir.go
+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+}
M pkg/typeChecker/typeChecker.go
+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 {