nx_nir.nx source
↩ module page · 164 lines · 5812 B
1// nx_nir.nx -- NishiLang IR builder, in pure NishiLang.
2//
3// THIRD CONCRETE STEP of the nxc2 self-hosting trajectory. Consumes
4// the AST produced by nx_nparse and emits IR instructions (mirroring
5// a subset of nxc2/ir.h's Opcode enum).
6//
7// Minimum viable opcodes:
8// NX_IR_CONST_I64 (value in operand 0)
9// NX_IR_RETURN (value-id in operand 0)
10// NX_IR_ADD/SUB/MUL (operand 0 + 1 -> result)
11//
12// SSA value-ids are dense integers (0, 1, 2, ...). Each NxInstr's
13// result_id is its own slot index. Operands reference earlier slots.
14
15import "syscalls.nx"
16import "nx_nlex.nx"
17import "nx_nparse.nx"
18
19// === opcodes (subset of nxc2/ir.h) =================================
20const NX_IR_CONST_I64: i64 = 1
21const NX_IR_RETURN: i64 = 2
22const NX_IR_ADD: i64 = 3
23const NX_IR_SUB: i64 = 4
24const NX_IR_MUL: i64 = 5
25const NX_IR_STORE_LOCAL: i64 = 6 // op0 = slot index, op1 = value-id
26const NX_IR_LOAD_LOCAL: i64 = 7 // op0 = slot index, result = loaded value
27const NX_IR_BR_IF_ZERO: i64 = 8 // op0 = value-id, op1 = label-id (jump if value is 0)
28const NX_IR_LABEL: i64 = 9 // op0 = label-id (placeholder, emits .Lid:)
29
30struct NxInstr {
31 opcode: i64,
32 op0: i64, // value-id or const value (for CONST_I64)
33 op1: i64,
34 result_id: i64, // index = position in instrs array
35}
36
37struct NxIrFunc {
38 name_start: i64,
39 name_len: i64,
40 return_ty: i64, // 0 = i64 (only one supported for now)
41 instrs: *NxInstr,
42 n_instrs: i64,
43 cap: i64,
44 n_locals: i64, // stack frame size = 8 * n_locals
45 n_labels: i64, // monotonic label-id allocator (for if/while later)
46}
47
48func nx_nir_func_new(cap: i64) -> *NxIrFunc {
49 let raw: *u8 = sys_mmap(64)
50 let f: *NxIrFunc = raw as *NxIrFunc
51 f.name_start = 0
52 f.name_len = 0
53 f.return_ty = 0
54 f.instrs = sys_mmap(cap * 32) as *NxInstr
55 f.n_instrs = 0
56 f.cap = cap
57 f.n_locals = 0
58 f.n_labels = 0
59 return f
60}
61
62func nx_nir_new_label(f: *NxIrFunc) -> i64 {
63 let id: i64 = f.n_labels
64 f.n_labels = f.n_labels + 1
65 return id
66}
67
68func nx_nir_instr_at(f: *NxIrFunc, i: i64) -> *NxInstr {
69 return (f.instrs as i64 + i * 32) as *NxInstr
70}
71
72func nx_nir_emit(f: *NxIrFunc, opcode: i64, op0: i64, op1: i64) -> i64 {
73 if f.n_instrs >= f.cap { return -1 }
74 let id: i64 = f.n_instrs
75 let ins: *NxInstr = nx_nir_instr_at(f, id)
76 ins.opcode = opcode
77 ins.op0 = op0
78 ins.op1 = op1
79 ins.result_id = id
80 f.n_instrs = f.n_instrs + 1
81 return id
82}
83
84// === AST -> IR lowering ============================================
85
86func nx_nir_lower_expr(f: *NxIrFunc, parser: *NxNParser, ast_idx: i64) -> i64 {
87 let node: *NxAst = nx_nparse_node_at(parser, ast_idx)
88 if node.kind == NX_AST_INT {
89 return nx_nir_emit(f, NX_IR_CONST_I64, node.a, 0)
90 }
91 if node.kind == NX_AST_BINOP {
92 let lhs_id: i64 = nx_nir_lower_expr(f, parser, node.a)
93 let rhs_id: i64 = nx_nir_lower_expr(f, parser, node.b)
94 var op: i64 = NX_IR_ADD
95 if node.c == NX_BIN_SUB { op = NX_IR_SUB }
96 if node.c == NX_BIN_MUL { op = NX_IR_MUL }
97 // NB: NX_BIN_DIV currently lowered as NX_IR_MUL (no IR_DIV yet).
98 return nx_nir_emit(f, op, lhs_id, rhs_id)
99 }
100 if node.kind == NX_AST_IDENT {
101 // node.c carries the local-slot index (from parser symbol table).
102 return nx_nir_emit(f, NX_IR_LOAD_LOCAL, node.c, 0)
103 }
104 return -1
105}
106
107func nx_nir_lower_stmt(f: *NxIrFunc, parser: *NxNParser, ast_idx: i64) -> i64 {
108 let node: *NxAst = nx_nparse_node_at(parser, ast_idx)
109 if node.kind == NX_AST_RETURN {
110 if node.a < 0 {
111 return nx_nir_emit(f, NX_IR_RETURN, -1, 0)
112 }
113 let val_id: i64 = nx_nir_lower_expr(f, parser, node.a)
114 return nx_nir_emit(f, NX_IR_RETURN, val_id, 0)
115 }
116 if node.kind == NX_AST_LET {
117 // Evaluate the value expression, then emit STORE_LOCAL.
118 let val_id: i64 = nx_nir_lower_expr(f, parser, node.a)
119 return nx_nir_emit(f, NX_IR_STORE_LOCAL, node.c, val_id)
120 }
121 if node.kind == NX_AST_IF {
122 // <cond>
123 // br_if_zero cond, Lend
124 // <then-block> (inlined since lower_block is defined later)
125 // Lend:
126 let cond_id: i64 = nx_nir_lower_expr(f, parser, node.a)
127 let lend: i64 = nx_nir_new_label(f)
128 nx_nir_emit(f, NX_IR_BR_IF_ZERO, cond_id, lend)
129 let block: *NxAst = nx_nparse_node_at(parser, node.b)
130 if block.kind == NX_AST_BLOCK {
131 let bn: i64 = block.e
132 if bn >= 1 { nx_nir_lower_stmt(f, parser, block.a) }
133 if bn >= 2 { nx_nir_lower_stmt(f, parser, block.b) }
134 if bn >= 3 { nx_nir_lower_stmt(f, parser, block.c) }
135 if bn >= 4 { nx_nir_lower_stmt(f, parser, block.d) }
136 }
137 return nx_nir_emit(f, NX_IR_LABEL, lend, 0)
138 }
139 return -1
140}
141
142func nx_nir_lower_block(f: *NxIrFunc, parser: *NxNParser, ast_idx: i64) -> i64 {
143 let block: *NxAst = nx_nparse_node_at(parser, ast_idx)
144 if block.kind != NX_AST_BLOCK { return -1 }
145 let n: i64 = block.e
146 if n >= 1 { nx_nir_lower_stmt(f, parser, block.a) }
147 if n >= 2 { nx_nir_lower_stmt(f, parser, block.b) }
148 if n >= 3 { nx_nir_lower_stmt(f, parser, block.c) }
149 if n >= 4 { nx_nir_lower_stmt(f, parser, block.d) }
150 return 0
151}
152
153func nx_nir_lower_func(parser: *NxNParser, func_ast_idx: i64, cap: i64) -> *NxIrFunc {
154 let ast: *NxAst = nx_nparse_node_at(parser, func_ast_idx)
155 if ast.kind != NX_AST_FUNC { return 0 as *NxIrFunc }
156 let f: *NxIrFunc = nx_nir_func_new(cap)
157 f.name_start = ast.a
158 f.name_len = ast.b
159 f.return_ty = 0
160 // Body is at ast.d
161 nx_nir_lower_block(f, parser, ast.d)
162 f.n_locals = parser.n_locals
163 return f
164}