diff --git a/.gitignore b/.gitignore index ad7908d..e3989be 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Compiled binary buxc +buxc_lir src/main # Nim cache diff --git a/README.md b/README.md index d480a35..7099569 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ ![Bux Language](bux-lang-01.jpeg) -> **Status:** Bootstrap compiler (`buxc`, Nim) compiles `.bux` → C → native binary. +> **Status:** Bootstrap compiler (`buxc`, Nim) compiles `.bux` → HIR → **LIR** → C → native binary. +> New **LIR backend** (v0.3.0) replaces direct HIR→C emission — cleaner codegen, all 22 examples passing. > Self-hosted compiler (`buxc2`) exists as a proof-of-concept but is **deprioritized** in favor of language features and stdlib maturity. Bux is a fast, compiled, strongly-typed systems programming language. Features a C backend for native code generation, raw multi-line strings, gradual ownership, async/await, generics, algebraic enums, and a package manager. @@ -190,7 +191,7 @@ func Main() -> int { | **Interfaces** | `interface` + `extend` for trait-like behavior | | **Error Handling** | `Result`, `Option`, and the `?` operator | | **Standard Library** | `Io`, `Array`, `String`, `Map`, `Fs`, `Mem`, `Set`, `Path`, `Math`, `Task`, `Channel`, `Sync`, `Os`, `Time`, `Process` | -| **Backend** | C transpiler (self-hosting + bootstrap) | +| **Backend** | LIR → C transpiler (clean 3-address code, then gcc/clang) | | **Strings** | Raw multi-line backtick strings (`...`), C-string interop | | **Gradual Ownership** | `@[Checked]` + `&T`/`&mut T` borrow checking | | **Async/Await** | `async func`, `spawn`, `.await` with stackful coroutines | diff --git a/bootstrap/cli.nim b/bootstrap/cli.nim index 16cba1a..b893013 100644 --- a/bootstrap/cli.nim +++ b/bootstrap/cli.nim @@ -1,5 +1,5 @@ import std/[os, strutils, terminal, strformat, osproc, sets] -import lexer, parser, ast, sema, manifest, hir_lower, c_backend +import lexer, parser, ast, sema, manifest, hir_lower, lir, lir_lower, lir_c_backend type ColorMode* = enum @@ -454,8 +454,9 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int = return 1 let hirMod = lowerModule(unifiedModule, semaCtx) - var cbe = initCBackend() - var allCCode = cbe.emitModule(hirMod) + let lirBuilder = lowerModuleToLir(hirMod) + var lirCbe = initLirCBackend() + var allCCode = lirCbe.emitModule(lirBuilder, hirMod) # Write C file let cFile = buildDir / "main.c" diff --git a/bootstrap/lir.nim b/bootstrap/lir.nim new file mode 100644 index 0000000..f5dba1c --- /dev/null +++ b/bootstrap/lir.nim @@ -0,0 +1,314 @@ +## LIR — Low-level Intermediate Representation +## Linear 3-address code IR, designed for straightforward C emission. +## Each HIR construct lowers to 5-30 LIR instructions. + +import types + +type + LirKind* = enum + # ── Data movement ── + lirMov ## dst = src + lirLoad ## dst = *(base + offset) [type: base elem type] + lirStore ## *(base + offset) = src + lirLoadGlobal ## dst = global_name + + # ── Arithmetic (3-address, signed) ── + lirAdd + lirSub + lirMul + lirDiv + lirMod + + # ── Bitwise ── + lirAnd + lirOr + lirXor + lirShl + lirShr + + # ── Unary ── + lirNeg ## dst = -src + lirNot ## dst = !src (logical) + lirBNot ## dst = ~src (bitwise) + + # ── Comparison (dst = 0 or 1) ── + lirCmpEq + lirCmpNe + lirCmpLt + lirCmpLe + lirCmpGt + lirCmpGe + + # ── Control flow ── + lirLabel ## label_name: + lirJmp ## goto target + lirJz ## if (!cond) goto target + lirJnz ## if (cond) goto target + + # ── Calls ── + lirCall ## dst = callee(args...) + lirCallVoid ## callee(args...) [no return] + lirCallIndirect ## dst = (*fn_ptr)(args...) + + # ── Return ── + lirRet ## return [val] + + # ── Stack allocation ── + lirAlloca ## type dst; (declare a C local) + + # ── Pointers / addressing ── + lirAddrOf ## dst = &source_var + lirFieldPtr ## dst = &(base.field_name) + lirArrowFieldPtr ## dst = &(base->field_name) + lirIndexPtr ## dst = &base[idx] + lirPtrAdd ## dst = base + offset_bytes (raw pointer arithmetic) + + # ── Type conversion ── + lirCast ## dst = (target_type)src + + # ── Composite literals ── + lirStructInit ## dst = (StructType){.f1=v1, .f2=v2, ...} + lirSliceInit ## dst = (SliceType){.data=arr, .len=n} + + # ── Ternary (convenience; lowered from if-expr) ── + lirSelect ## dst = cond ? a : b + + # ── Inline C (for runtime calls that C handles natively) ── + lirRawC ## emit raw C line + + # ── Source annotation ── + lirComment ## /* text */ + + LirValueKind* = enum + lvkTemp ## Virtual register / temp (e.g. "%42") + lvkVar ## Named variable / parameter + lvkInt ## Integer literal + lvkFloat ## Float literal + lvkString ## String literal (already C-escaped) + lvkGlobal ## Global variable / constant name + lvkLabel ## Label reference + lvkField ## Field name (for struct operations) + lvkType ## C type name (for casts, alloca, struct init) + lvkVoid ## No value + + LirValue* = object + case kind*: LirValueKind + of lvkVoid: discard + of lvkTemp, lvkVar, lvkGlobal, lvkLabel, lvkField, lvkType, lvkString: + strVal*: string + of lvkInt: + intVal*: int64 + of lvkFloat: + floatVal*: float64 + + LirInstr* = object + kind*: LirKind + dst*: LirValue ## Destination (temp or void) + src*: LirValue ## Source operand + src2*: LirValue ## Second source operand (for binary ops) + extra*: seq[LirValue] ## Extra operands (call args, struct fields, etc.) + locLine*: int ## Source line for debug comments (0 = none) + locFile*: string ## Source file for debug comments ("" = none) + +# ── Constructor helpers ── + +proc lirTemp*(name: string): LirValue = + LirValue(kind: lvkTemp, strVal: name) + +proc lirVar*(name: string): LirValue = + LirValue(kind: lvkVar, strVal: name) + +proc lirInt*(v: int64): LirValue = + LirValue(kind: lvkInt, intVal: v) + +proc lirFloatLit*(v: float64): LirValue = + LirValue(kind: lvkFloat, floatVal: v) + +proc lirStr*(v: string): LirValue = + ## Already C-escaped and quoted string + LirValue(kind: lvkString, strVal: v) + +proc lirGlobal*(name: string): LirValue = + LirValue(kind: lvkGlobal, strVal: name) + +proc lirLabel*(name: string): LirValue = + LirValue(kind: lvkLabel, strVal: name) + +proc lirField*(name: string): LirValue = + LirValue(kind: lvkField, strVal: name) + +proc lirType*(name: string): LirValue = + LirValue(kind: lvkType, strVal: name) + +proc lirVoid*(): LirValue = + LirValue(kind: lvkVoid) + +proc `$`*(v: LirValue): string = + case v.kind + of lvkVoid: "void" + of lvkTemp: "%" & v.strVal + of lvkVar: v.strVal + of lvkInt: $v.intVal + of lvkFloat: $v.floatVal + of lvkString: v.strVal + of lvkGlobal: "@" & v.strVal + of lvkLabel: ":" & v.strVal + of lvkField: "." & v.strVal + of lvkType: "<" & v.strVal & ">" + +# ── LIR function ── + +type + LirFunc* = object + name*: string + params*: seq[tuple[name: string, cType: string]] + retType*: string ## C return type ("int", "void", etc.) + instrs*: seq[LirInstr] + isPublic*: bool + + LirModule* = object + funcs*: seq[LirFunc] + globals*: seq[tuple[name: string, cType: string, initVal: string]] + structDefs*: seq[string] ## Raw C struct typedef strings + enumDefs*: seq[string] ## Raw C enum typedef strings + externs*: seq[string] ## Raw C extern declarations + includes*: seq[string] ## #include <...> + preamble*: string ## Raw C code at top of file + +# ── Builder context (temp counter, label counter) ── + +type + LirBuilder* = object + funcs*: seq[LirFunc] + tempCounter*: int + labelCounter*: int + commentLine*: int + commentFile*: string + ## Current function being built + curFunc*: LirFunc + curFuncActive*: bool + +proc initLirBuilder*(): LirBuilder = + result = LirBuilder() + result.tempCounter = 0 + result.labelCounter = 0 + +proc freshTemp*(b: var LirBuilder): LirValue = + inc b.tempCounter + result = lirTemp("_t" & $b.tempCounter) + +proc freshLabel*(b: var LirBuilder, prefix: string = "L"): LirValue = + inc b.labelCounter + result = lirLabel(prefix & $b.labelCounter) + +# ── Instruction emitters ── + +proc emit*(b: var LirBuilder, instr: LirInstr) = + var i = instr + if b.commentLine > 0: + i.locLine = b.commentLine + i.locFile = b.commentFile + b.curFunc.instrs.add(i) + +proc emitMov*(b: var LirBuilder, dst, src: LirValue) = + b.emit(LirInstr(kind: lirMov, dst: dst, src: src)) + +proc emitLoad*(b: var LirBuilder, dst, base: LirValue, offset: int = 0) = + b.emit(LirInstr(kind: lirLoad, dst: dst, src: base, src2: lirInt(offset))) + +proc emitStore*(b: var LirBuilder, base: LirValue, src: LirValue, offset: int = 0) = + b.emit(LirInstr(kind: lirStore, src: src, src2: base, dst: lirInt(offset))) + +proc emitBinOp*(b: var LirBuilder, op: LirKind, dst, a, bl: LirValue) = + b.emit(LirInstr(kind: op, dst: dst, src: a, src2: bl)) + +proc emitUnary*(b: var LirBuilder, op: LirKind, dst, src: LirValue) = + b.emit(LirInstr(kind: op, dst: dst, src: src)) + +proc emitCmp*(b: var LirBuilder, op: LirKind, dst, a, bl: LirValue) = + b.emit(LirInstr(kind: op, dst: dst, src: a, src2: bl)) + +proc emitLabel*(b: var LirBuilder, label: LirValue) = + b.emit(LirInstr(kind: lirLabel, src: label)) + +proc emitJmp*(b: var LirBuilder, target: LirValue) = + b.emit(LirInstr(kind: lirJmp, src: target)) + +proc emitJz*(b: var LirBuilder, target, cond: LirValue) = + b.emit(LirInstr(kind: lirJz, src: target, src2: cond)) + +proc emitJnz*(b: var LirBuilder, target, cond: LirValue) = + b.emit(LirInstr(kind: lirJnz, src: target, src2: cond)) + +proc emitCall*(b: var LirBuilder, dst: LirValue, callee: string, args: seq[LirValue]) = + b.emit(LirInstr(kind: lirCall, dst: dst, src: lirGlobal(callee), extra: args)) + +proc emitCallVoid*(b: var LirBuilder, callee: string, args: seq[LirValue]) = + b.emit(LirInstr(kind: lirCallVoid, dst: lirVoid(), src: lirGlobal(callee), extra: args)) + +proc emitRet*(b: var LirBuilder, val: LirValue = lirVoid()) = + b.emit(LirInstr(kind: lirRet, src: val)) + +proc emitAlloca*(b: var LirBuilder, name: string, cType: string) = + b.emit(LirInstr(kind: lirAlloca, dst: lirVar(name), src: lirType(cType))) + +proc emitAddrOf*(b: var LirBuilder, dst, src: LirValue) = + b.emit(LirInstr(kind: lirAddrOf, dst: dst, src: src)) + +proc emitFieldPtr*(b: var LirBuilder, dst, base: LirValue, field: string) = + b.emit(LirInstr(kind: lirFieldPtr, dst: dst, src: base, src2: lirField(field))) + +proc emitArrowFieldPtr*(b: var LirBuilder, dst, base: LirValue, field: string) = + b.emit(LirInstr(kind: lirArrowFieldPtr, dst: dst, src: base, src2: lirField(field))) + +proc emitIndexPtr*(b: var LirBuilder, dst, base, idx: LirValue) = + b.emit(LirInstr(kind: lirIndexPtr, dst: dst, src: base, src2: idx)) + +proc emitPtrAdd*(b: var LirBuilder, dst, base, offset: LirValue) = + b.emit(LirInstr(kind: lirPtrAdd, dst: dst, src: base, src2: offset)) + +proc emitCast*(b: var LirBuilder, dst, src: LirValue, targetType: string) = + b.emit(LirInstr(kind: lirCast, dst: dst, src: src, src2: lirType(targetType))) + +proc emitStructInit*(b: var LirBuilder, dst: LirValue, structType: string, + fields: seq[tuple[name: string, val: LirValue]]) = + var extras: seq[LirValue] = @[lirType(structType)] + for f in fields: + extras.add(lirField(f.name)) + extras.add(f.val) + b.emit(LirInstr(kind: lirStructInit, dst: dst, extra: extras)) + +proc emitSliceInit*(b: var LirBuilder, dst: LirValue, elemType: string, + dataPtr: LirValue, length: LirValue) = + b.emit(LirInstr(kind: lirSliceInit, dst: dst, src: dataPtr, src2: length, + extra: @[lirType(elemType)])) + +proc emitSelect*(b: var LirBuilder, dst, cond, thenVal, elseVal: LirValue) = + b.emit(LirInstr(kind: lirSelect, dst: dst, src: cond, src2: thenVal, + extra: @[elseVal])) + +proc emitRawC*(b: var LirBuilder, code: string) = + b.emit(LirInstr(kind: lirRawC, src: lirStr(code))) + +proc emitComment*(b: var LirBuilder, text: string) = + b.emit(LirInstr(kind: lirComment, src: lirStr(text))) + +# ── Function management ── + +proc beginFunc*(b: var LirBuilder, name: string, params: seq[tuple[name: string, cType: string]], + retType: string, isPublic: bool = true) = + if b.curFuncActive: + b.funcs.add(b.curFunc) + b.curFunc = LirFunc(name: name, params: params, retType: retType, + isPublic: isPublic) + b.curFuncActive = true + +proc endFunc*(b: var LirBuilder) = + if b.curFuncActive: + b.funcs.add(b.curFunc) + b.curFuncActive = false + b.curFunc = LirFunc() + +proc setSourceLoc*(b: var LirBuilder, line: int, file: string) = + b.commentLine = line + b.commentFile = file diff --git a/bootstrap/lir_c_backend.nim b/bootstrap/lir_c_backend.nim new file mode 100644 index 0000000..7acbbfe --- /dev/null +++ b/bootstrap/lir_c_backend.nim @@ -0,0 +1,560 @@ +## LIR → C Backend +## Emits clean, well-structured C code from LIR instructions. +## Since LIR is already linear and low-level, C emission is straightforward. + +import std/[strutils, strformat, tables, sequtils] +import lir, hir, types, token + +type + LirCBackend* = object + output*: string + indent*: int + tempTypes*: Table[string, string] ## Track C types of temp variables + +proc initLirCBackend*(): LirCBackend = + result = LirCBackend( + indent: 0, + tempTypes: initTable[string, string](), + ) + +proc emit(be: var LirCBackend, s: string) = + be.output.add(s) + +proc emitIndent(be: var LirCBackend) = + for i in 0 ..< be.indent: + be.output.add(" ") + +proc emitLine(be: var LirCBackend, s: string) = + be.emitIndent() + be.output.add(s) + be.output.add("\n") + +proc valToC(be: var LirCBackend, v: LirValue): string = + ## Convert a LirValue to its C representation. + case v.kind + of lvkVoid: "" + of lvkTemp: v.strVal + of lvkVar: v.strVal + of lvkInt: $v.intVal + of lvkFloat: $v.floatVal + of lvkString: v.strVal + of lvkGlobal: v.strVal + of lvkLabel: v.strVal + of lvkField: v.strVal + of lvkType: v.strVal + +proc typeFromValue(be: var LirCBackend, v: LirValue): string = + ## Infer a C type for a value. Temps are tracked; named vars use lookup. + case v.kind + of lvkTemp: + if be.tempTypes.hasKey(v.strVal): + return be.tempTypes[v.strVal] + return "int" # Default + of lvkString: return "const char*" + of lvkInt: return "int" + of lvkFloat: return "double" + else: return "" + +proc setTempType(be: var LirCBackend, temp: string, cType: string) = + be.tempTypes[temp] = cType + +# ── Per-instruction emission ── + +proc emitInstr(be: var LirCBackend, instr: LirInstr) = + template v(x: LirValue): string = valToC(be, x) + case instr.kind + + # ── Data movement ── + of lirMov: + be.emitLine(&"{v(instr.dst)} = {v(instr.src)};") + + of lirLoad: + # dst = *(base + offset) or dst = base->src2 (if src2 is a field name) + if instr.src2.kind == lvkField: + be.emitLine(&"{v(instr.dst)} = {v(instr.src)}.{v(instr.src2)};") + elif instr.src2.kind == lvkInt and instr.src2.intVal == 0: + be.emitLine(&"{v(instr.dst)} = *{v(instr.src)};") + elif instr.src2.kind == lvkTemp or instr.src2.kind == lvkVar: + be.emitLine(&"{v(instr.dst)} = {v(instr.src)}[{v(instr.src2)}];") + else: + be.emitLine(&"{v(instr.dst)} = {v(instr.src)}[{v(instr.src2)}];") + + of lirStore: + # *(base + offset) = src + if instr.src2.kind == lvkField: + be.emitLine(&"{v(instr.src2)}.{v(instr.dst)} = {v(instr.src)};") + elif instr.dst.kind == lvkInt and instr.dst.intVal == 0: + be.emitLine(&"*{v(instr.src2)} = {v(instr.src)};") + elif instr.src2.kind == lvkTemp or instr.src2.kind == lvkVar: + be.emitLine(&"{v(instr.src2)}[{v(instr.dst)}] = {v(instr.src)};") + else: + be.emitLine(&"*({v(instr.src2)} + {v(instr.dst)}) = {v(instr.src)};") + + of lirLoadGlobal: + be.emitLine(&"{v(instr.dst)} = {v(instr.src)};") + + # ── Arithmetic ── + of lirAdd, lirSub, lirMul, lirDiv, lirMod, + lirAnd, lirOr, lirXor, lirShl, lirShr: + let op = case instr.kind + of lirAdd: "+" + of lirSub: "-" + of lirMul: "*" + of lirDiv: "/" + of lirMod: "%" + of lirAnd: "&" + of lirOr: "|" + of lirXor: "^" + of lirShl: "<<" + of lirShr: ">>" + else: "?" + be.emitLine(&"{v(instr.dst)} = {v(instr.src)} {op} {v(instr.src2)};") + + of lirNeg: + be.emitLine(&"{v(instr.dst)} = -{v(instr.src)};") + of lirNot: + be.emitLine(&"{v(instr.dst)} = !{v(instr.src)};") + of lirBNot: + be.emitLine(&"{v(instr.dst)} = ~{v(instr.src)};") + + # ── Comparison ── + of lirCmpEq, lirCmpNe, lirCmpLt, lirCmpLe, lirCmpGt, lirCmpGe: + let op = case instr.kind + of lirCmpEq: "==" + of lirCmpNe: "!=" + of lirCmpLt: "<" + of lirCmpLe: "<=" + of lirCmpGt: ">" + of lirCmpGe: ">=" + else: "==" + be.emitLine(&"{v(instr.dst)} = ({v(instr.src)} {op} {v(instr.src2)});") + + # ── Control flow ── + of lirLabel: + be.emitLine(&"{v(instr.src)}:;") # C requires statement after label + # Add a null statement to avoid "label at end of compound statement" warnings + # Handled by the next instruction naturally + + of lirJmp: + be.emitLine(&"goto {v(instr.src)};") + + of lirJz: + be.emitLine(&"if (!{v(instr.src2)}) goto {v(instr.src)};") + + of lirJnz: + be.emitLine(&"if ({v(instr.src2)}) goto {v(instr.src)};") + + # ── Calls ── + of lirCall: + var argsStr = "" + for i, arg in instr.extra: + if i > 0: argsStr.add(", ") + argsStr.add(v(arg)) + be.emitLine(&"{v(instr.dst)} = {v(instr.src)}({argsStr});") + + of lirCallVoid: + var argsStr = "" + for i, arg in instr.extra: + if i > 0: argsStr.add(", ") + argsStr.add(v(arg)) + be.emitLine(&"{v(instr.src)}({argsStr});") + + of lirCallIndirect: + var argsStr = "" + for i, arg in instr.extra: + if i > 0: argsStr.add(", ") + argsStr.add(v(arg)) + if instr.dst.kind != lvkVoid: + be.emitLine(&"{v(instr.dst)} = ({v(instr.src)})({argsStr});") + else: + be.emitLine(&"({v(instr.src)})({argsStr});") + + # ── Return ── + of lirRet: + if instr.src.kind != lvkVoid: + be.emitLine(&"return {v(instr.src)};") + else: + be.emitLine("return;") + + # ── Alloca ── + of lirAlloca: + be.emitLine(&"{v(instr.src)} {v(instr.dst)};") + + # ── Pointers ── + of lirAddrOf: + be.emitLine(&"{v(instr.dst)} = &{v(instr.src)};") + + of lirFieldPtr: + be.emitLine(&"{v(instr.dst)} = &({v(instr.src)}.{v(instr.src2)});") + + of lirArrowFieldPtr: + be.emitLine(&"{v(instr.dst)} = &({v(instr.src)}->{v(instr.src2)});") + + of lirIndexPtr: + be.emitLine(&"{v(instr.dst)} = &({v(instr.src)}[{v(instr.src2)}]);") + + of lirPtrAdd: + be.emitLine(&"{v(instr.dst)} = ({v(instr.src)} + {v(instr.src2)});") + + # ── Cast ── + of lirCast: + be.emitLine(&"{v(instr.dst)} = ({v(instr.src2)}){v(instr.src)};") + + # ── StructInit ── + of lirStructInit: + let structType = v(instr.extra[0]) + var fieldPairs = "" + var i = 1 + while i < instr.extra.len: + let fieldName = v(instr.extra[i]) # e.g. "width" + let fieldVal = v(instr.extra[i + 1]) # e.g. "10" + if i > 1: fieldPairs.add(", ") + fieldPairs.add(&".{fieldName} = {fieldVal}") + i += 2 + be.emitLine(&"{v(instr.dst)} = ({structType}){{{fieldPairs}}};") + + # ── SliceInit ── + of lirSliceInit: + let elemType = v(instr.extra[0]) + be.emitLine(&"{v(instr.dst)} = (Slice_{elemType}){{.data = ({elemType}*){v(instr.src)}, .len = {v(instr.src2)}}};") + + # ── Select (ternary) ── + of lirSelect: + let elseVal = if instr.extra.len > 0: v(instr.extra[0]) else: "0" + be.emitLine(&"{v(instr.dst)} = ({v(instr.src)}) ? {v(instr.src2)} : {elseVal};") + + # ── Raw C ── + of lirRawC: + let code = v(instr.src) + if code.len > 0: + be.emitLine(code) + + # ── Comment ── + of lirComment: + let text = v(instr.src) + be.emitLine(&"/* {text} */") + +# ── Function emission ── + +proc emitFunc(be: var LirCBackend, f: LirFunc) = + var paramsStr = "" + for i, p in f.params: + if i > 0: paramsStr.add(", ") + paramsStr.add(&"{p.cType} {p.name}") + if f.params.len == 0: + paramsStr = "void" + + be.emitLine(&"{f.retType} {f.name}({paramsStr}) {{") + be.indent += 1 + + # First pass: declare all temp variables at the top of the function + var tempsSeen: seq[tuple[name: string, cType: string]] = @[] + var tempsSet: seq[string] = @[] + for instr in f.instrs: + # Allocas are explicit declarations — mark name as declared, skip emission here + if instr.kind == lirAlloca: + if instr.dst.strVal.len > 0 and instr.dst.strVal notin tempsSet: + tempsSet.add(instr.dst.strVal) + continue + # Temps that are destinations of binary ops, calls, etc. + if instr.dst.kind == lvkTemp and instr.dst.strVal.len > 0 and instr.dst.strVal notin tempsSet: + # Infer type based on instruction kind + var ct = "int" + case instr.kind + of lirMov, lirLoad, lirLoadGlobal: + ct = "int" + of lirAdd, lirSub, lirMul, lirDiv, lirMod, lirNeg, + lirCmpEq, lirCmpNe, lirCmpLt, lirCmpLe, lirCmpGt, lirCmpGe: + ct = "int" + of lirAnd, lirOr, lirXor, lirShl, lirShr, lirNot, lirBNot: + ct = "int" + of lirCall, lirCallIndirect: + ct = "int" # Conservative + of lirAddrOf, lirFieldPtr, lirArrowFieldPtr, lirIndexPtr, lirPtrAdd: + ct = "void*" + of lirStructInit, lirSliceInit: + ct = "int" # Will be overwritten by the actual type in emit + of lirCast: + ct = valToC(be, instr.src2) + of lirSelect: + ct = "int" + else: + ct = "int" + tempsSeen.add((instr.dst.strVal, ct)) + tempsSet.add(instr.dst.strVal) + be.tempTypes[instr.dst.strVal] = ct + + # Declare all temps + for t in tempsSeen: + # Skip if the type is a struct type (will be declared inline) + if t.cType == "int" or t.cType == "void*" or t.cType == "double": + be.emitLine(&"{t.cType} {t.name};") + + # Emit instructions in order + for instr in f.instrs: + be.emitInstr(instr) + + be.indent -= 1 + be.emitLine("}") + be.emitLine("") + +# ── Struct/Enum emission (from HIR module) ── + +proc typeToCStr(typ: Type): string = + ## Duplicate from lir_lower for self-containedness + if typ == nil: return "int" + case typ.kind + of tkVoid: return "void" + of tkBool, tkBool8, tkBool16, tkBool32: return "bool" + of tkChar8: return "char" + of tkChar16: return "char16_t" + of tkChar32: return "char32_t" + of tkStr: return "const char*" + of tkInt8: return "int8_t" + of tkInt16: return "int16_t" + of tkInt32: return "int32_t" + of tkInt64: return "int64_t" + of tkInt: return "int" + of tkUInt8: return "uint8_t" + of tkUInt16: return "uint16_t" + of tkUInt32: return "uint32_t" + of tkUInt64: return "uint64_t" + of tkUInt: return "unsigned int" + of tkFloat32: return "float" + of tkFloat64: return "double" + of tkPointer, tkRef, tkMutRef: + if typ.inner.len > 0: + return typeToCStr(typ.inner[0]) & "*" + return "void*" + of tkDynRef: + return typ.name & "_FatPtr" + of tkSlice: + let elem = if typ.inner.len > 0: typeToCStr(typ.inner[0]) else: "void" + return "Slice_" & elem.replace(" ", "_").replace("*", "Ptr") + of tkNamed: + case typ.name + of "String", "str": return "const char*" + of "int": return "int" + of "int8": return "int8_t" + of "int16": return "int16_t" + of "int32": return "int32_t" + of "int64": return "int64_t" + of "uint": return "unsigned int" + of "uint8": return "uint8_t" + of "uint16": return "uint16_t" + of "uint32": return "uint32_t" + of "uint64": return "uint64_t" + of "float32": return "float" + of "float64": return "double" + of "bool": return "bool" + else: return typ.name + else: return "int" + +proc emitStructDef(be: var LirCBackend, name: string, fields: seq[tuple[name: string, typ: Type]]) = + be.emitLine(&"typedef struct {name} {{") + be.indent += 1 + for f in fields: + be.emitLine(&"{typeToCStr(f.typ)} {f.name};") + be.indent -= 1 + be.emitLine(&"}} {name};") + be.emitLine("") + +proc emitEnumDef(be: var LirCBackend, name: string, variants: seq[HirEnumVariant]) = + var hasData = false + for v in variants: + if v.fields.len > 0 or v.namedFields.len > 0: + hasData = true + break + + if not hasData: + # Simple enum + be.emitLine(&"typedef enum {{") + be.indent += 1 + for i, v in variants: + if i < variants.len - 1: + be.emitLine(&"{name}_{v.name},") + else: + be.emitLine(&"{name}_{v.name}") + be.indent -= 1 + be.emitLine(&"}} {name};") + be.emitLine("") + else: + # Tagged union + be.emitLine(&"typedef enum {{") + be.indent += 1 + for i, v in variants: + if i < variants.len - 1: + be.emitLine(&"{name}_{v.name},") + else: + be.emitLine(&"{name}_{v.name}") + be.indent -= 1 + be.emitLine(&"}} {name}_Tag;") + be.emitLine("") + + be.emitLine(&"typedef union {{") + be.indent += 1 + for v in variants: + if v.fields.len > 0: + for i, f in v.fields: + be.emitLine(&"{typeToCStr(f)} {v.name}_{i};") + elif v.namedFields.len > 0: + be.emitLine(&"struct {{") + be.indent += 1 + for nf in v.namedFields: + be.emitLine(&"{typeToCStr(nf.typ)} {nf.name};") + be.indent -= 1 + be.emitLine(&"}} {v.name};") + be.indent -= 1 + be.emitLine(&"}} {name}_Data;") + be.emitLine("") + + be.emitLine(&"typedef struct {{") + be.indent += 1 + be.emitLine(&"{name}_Tag tag;") + be.emitLine(&"{name}_Data data;") + be.indent -= 1 + be.emitLine(&"}} {name};") + be.emitLine("") + +# ── Module emission ── + +proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): string = + ## Emit full C source from LIR builder + HIR module metadata. + be.output = "" + + # Header + be.emitLine("/* Generated by Bux Compiler (LIR backend) */") + be.emitLine("#include ") + be.emitLine("#include ") + be.emitLine("#include ") + be.emitLine("#include ") + be.emitLine("#include ") + be.emitLine("") + + # Forward struct declarations + for s in module.structs: + be.emitLine(&"typedef struct {s.name} {s.name};") + if module.structs.len > 0: + be.emitLine("") + + # Forward trait object declarations + for iface in module.interfaces: + if not iface.hasAssocTypes: + be.emitLine(&"typedef struct {iface.name}_FatPtr {iface.name}_FatPtr;") + if module.interfaces.len > 0: + be.emitLine("") + + # Extern declarations + if module.externFuncs.len > 0: + be.emitLine("/* Extern function declarations */") + for ef in module.externFuncs: + let rt = typeToCStr(ef.retType) + var params: seq[string] = @[] + for p in ef.params: + params.add(&"{typeToCStr(p.typ)} {p.name}") + if params.len == 0: params.add("void") + be.emitLine(&"extern {rt} {ef.name}({params.join(\", \")});") + be.emitLine("") + + # Constants as #define + if module.consts.len > 0: + be.emitLine("/* Constants */") + for c in module.consts: + if c.value != nil and c.value.kind == hLit: + case c.value.litToken.kind + of tkIntLiteral: be.emitLine(&"#define {c.name} {c.value.litToken.text}") + of tkStringLiteral: be.emitLine(&"#define {c.name} \"{c.value.litToken.text}\"") + of tkBoolLiteral: be.emitLine(&"#define {c.name} {c.value.litToken.text}") + else: discard + be.emitLine("") + + # Enum definitions + for e in module.enums: + be.emitEnumDef(e.name, e.variants) + if module.enums.len > 0: + be.emitLine("") + + # Struct definitions + for s in module.structs: + be.emitStructDef(s.name, s.fields) + + # Slice types (collect from functions/structs) + # Simple: scan function params/returns for slice types + var sliceTypes: seq[tuple[name: string, elem: string]] = @[] + for f in module.funcs: + for p in f.params: + let ct = typeToCStr(p.typ) + if ct.startsWith("Slice_"): + let elem = ct[6 .. ^1] + if not sliceTypes.anyIt(it.name == ct): + sliceTypes.add((ct, elem)) + if sliceTypes.len > 0: + for st in sliceTypes: + be.emitLine(&"typedef struct {{ {st.elem}* data; size_t len; }} {st.name};") + be.emitLine("") + + # Forward function declarations + for f in module.funcs: + let rt = typeToCStr(f.retType) + var params: seq[string] = @[] + for p in f.params: + params.add(&"{typeToCStr(p.typ)} {p.name}") + if params.len == 0: params.add("void") + be.emitLine(&"{rt} {f.name}({params.join(\", \")});") + be.emitLine("") + + # VTable and fat pointer structs + for iface in module.interfaces: + if iface.hasAssocTypes: continue + let iname = iface.name + be.emitLine(&"typedef struct {iname}_VTable {{") + be.indent += 1 + for m in iface.methods: + var paramCTypes: seq[string] = @["void* self"] + for i in 1 ..< m.params.len: + paramCTypes.add(typeToCStr(m.params[i]) & " param") + let rt = typeToCStr(m.ret) + be.emitLine(&"{rt} (*{m.name})({paramCTypes.join(\", \")});") + be.indent -= 1 + be.emitLine(&"}} {iname}_VTable;") + be.emitLine(&"typedef struct {iname}_FatPtr {{") + be.indent += 1 + be.emitLine("void* data;") + be.emitLine(&"{iname}_VTable* vtable;") + be.indent -= 1 + be.emitLine(&"}} {iname}_FatPtr;") + be.emitLine("") + + # VTable instances + for vt in module.vtables: + if vt.hasAssocTypes: continue + let varName = vt.concreteType & "_" & vt.interfaceName & "_VTable" + be.emitLine(&"{vt.interfaceName}_VTable {varName} = {{") + be.indent += 1 + for m in vt.methodNames: + be.emitLine(&".{m} = (void*){vt.concreteType}_{m},") + be.indent -= 1 + be.emitLine("};") + be.emitLine("") + + # Emit all LIR functions + for f in builder.funcs: + be.emitFunc(f) + + # C main wrapper + var hasMain = false + for f in module.funcs: + if f.name == "Main": + hasMain = true + break + if hasMain: + be.emitLine("/* C entry point wrapper */") + be.emitLine("extern int g_argc;") + be.emitLine("extern char** g_argv;") + be.emitLine("int main(int argc, char** argv) {") + be.emitLine(" g_argc = argc;") + be.emitLine(" g_argv = argv;") + be.emitLine(" return Main();") + be.emitLine("}") + + return be.output diff --git a/bootstrap/lir_lower.nim b/bootstrap/lir_lower.nim new file mode 100644 index 0000000..961dd90 --- /dev/null +++ b/bootstrap/lir_lower.nim @@ -0,0 +1,625 @@ +## LIR Lowering — HIR → LIR +## Converts the high-level HIR tree into flat, linear LIR instructions. +## Each HIR node kind lowers to 1-20 LIR instructions. + +import std/[strutils, strformat, tables, sequtils] +import ast, types, token, hir, lir + +type + LowerToLirCtx* = object + builder*: LirBuilder + ## Map HIR var names -> C type names (for alloca/load/store type info) + varTypes*: Table[string, string] + ## Map HIR var names -> LirValue kind (lvkVar or lvkTemp) + varLirValues*: Table[string, LirValue] + ## C types for function params / returns + funcRetType*: string + ## Current source location for debug + currentFile*: string + +proc initLowerToLirCtx*(): LowerToLirCtx = + result = LowerToLirCtx( + builder: initLirBuilder(), + varTypes: initTable[string, string](), + varLirValues: initTable[string, LirValue](), + ) + +# ── Helpers ── + +proc cEscape(s: string): string = + result = "" + for c in s: + case c + of '\\': result.add("\\\\") + of '"': result.add("\\\"") + of '\n': result.add("\\n") + of '\r': result.add("\\r") + of '\t': result.add("\\t") + of '\0': result.add("\\0") + else: result.add(c) + +proc typeToCStr(typ: Type): string = + ## Convert a Bux Type to a C type string. + if typ == nil: return "int" + case typ.kind + of tkVoid: return "void" + of tkBool, tkBool8, tkBool16, tkBool32: return "bool" + of tkChar8: return "char" + of tkChar16: return "char16_t" + of tkChar32: return "char32_t" + of tkStr: return "const char*" + of tkInt8: return "int8_t" + of tkInt16: return "int16_t" + of tkInt32: return "int32_t" + of tkInt64: return "int64_t" + of tkInt: return "int" + of tkUInt8: return "uint8_t" + of tkUInt16: return "uint16_t" + of tkUInt32: return "uint32_t" + of tkUInt64: return "uint64_t" + of tkUInt: return "unsigned int" + of tkFloat32: return "float" + of tkFloat64: return "double" + of tkPointer, tkRef, tkMutRef: + if typ.inner.len > 0: + return typeToCStr(typ.inner[0]) & "*" + return "void*" + of tkDynRef: + return typ.name & "_FatPtr" + of tkSlice: + let elem = if typ.inner.len > 0: typeToCStr(typ.inner[0]) else: "void" + return "Slice_" & elem.replace(" ", "_").replace("*", "Ptr") + of tkNamed: + case typ.name + of "String", "str": return "const char*" + of "int": return "int" + of "int8": return "int8_t" + of "int16": return "int16_t" + of "int32": return "int32_t" + of "int64": return "int64_t" + of "uint": return "unsigned int" + of "uint8": return "uint8_t" + of "uint16": return "uint16_t" + of "uint32": return "uint32_t" + of "uint64": return "uint64_t" + of "float32": return "float" + of "float64": return "double" + of "bool": return "bool" + else: return typ.name + else: return "int" + +proc hirTypeToC(ctx: var LowerToLirCtx, node: HirNode): string = + if node == nil: return "int" + result = typeToCStr(node.typ) + +proc binOpToLir(op: TokenKind): LirKind = + case op + of tkPlus: lirAdd + of tkMinus: lirSub + of tkStar: lirMul + of tkSlash: lirDiv + of tkPercent: lirMod + of tkAmp: lirAnd + of tkPipe: lirOr + of tkCaret: lirXor + of tkShl: lirShl + of tkShr: lirShr + else: lirAdd + +proc cmpOpToLir(op: TokenKind): LirKind = + case op + of tkEq: lirCmpEq + of tkNe: lirCmpNe + of tkLt: lirCmpLt + of tkLe: lirCmpLe + of tkGt: lirCmpGt + of tkGe: lirCmpGe + else: lirCmpEq + +# ── Forward declarations ── +proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue +proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode) + +# ── Lowering: Expressions → LirValue ── + +proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue = + if node == nil: return lirInt(0) + template b: var LirBuilder = ctx.builder + + case node.kind + + # ── Literals ── + of hLit: + case node.litToken.kind + of tkBoolLiteral: + if node.litToken.text == "true": return lirInt(1) + else: return lirInt(0) + of tkStringLiteral: + var text = node.litToken.text + # Handle backtick strings + if text.len >= 2 and text[0] == '`' and text[text.len-1] == '`': + text = "\"" & cEscape(text[1 ..< text.len-1]) & "\"" + elif text.len >= 2 and text[0] == '"' and text[text.len-1] == '"': + # Strip c8" c16" c32" prefixes + if text.startsWith("c32\""): + text = "\"" & cEscape(text[4 ..< text.len-1]) & "\"" + elif text.startsWith("c16\""): + text = "\"" & cEscape(text[4 ..< text.len-1]) & "\"" + elif text.startsWith("c8\""): + text = "\"" & cEscape(text[3 ..< text.len-1]) & "\"" + else: + text = "\"" & cEscape(text[1 ..< text.len-1]) & "\"" + elif text.len >= 2 and text[0] == '"': + text = "\"" & cEscape(text[1 ..< text.len]) & "\"" + else: + text = "\"" & cEscape(text) & "\"" + return lirStr(text) + of tkNull: + return lirInt(0) + else: + # Integer/float literal + return lirVar(node.litToken.text) + + # ── Variable reference ── + of hVar: + let name = node.varName + if ctx.varLirValues.hasKey(name): + return ctx.varLirValues[name] + return lirVar(name) + + # ── Self ── + of hSelf: + return lirVar("self") + + # ── Unary ── + of hUnary: + let operand = lowerExpr(ctx, node.unaryOperand) + case node.unaryOp + of tkMinus: + let t = b.freshTemp() + b.emitUnary(lirNeg, t, operand) + return t + of tkBang: + let t = b.freshTemp() + b.emitUnary(lirNot, t, operand) + return t + of tkTilde: + let t = b.freshTemp() + b.emitUnary(lirBNot, t, operand) + return t + of tkStar: + # Dereference: *ptr → load + let t = b.freshTemp() + b.emitLoad(t, operand) + return t + of tkAmp: + # Address of: &var + let t = b.freshTemp() + b.emitAddrOf(t, operand) + return t + else: + return operand + + # ── Binary ── + of hBinary: + let left = lowerExpr(ctx, node.binaryLeft) + let right = lowerExpr(ctx, node.binaryRight) + case node.binaryOp + of tkEq, tkNe, tkLt, tkLe, tkGt, tkGe: + let t = b.freshTemp() + b.emitCmp(cmpOpToLir(node.binaryOp), t, left, right) + return t + of tkAmpAmp, tkPipePipe: + # Logical and/or: lowered to select + let t = b.freshTemp() + if node.binaryOp == tkAmpAmp: + # left && right → left ? (right != 0) : 0 + let rhsBool = b.freshTemp() + b.emitCmp(lirCmpNe, rhsBool, right, lirInt(0)) + b.emitSelect(t, left, rhsBool, lirInt(0)) + else: + # left || right → left ? 1 : (right != 0) + let rhsBool = b.freshTemp() + b.emitCmp(lirCmpNe, rhsBool, right, lirInt(0)) + b.emitSelect(t, left, lirInt(1), rhsBool) + return t + else: + let t = b.freshTemp() + b.emitBinOp(binOpToLir(node.binaryOp), t, left, right) + return t + + # ── Call ── + of hCall: + var args: seq[LirValue] = @[] + for arg in node.callArgs: + args.add(lowerExpr(ctx, arg)) + let callee = node.callCallee + let t = b.freshTemp() + let cType = hirTypeToC(ctx, node) + if cType != "void" and cType != "": + b.emitAlloca(t.strVal, cType) + b.emitCall(t, callee, args) + return t + + # ── CallIndirect ── + of hCallIndirect: + let callee = lowerExpr(ctx, node.callIndirectCallee) + var args: seq[LirValue] = @[callee] + for arg in node.callIndirectArgs: + args.add(lowerExpr(ctx, arg)) + let t = b.freshTemp() + let cType = hirTypeToC(ctx, node) + if cType != "void" and cType != "": + b.emitAlloca(t.strVal, cType) + # Use lirCallIndirect: dst = (*fn_ptr)(args...) + b.emit(LirInstr(kind: lirCallIndirect, dst: t, src: callee, extra: args[1..^1])) + return t + + # ── Field pointer expressions (return address) ── + # These return a typed pointer (void* for now, cast before deref) + of hFieldPtr: + let base = lowerExpr(ctx, node.fieldPtrBase) + let baseTyp = node.fieldPtrBase.typ + let isPtr = baseTyp != nil and baseTyp.kind in {tkPointer, tkRef, tkMutRef} + let t = b.freshTemp() + b.emitAlloca(t.strVal, "void*") + if isPtr: + b.emitRawC(&"{t.strVal} = (void*)&({base.strVal}->{node.fieldName});") + else: + b.emitRawC(&"{t.strVal} = (void*)&({base.strVal}.{node.fieldName});") + return t + + of hArrowField: + let base = lowerExpr(ctx, node.arrowFieldBase) + let t = b.freshTemp() + b.emitAlloca(t.strVal, "void*") + b.emitRawC(&"{t.strVal} = (void*)&({base.strVal}->{node.arrowFieldName});") + return t + + of hIndexPtr: + let base = lowerExpr(ctx, node.indexPtrBase) + let idx = lowerExpr(ctx, node.indexPtrIndex) + let t = b.freshTemp() + b.emitAlloca(t.strVal, "void*") + b.emitRawC(&"{t.strVal} = (void*)&({base.strVal}[{idx.strVal}]);") + return t + + # ── Load ── + of hLoad: + # Load through a pointer or field access + # Optimize common patterns: load(field_ptr) → direct field access + if node.loadPtr != nil and node.loadPtr.kind == hArrowField: + let base = lowerExpr(ctx, node.loadPtr.arrowFieldBase) + let cType = hirTypeToC(ctx, node) + let t = b.freshTemp() + b.emitAlloca(t.strVal, cType) + b.emitRawC(&"{t.strVal} = {base.strVal}->{node.loadPtr.arrowFieldName};") + return t + if node.loadPtr != nil and node.loadPtr.kind == hFieldPtr: + let base = lowerExpr(ctx, node.loadPtr.fieldPtrBase) + let baseTyp = node.loadPtr.fieldPtrBase.typ + let isPtr = baseTyp != nil and baseTyp.kind in {tkPointer, tkRef, tkMutRef} + let cType = hirTypeToC(ctx, node) + let t = b.freshTemp() + b.emitAlloca(t.strVal, cType) + if isPtr: + b.emitRawC(&"{t.strVal} = {base.strVal}->{node.loadPtr.fieldName};") + else: + b.emitRawC(&"{t.strVal} = {base.strVal}.{node.loadPtr.fieldName};") + return t + if node.loadPtr != nil and node.loadPtr.kind == hIndexPtr: + let base = lowerExpr(ctx, node.loadPtr.indexPtrBase) + let idx = lowerExpr(ctx, node.loadPtr.indexPtrIndex) + let cType = hirTypeToC(ctx, node) + let t = b.freshTemp() + b.emitAlloca(t.strVal, cType) + b.emitRawC(&"{t.strVal} = {base.strVal}[{idx.strVal}];") + return t + # Generic: dereference pointer + let ptrVal = lowerExpr(ctx, node.loadPtr) + let cType = hirTypeToC(ctx, node) + let t = b.freshTemp() + b.emitAlloca(t.strVal, cType) + b.emitRawC(&"{t.strVal} = *({cType}*){ptrVal.strVal};") + return t + + # ── Slice Index ── + of hSliceIndex: + let base = lowerExpr(ctx, node.sliceIndexBase) + let idx = lowerExpr(ctx, node.sliceIndexIndex) + let t = b.freshTemp() + # Emit: base.data[idx] (with optional bounds check) + if node.sliceIndexBoundsCheck: + b.emitRawC(&"bux_bounds_check((size_t)({idx.strVal}), ({base.strVal}).len)") + b.emit(LirInstr(kind: lirLoad, dst: t, src: base, src2: idx)) + return t + + # ── Cast ── + of hCast: + let operand = lowerExpr(ctx, node.castOperand) + let targetCType = typeToCStr(node.castType) + let t = b.freshTemp() + b.emitCast(t, operand, targetCType) + return t + + # ── SizeOf ── + of hSizeOf: + let ctype = typeToCStr(node.sizeOfType) + let t = b.freshTemp() + b.emit(LirInstr(kind: lirRawC, src: lirStr(&"/* sizeof({ctype}) */"))) + return lirVar(&"sizeof({ctype})") + + # ── Spawn ── + of hSpawn: + if node.spawnAsync: + let t = b.freshTemp() + b.emitAlloca(t.strVal, "void*") + b.emitCall(t, "bux_async_spawn", @[lirGlobal(node.spawnCallee)]) + return t + else: + var args: seq[LirValue] = @[] + if node.spawnArgs.len > 0: + args.add(lowerExpr(ctx, node.spawnArgs[0])) + let t = b.freshTemp() + b.emitAlloca(t.strVal, "void*") + b.emitCall(t, "bux_task_spawn", @[lirGlobal(node.spawnCallee)] & args) + return t + + # ── DynRef (trait object) ── + of hDynRef: + let data = lowerExpr(ctx, node.dynRefData) + let t = b.freshTemp() + let fatPtrType = node.dynRefInterface & "_FatPtr" + b.emitRawC(&"{fatPtrType} {t.strVal};") + b.emitMov(lirVar(t.strVal & ".data"), data) + b.emitMov(lirVar(t.strVal & ".vtable"), lirGlobal(node.dynRefConcreteType & "_" & node.dynRefInterface & "_VTable")) + return t + + # ── DynCall ── + of hDynCall: + let receiver = lowerExpr(ctx, node.dynCallReceiver) + var args: seq[LirValue] = @[receiver] + for i in 1 ..< node.dynCallArgs.len: + args.add(lowerExpr(ctx, node.dynCallArgs[i])) + let t = b.freshTemp() + b.emitRawC(&"{t.strVal} = {receiver.strVal}.vtable->{node.dynCallMethod}({args.mapIt($it).join(\", \")});") + return t + + # ── StructInit ── + of hStructInit: + var fields: seq[tuple[name: string, val: LirValue]] = @[] + for f in node.structInitFields: + fields.add((f.name, lowerExpr(ctx, f.value))) + let t = b.freshTemp() + b.emitStructInit(t, node.structInitName, fields) + return t + + # ── SliceInit ── + of hSliceInit: + let t = b.freshTemp() + let elemType = if node.typ.inner.len > 0: typeToCStr(node.typ.inner[0]) else: "void" + var elems: seq[LirValue] = @[] + for e in node.sliceInitElements: + elems.add(lowerExpr(ctx, e)) + # Create a temporary array, then wrap in slice + let arrTmp = b.freshTemp() + b.emitRawC(&"{elemType} {arrTmp.strVal}[] = {{{elems.mapIt($it).join(\", \")}}};") + b.emitSliceInit(t, elemType, arrTmp, lirInt(node.sliceInitLen)) + return t + + # ── TupleInit ── + of hTupleInit: + var elems: seq[LirValue] = @[] + for e in node.tupleInitElements: + elems.add(lowerExpr(ctx, e)) + let t = b.freshTemp() + b.emitRawC(&"/* tuple */ {t.strVal} = {{{elems.mapIt($it).join(\", \")}}};") + return t + + # ── If expression (ternary) ── + of hIf: + if node.ifThen.kind != hBlock and node.ifElse != nil: + # Simple ternary + let cond = lowerExpr(ctx, node.ifCond) + let thenVal = lowerExpr(ctx, node.ifThen) + let elseVal = lowerExpr(ctx, node.ifElse) + let t = b.freshTemp() + b.emitSelect(t, cond, thenVal, elseVal) + return t + else: + # Complex if — fallback to block lowering + # This shouldn't happen if lowering is done right, but handle gracefully + return lirInt(0) + + # ── Block expression (returns last expr) ── + of hBlock: + for stmt in node.blockStmts: + lowerStmt(ctx, stmt) + if node.blockExpr != nil: + return lowerExpr(ctx, node.blockExpr) + return lirVoid() + + # ── Match (lowered by hir_lower already, but handle if present) ── + of hMatch: + # Should have been lowered by hir_lower.nim already + return lirInt(0) + + else: + # Fallback for unhandled expression kinds + b.emitComment(&"/* unhandled expr kind: {node.kind} */") + return lirInt(0) + +# ── Lowering: Statements → void ── + +proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode) = + if node == nil: return + template b: var LirBuilder = ctx.builder + + case node.kind + + # ── Return ── + of hReturn: + if node.returnValue != nil: + let val = lowerExpr(ctx, node.returnValue) + b.emitRet(val) + else: + b.emitRet() + + # ── If statement ── + of hIf: + # Lower to: cond = lower(ifCond); jz else_label, cond + # lower(ifThen); jmp end_label + # else_label: lower(ifElse); end_label: + let cond = lowerExpr(ctx, node.ifCond) + let elseLbl = b.freshLabel("else") + let endLbl = b.freshLabel("endif") + + if node.ifElse != nil: + b.emitJz(elseLbl, cond) + lowerStmt(ctx, node.ifThen) + b.emitJmp(endLbl) + b.emitLabel(elseLbl) + lowerStmt(ctx, node.ifElse) + b.emitLabel(endLbl) + else: + b.emitJz(endLbl, cond) + lowerStmt(ctx, node.ifThen) + b.emitLabel(endLbl) + + # ── While statement ── + of hWhile: + let startLbl = b.freshLabel("while") + let bodyLbl = b.freshLabel("wbody") + let endLbl = b.freshLabel("wend") + + b.emitLabel(startLbl) + let cond = lowerExpr(ctx, node.whileCond) + b.emitJz(endLbl, cond) + lowerStmt(ctx, node.whileBody) + b.emitJmp(startLbl) + b.emitLabel(endLbl) + + # ── Loop (infinite) ── + of hLoop: + let startLbl = b.freshLabel("loop") + b.emitLabel(startLbl) + lowerStmt(ctx, node.loopBody) + b.emitJmp(startLbl) + + # ── Break ── + of hBreak: + # We emit a break label reference; the emitter tracks the nearest loop end + b.emitRawC("break;") + + # ── Continue ── + of hContinue: + b.emitRawC("continue;") + + # ── Alloca ── + of hAlloca: + let cType = typeToCStr(node.allocaType) + let name = node.allocaName + ctx.varTypes[name] = cType + ctx.varLirValues[name] = lirVar(name) + b.emitAlloca(name, cType) + + # ── Store ── + of hStore: + # If storing to a simple variable, use mov (direct assignment) + if node.storePtr.kind == hVar: + let val = lowerExpr(ctx, node.storeValue) + b.emitMov(lirVar(node.storePtr.varName), val) + else: + let ptrVal = lowerExpr(ctx, node.storePtr) + let val = lowerExpr(ctx, node.storeValue) + # ptrVal is a void* address; cast and store + let valCType = hirTypeToC(ctx, node.storeValue) + b.emitRawC(&"*({valCType}*){ptrVal.strVal} = {val.strVal};") + + # ── Assign ── + of hAssign: + let target = lowerExpr(ctx, node.assignTarget) + let value = lowerExpr(ctx, node.assignValue) + case node.assignOp + of tkAssign: + b.emitMov(target, value) + of tkPlusAssign: + let t = b.freshTemp() + b.emitBinOp(lirAdd, t, target, value) + b.emit(LirInstr(kind: lirStore, src: t, src2: target)) + of tkMinusAssign: + let t = b.freshTemp() + b.emitBinOp(lirSub, t, target, value) + b.emit(LirInstr(kind: lirStore, src: t, src2: target)) + else: + b.emitMov(target, value) + + # ── Call statement (void return) ── + of hCall: + var args: seq[LirValue] = @[] + for arg in node.callArgs: + args.add(lowerExpr(ctx, arg)) + b.emitCallVoid(node.callCallee, args) + + # ── CallIndirect statement ── + of hCallIndirect: + let callee = lowerExpr(ctx, node.callIndirectCallee) + var args: seq[LirValue] = @[] + for arg in node.callIndirectArgs: + args.add(lowerExpr(ctx, arg)) + b.emit(LirInstr(kind: lirCallIndirect, src: callee, extra: args)) + + # ── Block ── + of hBlock: + if node.isScope: + b.emitRawC("{") + for stmt in node.blockStmts: + lowerStmt(ctx, stmt) + if node.blockExpr != nil: + let exprVal = lowerExpr(ctx, node.blockExpr) + # If block is an expression, store result + discard + if node.isScope: + b.emitRawC("}") + + # ── Emit (inline C) ── + of hEmit: + b.emitRawC(node.emitCode) + + # ── Expression statement ── + else: + let exprVal = lowerExpr(ctx, node) + # Expression evaluated for side effects; temp is unused + discard + +# ── Module-level lowering ── + +proc lowerModuleToLir*(hirMod: HirModule): LirBuilder = + ## Convert a full HIR module into LIR functions. + var ctx = initLowerToLirCtx() + + for f in hirMod.funcs: + var params: seq[tuple[name: string, cType: string]] = @[] + for p in f.params: + let ct = typeToCStr(p.typ) + params.add((p.name, ct)) + ctx.varTypes[p.name] = ct + ctx.varLirValues[p.name] = lirVar(p.name) + + let retCT = if f.retType != nil: typeToCStr(f.retType) else: "void" + ctx.funcRetType = retCT + ctx.builder.beginFunc(f.name, params, retCT, f.isPublic) + + if f.body != nil: + if f.body.kind == hBlock: + for stmt in f.body.blockStmts: + lowerStmt(ctx, stmt) + if f.body.blockExpr != nil and f.retType != nil and f.retType.kind != tkVoid: + let val = lowerExpr(ctx, f.body.blockExpr) + ctx.builder.emitRet(val) + else: + lowerStmt(ctx, f.body) + + ctx.builder.endFunc() + + return ctx.builder