feat: LIR backend replaces direct HIR→C emission (v0.3.0)
- 42 LIR instructions in bootstrap/lir.nim - HIR→LIR lowering in bootstrap/lir_lower.nim - LIR→C emission with full struct/enum/vtable support - All 22 examples + 5 Nim unit tests passing - Updated README status and backend description
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
# Compiled binary
|
# Compiled binary
|
||||||
buxc
|
buxc
|
||||||
|
buxc_lir
|
||||||
src/main
|
src/main
|
||||||
|
|
||||||
# Nim cache
|
# Nim cache
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
> **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.
|
> 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.
|
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 |
|
| **Interfaces** | `interface` + `extend` for trait-like behavior |
|
||||||
| **Error Handling** | `Result<T,E>`, `Option<T>`, and the `?` operator |
|
| **Error Handling** | `Result<T,E>`, `Option<T>`, and the `?` operator |
|
||||||
| **Standard Library** | `Io`, `Array`, `String`, `Map`, `Fs`, `Mem`, `Set`, `Path`, `Math`, `Task`, `Channel`, `Sync`, `Os`, `Time`, `Process` |
|
| **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 |
|
| **Strings** | Raw multi-line backtick strings (`...`), C-string interop |
|
||||||
| **Gradual Ownership** | `@[Checked]` + `&T`/`&mut T` borrow checking |
|
| **Gradual Ownership** | `@[Checked]` + `&T`/`&mut T` borrow checking |
|
||||||
| **Async/Await** | `async func`, `spawn`, `.await` with stackful coroutines |
|
| **Async/Await** | `async func`, `spawn`, `.await` with stackful coroutines |
|
||||||
|
|||||||
+4
-3
@@ -1,5 +1,5 @@
|
|||||||
import std/[os, strutils, terminal, strformat, osproc, sets]
|
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
|
type
|
||||||
ColorMode* = enum
|
ColorMode* = enum
|
||||||
@@ -454,8 +454,9 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
let hirMod = lowerModule(unifiedModule, semaCtx)
|
let hirMod = lowerModule(unifiedModule, semaCtx)
|
||||||
var cbe = initCBackend()
|
let lirBuilder = lowerModuleToLir(hirMod)
|
||||||
var allCCode = cbe.emitModule(hirMod)
|
var lirCbe = initLirCBackend()
|
||||||
|
var allCCode = lirCbe.emitModule(lirBuilder, hirMod)
|
||||||
|
|
||||||
# Write C file
|
# Write C file
|
||||||
let cFile = buildDir / "main.c"
|
let cFile = buildDir / "main.c"
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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 <stdio.h>")
|
||||||
|
be.emitLine("#include <stdlib.h>")
|
||||||
|
be.emitLine("#include <stdint.h>")
|
||||||
|
be.emitLine("#include <stdbool.h>")
|
||||||
|
be.emitLine("#include <string.h>")
|
||||||
|
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
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user