diff options
Diffstat (limited to 'src/compiler.c')
-rw-r--r-- | src/compiler.c | 1427 |
1 files changed, 1241 insertions, 186 deletions
diff --git a/src/compiler.c b/src/compiler.c index 5bddcb6..c2b48b3 100644 --- a/src/compiler.c +++ b/src/compiler.c | |||
@@ -25,7 +25,7 @@ typedef struct Instruction { | |||
25 | typedef union Constant { | 25 | typedef union Constant { |
26 | s64 i; | 26 | s64 i; |
27 | u64 u; | 27 | u64 u; |
28 | double f; | 28 | f64 f; |
29 | ptrsize ptr; | 29 | ptrsize ptr; |
30 | } Constant; | 30 | } Constant; |
31 | 31 | ||
@@ -34,9 +34,23 @@ typedef struct LineCol { | |||
34 | sz col; | 34 | sz col; |
35 | } LineCol; | 35 | } LineCol; |
36 | 36 | ||
37 | typedef struct Function { | ||
38 | Str name; | ||
39 | sz param_arity; | ||
40 | sz return_arity; | ||
41 | sz index; | ||
42 | } Function; | ||
43 | |||
44 | MAPDEF(FunctionMap, funcmap, Str, Function, str_hash, str_eq) | ||
45 | |||
37 | typedef struct Chunk { | 46 | typedef struct Chunk { |
38 | sz id; | 47 | sz id; |
48 | Str name; | ||
49 | struct Chunk *parent; | ||
50 | |||
39 | Instruction *code; | 51 | Instruction *code; |
52 | IntIntMap *labels; // label -> chunk_index | ||
53 | sz labels_idx; | ||
40 | 54 | ||
41 | // Constant values that fit in 64 bits. | 55 | // Constant values that fit in 64 bits. |
42 | Constant *constants; | 56 | Constant *constants; |
@@ -52,45 +66,91 @@ typedef struct Chunk { | |||
52 | Variable *vars; | 66 | Variable *vars; |
53 | StrVarMap *varmap; | 67 | StrVarMap *varmap; |
54 | sz var_off; | 68 | sz var_off; |
69 | sz param_off; | ||
55 | 70 | ||
56 | // Number of registers currently used in this chunk. | 71 | // Number of registers currently used in this chunk. |
57 | sz reg_idx; | 72 | sz reg_idx; |
58 | 73 | ||
74 | // Number of functions currently used in this chunk. | ||
75 | struct Chunk **functions; | ||
76 | FunctionMap *funmap; | ||
77 | sz fun_idx; | ||
78 | |||
59 | // Debugging. | 79 | // Debugging. |
60 | Str file_name; | 80 | Str file_name; |
61 | Arena *storage; | 81 | Arena *storage; |
62 | LineCol *linecol; | 82 | LineCol *linecol; |
63 | } Chunk; | 83 | } Chunk; |
64 | 84 | ||
85 | typedef struct Compiler { | ||
86 | Chunk main_chunk; | ||
87 | Str file_name; | ||
88 | Arena *storage; | ||
89 | |||
90 | // Tables. | ||
91 | StrSet *integer_types; | ||
92 | StrSet *numeric_types; | ||
93 | |||
94 | // Destinations. | ||
95 | sz lab_pre; | ||
96 | sz lab_post; | ||
97 | } Compiler; | ||
98 | |||
65 | typedef enum OpCode { | 99 | typedef enum OpCode { |
66 | // OP DST A B | 100 | // OP DST A B |
67 | // --------------------------------------------------------------- | 101 | // --------------------------------------------------------------- |
68 | // VM/high level instructions. | 102 | // VM/high level instructions. |
69 | OP_HALT, // halt | 103 | OP_HALT, // halt |
104 | // NOTE: LDGVAR/STGVAR* could be obtained in terms of LDGADDR. | ||
70 | OP_STGVARI, // stgvari vx, ca | 105 | OP_STGVARI, // stgvari vx, ca |
71 | OP_STGVAR, // stgvar vx, ra | 106 | OP_STGVAR, // stgvar vx, ra |
72 | OP_LDGVAR, // ldgvar rx, vx | 107 | OP_LDGVAR, // ldgvar rx, va |
108 | OP_LDGADDR, // ldgaddr rx, va | ||
109 | OP_STLVARI, // stlvari vx, ca | ||
110 | OP_STLVAR, // stlvar vx, ra | ||
111 | OP_LDLVAR, // ldlvar rx, va | ||
112 | OP_LDLADDR, // ldladdr rx, va | ||
113 | OP_LDSTR, // ldstr rx, sa ; Stores the address of the string sa into rx | ||
114 | // Functions. | ||
115 | OP_CALL, // call fx ; Call the function fx | ||
116 | OP_RECUR, // recur ; Jump to the beginning of the function. | ||
117 | OP_RET, // ret ; Returns from current function | ||
118 | OP_RESERVE, // reserve cx ; Increments the stack pointer by cx bytes | ||
119 | OP_POP, // pop rx ; Pops the last value of the stack into rx. | ||
120 | OP_PUSH, // push rx ; Push the rx value to the stack. | ||
121 | OP_PUSHI, // pushi cx ; Push the cx value to the stack. | ||
122 | OP_PUTRET, // putret rx ; Put rx into the return value memory. | ||
123 | OP_PUTRETI, // putreti cx ; Put cx into the return value memory. | ||
124 | // Printing values with builtin print/println functions. | ||
125 | OP_PRINTSTR, // p rx | ||
126 | OP_PRINTSTRI, // p cx | ||
127 | OP_PRINTS64, // p rx | ||
128 | OP_PRINTS64I, // p cx | ||
129 | OP_PRINTF64, // p rx | ||
130 | OP_PRINTF64I, // p cx | ||
131 | OP_PRINTBOOL, // p rx | ||
132 | OP_PRINTBOOLI, // p cx | ||
73 | // Load/Store instructions. | 133 | // Load/Store instructions. |
74 | OP_LD8K, // ld8k rx, ca -> u8 rx = ca | 134 | OP_LD8K, // ld8k rx, ca -> u8 rx = ca |
75 | OP_LD16K, // ld16k rx, ca -> u16 rx = ca | 135 | OP_LD16K, // ld16k rx, ca -> u16 rx = ca |
76 | OP_LD32K, // ld32k rx, ca -> u32 rx = ca | 136 | OP_LD32K, // ld32k rx, ca -> u32 rx = ca |
77 | OP_LD64K, // ld64k rx, ca -> u64 rx = ca | 137 | OP_LD64K, // ld64k rx, ca -> u64 rx = ca |
78 | OP_LD8I, // ld8i rx, ra, cb -> u8 *p; rx = p[ra + cb] | 138 | OP_LD8I, // ld8i rx, ra, cb -> u8 *p = ra; rx = p[cb] |
79 | OP_LD16I, // ld16i rx, ra, cb -> u16 *p; rx = p[ra + cb] | 139 | OP_LD16I, // ld16i rx, ra, cb -> u16 *p = ra; rx = p[cb] |
80 | OP_LD32I, // ld32i rx, ra, cb -> u32 *p; rx = p[ra + cb] | 140 | OP_LD32I, // ld32i rx, ra, cb -> u32 *p = ra; rx = p[cb] |
81 | OP_LD64I, // ld64i rx, ra, cb -> u64 *p; rx = p[ra + cb] | 141 | OP_LD64I, // ld64i rx, ra, cb -> u64 *p = ra; rx = p[cb] |
82 | OP_LD8, // ld8 rx, ra, rb -> u8 *p; rx = p[ra + rb] | 142 | OP_LD8, // ld8 rx, ra, rb -> u8 *p = ra; rx = p[rb] |
83 | OP_LD16, // ld16 rx, ra, rb -> u16 *p; rx = p[ra + rb] | 143 | OP_LD16, // ld16 rx, ra, rb -> u16 *p = ra; rx = p[rb] |
84 | OP_LD32, // ld32 rx, ra, rb -> u32 *p; rx = p[ra + rb] | 144 | OP_LD32, // ld32 rx, ra, rb -> u32 *p = ra; rx = p[rb] |
85 | OP_LD64, // ld64 rx, ra, rb -> u64 *p; rx = p[ra + rb] | 145 | OP_LD64, // ld64 rx, ra, rb -> u64 *p = ra; rx = p[rb] |
86 | OP_ST8I, // st8i rx, ra, cb -> u8 *p; p[ra + cb] = rx | 146 | OP_ST8I, // st8i rx, ra, cb -> u8 *p = ra; p[cb] = rx |
87 | OP_ST16I, // st16i rx, ra, cb -> u16 *p; p[ra + cb] = rx | 147 | OP_ST16I, // st16i rx, ra, cb -> u16 *p = ra; p[cb] = rx |
88 | OP_ST32I, // st32i rx, ra, cb -> u32 *p; p[ra + cb] = rx | 148 | OP_ST32I, // st32i rx, ra, cb -> u32 *p = ra; p[cb] = rx |
89 | OP_ST64I, // st64i rx, ra, cb -> u64 *p; p[ra + cb] = rx | 149 | OP_ST64I, // st64i rx, ra, cb -> u64 *p = ra; p[cb] = rx |
90 | OP_ST8, // st8 rx, ra, rb -> u8 *p; p[ra + rb] = rx | 150 | OP_ST8, // st8 rx, ra, rb -> u8 *p = ra; p[rb] = rx |
91 | OP_ST16, // st16 rx, ra, rb -> u16 *p; p[ra + rb] = rx | 151 | OP_ST16, // st16 rx, ra, rb -> u16 *p = ra; p[rb] = rx |
92 | OP_ST32, // st32 rx, ra, rb -> u32 *p; p[ra + rb] = rx | 152 | OP_ST32, // st32 rx, ra, rb -> u32 *p = ra; p[rb] = rx |
93 | OP_ST64, // st64 rx, ra, rb -> u64 *p; p[ra + rb] = rx | 153 | OP_ST64, // st64 rx, ra, rb -> u64 *p = ra; p[rb] = rx |
94 | // Integer arithmetic (only int/s64 for now). | 154 | // Integer arithmetic (only int/s64 for now). |
95 | OP_ADDI, // addk rx, ra, cb | 155 | OP_ADDI, // addk rx, ra, cb |
96 | OP_SUBI, // subk rx, ra, cb | 156 | OP_SUBI, // subk rx, ra, cb |
@@ -142,26 +202,52 @@ typedef enum OpCode { | |||
142 | OP_BITRSHIFTI, // shri rx, ra, cb | 202 | OP_BITRSHIFTI, // shri rx, ra, cb |
143 | OP_BITANDI, // bandi rx, ra, cb | 203 | OP_BITANDI, // bandi rx, ra, cb |
144 | OP_BITORI, // bori rx, ra, cb | 204 | OP_BITORI, // bori rx, ra, cb |
205 | OP_BITXORI, // bxor rx, ra, cb | ||
145 | OP_BITNOTI, // bnoti rx, ca | 206 | OP_BITNOTI, // bnoti rx, ca |
146 | OP_BITLSHIFT, // shl rx, ra, rb | 207 | OP_BITLSHIFT, // shl rx, ra, rb |
147 | OP_BITRSHIFT, // shr rx, ra, rb | 208 | OP_BITRSHIFT, // shr rx, ra, rb |
148 | OP_BITAND, // band rx, ra, rb | 209 | OP_BITAND, // band rx, ra, rb |
149 | OP_BITOR, // bor rx, ra, rb | 210 | OP_BITOR, // bor rx, ra, rb |
211 | OP_BITXOR, // bxor rx, ra, rb | ||
150 | OP_BITNOT, // bnot rx, ra | 212 | OP_BITNOT, // bnot rx, ra |
151 | // Jump instructions. | 213 | // Jump instructions. |
152 | OP_JMPI, // jmp cx ; cx := signed offset | 214 | OP_JMP, // jmp lx ; jmp to label lx |
153 | OP_JMPFI, // jmpf cx, ca ; rx := condition, ca := offset | 215 | OP_JMPF, // jmpf lx, rx ; jmp to label lx if rx is false |
154 | OP_JMPTI, // jmpt cx, ca ; rx := condition, ca := offset | 216 | OP_JMPT, // jmpt lx, rx ; jmp to label lx if rx is true |
155 | OP_JMP, // jmp rx ; rx := signed offset | 217 | OP_JMPFI, // jmpf lx, cx ; jmp to label lx if rx is false |
156 | OP_JMPF, // jmpf rx, ca ; rx := condition, ca := offset | 218 | OP_JMPTI, // jmpt lx, cx ; jmp to label lx if rx is true |
157 | OP_JMPT, // jmpt rx, ca ; rx := condition, ca := offset | ||
158 | } OpCode; | 219 | } OpCode; |
159 | 220 | ||
160 | Str op_str[] = { | 221 | Str op_str[] = { |
222 | // High level ops. | ||
161 | [OP_HALT] = cstr("HALT "), | 223 | [OP_HALT] = cstr("HALT "), |
162 | [OP_STGVAR] = cstr("STGVAR "), | 224 | [OP_STGVAR] = cstr("STGVAR "), |
163 | [OP_STGVARI] = cstr("STGVARI "), | 225 | [OP_STGVARI] = cstr("STGVARI "), |
164 | [OP_LDGVAR] = cstr("LDGVAR "), | 226 | [OP_LDGVAR] = cstr("LDGVAR "), |
227 | [OP_LDGADDR] = cstr("LDGADDR "), | ||
228 | [OP_STLVAR] = cstr("STLVAR "), | ||
229 | [OP_STLVARI] = cstr("STLVARI "), | ||
230 | [OP_LDLVAR] = cstr("LDLVAR "), | ||
231 | [OP_LDLADDR] = cstr("LDLADDR "), | ||
232 | [OP_LDSTR] = cstr("LDSTR "), | ||
233 | [OP_PRINTSTR] = cstr("PRNSTR "), | ||
234 | [OP_PRINTSTRI] = cstr("PRNSTRI "), | ||
235 | [OP_PRINTS64] = cstr("PRNS64 "), | ||
236 | [OP_PRINTS64I] = cstr("PRNS64I "), | ||
237 | [OP_PRINTF64] = cstr("PRNF64 "), | ||
238 | [OP_PRINTF64I] = cstr("PRNF64I "), | ||
239 | [OP_PRINTBOOL] = cstr("PRNBOOL "), | ||
240 | [OP_PRINTBOOLI] = cstr("PRNBOOLI"), | ||
241 | [OP_PUTRET] = cstr("PUTRET "), | ||
242 | [OP_PUTRETI] = cstr("PUTRETI "), | ||
243 | // Functions. | ||
244 | [OP_CALL] = cstr("CALL "), | ||
245 | [OP_RECUR] = cstr("RECUR "), | ||
246 | [OP_RET] = cstr("RET "), | ||
247 | [OP_RESERVE] = cstr("RESERVE "), | ||
248 | [OP_POP] = cstr("POP "), | ||
249 | [OP_PUSH] = cstr("PUSH "), | ||
250 | [OP_PUSHI] = cstr("PUSHI "), | ||
165 | // Load ops. | 251 | // Load ops. |
166 | [OP_LD8K] = cstr("LD8K "), | 252 | [OP_LD8K] = cstr("LD8K "), |
167 | [OP_LD16K] = cstr("LD16K "), | 253 | [OP_LD16K] = cstr("LD16K "), |
@@ -234,19 +320,20 @@ Str op_str[] = { | |||
234 | [OP_BITRSHIFTI] = cstr("RSHI "), | 320 | [OP_BITRSHIFTI] = cstr("RSHI "), |
235 | [OP_BITANDI] = cstr("BANDI "), | 321 | [OP_BITANDI] = cstr("BANDI "), |
236 | [OP_BITORI] = cstr("BORI "), | 322 | [OP_BITORI] = cstr("BORI "), |
323 | [OP_BITXORI] = cstr("BXORI "), | ||
237 | [OP_BITNOTI] = cstr("BNOTI "), | 324 | [OP_BITNOTI] = cstr("BNOTI "), |
238 | [OP_BITLSHIFT] = cstr("LSH "), | 325 | [OP_BITLSHIFT] = cstr("LSH "), |
239 | [OP_BITRSHIFT] = cstr("RSH "), | 326 | [OP_BITRSHIFT] = cstr("RSH "), |
240 | [OP_BITAND] = cstr("BAND "), | 327 | [OP_BITAND] = cstr("BAND "), |
241 | [OP_BITOR] = cstr("BOR "), | 328 | [OP_BITOR] = cstr("BOR "), |
329 | [OP_BITXOR] = cstr("XBOR "), | ||
242 | [OP_BITNOT] = cstr("BNOT "), | 330 | [OP_BITNOT] = cstr("BNOT "), |
243 | // Jump instructions. | 331 | // Jump instructions. |
244 | [OP_JMPI] = cstr("JMPI "), | ||
245 | [OP_JMPFI] = cstr("JMPFI "), | ||
246 | [OP_JMPTI] = cstr("JMPTI "), | ||
247 | [OP_JMP] = cstr("JMP "), | 332 | [OP_JMP] = cstr("JMP "), |
248 | [OP_JMPF] = cstr("JMPF "), | 333 | [OP_JMPF] = cstr("JMPF "), |
249 | [OP_JMPT] = cstr("JMPT "), | 334 | [OP_JMPT] = cstr("JMPT "), |
335 | [OP_JMPFI] = cstr("JMPFI "), | ||
336 | [OP_JMPTI] = cstr("JMPTI "), | ||
250 | }; | 337 | }; |
251 | 338 | ||
252 | typedef enum { | 339 | typedef enum { |
@@ -254,6 +341,7 @@ typedef enum { | |||
254 | COMP_CONST, | 341 | COMP_CONST, |
255 | COMP_STRING, | 342 | COMP_STRING, |
256 | COMP_REG, | 343 | COMP_REG, |
344 | COMP_RET, | ||
257 | COMP_ERR, | 345 | COMP_ERR, |
258 | } CompResultType; | 346 | } CompResultType; |
259 | 347 | ||
@@ -262,23 +350,108 @@ typedef struct CompResult { | |||
262 | CompResultType type; | 350 | CompResultType type; |
263 | } CompResult; | 351 | } CompResult; |
264 | 352 | ||
265 | CompResult compile_expr(Chunk *chunk, Node *node); | 353 | CompResult compile_expr(Compiler *compiler, Chunk *chunk, Node *node); |
266 | 354 | ||
267 | #define EMIT_OP(OP, DST, A, B, NODE, CHUNK) \ | 355 | sz |
268 | do { \ | 356 | add_constant(Chunk *chunk, sz value) { |
269 | Instruction inst = (Instruction){ \ | 357 | IntIntMap *map = intintmap_lookup(&chunk->intmap, value); |
270 | .op = (OP), \ | 358 | // Make sure we don't have duplicated constants. |
271 | .dst = (DST), \ | 359 | if (!map) { |
272 | .a = (A), \ | 360 | map = intintmap_insert(&chunk->intmap, value, chunk->const_idx++, |
273 | .b = (B), \ | 361 | chunk->storage); |
274 | }; \ | 362 | Constant c = (Constant){.i = value}; |
275 | array_push((CHUNK)->code, inst, (CHUNK)->storage); \ | 363 | array_push(chunk->constants, c, chunk->storage); |
276 | LineCol linecol = (LineCol){.line = (NODE)->line, .col = (NODE)->col}; \ | 364 | } |
277 | array_push((CHUNK)->linecol, linecol, (CHUNK)->storage); \ | 365 | return map->val; |
278 | } while (0) | 366 | } |
367 | |||
368 | sz | ||
369 | add_string(Chunk *chunk, Str string) { | ||
370 | // Make sure we don't have duplicated string. | ||
371 | StrIntMap *map = strintmap_lookup(&chunk->strmap, string); | ||
372 | if (!map) { | ||
373 | map = strintmap_insert(&chunk->strmap, string, chunk->str_idx++, | ||
374 | chunk->storage); | ||
375 | array_push(chunk->strings, string, chunk->storage); | ||
376 | } | ||
377 | return map->val; | ||
378 | } | ||
379 | |||
380 | sz | ||
381 | add_variable(Chunk *chunk, Str name, Str type, sz arr_size) { | ||
382 | sz idx = array_size(chunk->vars); | ||
383 | sz size = 8; | ||
384 | // TODO: get type storage from a table to consider all the basic | ||
385 | // types as well as user defined ones. | ||
386 | if (str_eq(type, cstr("str"))) { | ||
387 | size = 16; | ||
388 | } | ||
389 | if (arr_size) { | ||
390 | // TODO: get the proper storage size for the multiplication. | ||
391 | size *= arr_size; | ||
392 | // FIXME: this should be done on the static analysis, plus, | ||
393 | // we shouldn't be checking all these types by hand, but | ||
394 | // using the symbol tables. | ||
395 | type = str_remove_prefix(type, cstr("@")); | ||
396 | type = str_concat(cstr("[]"), type, chunk->storage); | ||
397 | } | ||
398 | Variable var = (Variable){ | ||
399 | .name = name, | ||
400 | .type = type, | ||
401 | .size = size, | ||
402 | .offset = chunk->var_off, | ||
403 | .idx = idx, | ||
404 | }; | ||
405 | varmap_insert(&chunk->varmap, name, var, chunk->storage); | ||
406 | array_push(chunk->vars, var, chunk->storage); | ||
407 | chunk->var_off += size; | ||
408 | return idx; | ||
409 | } | ||
410 | |||
411 | void | ||
412 | emit_op(OpCode op, sz dst, sz a, sz b, Node *node, Chunk *chunk) { | ||
413 | Instruction inst = (Instruction){ | ||
414 | .op = op, | ||
415 | .dst = dst, | ||
416 | .a = a, | ||
417 | .b = b, | ||
418 | }; | ||
419 | array_push(chunk->code, inst, chunk->storage); | ||
420 | LineCol linecol = (LineCol){0}; | ||
421 | if (node) { | ||
422 | linecol = (LineCol){.line = node->line, .col = node->col}; | ||
423 | } | ||
424 | array_push(chunk->linecol, linecol, chunk->storage); | ||
425 | } | ||
426 | |||
427 | void | ||
428 | emit_fat_copy(Chunk *chunk, Node *node, sz dst_addr, sz src_addr) { | ||
429 | sz reg_dst = chunk->reg_idx++; | ||
430 | |||
431 | // Store the fat string pointer into the variable. | ||
432 | sz zero = add_constant(chunk, 0); | ||
433 | sz one = add_constant(chunk, 1); | ||
434 | |||
435 | // Get the value for the first word of the string | ||
436 | // pointer. | ||
437 | emit_op(OP_LD64I, reg_dst, src_addr, zero, node, chunk); | ||
438 | emit_op(OP_ST64I, reg_dst, dst_addr, zero, node, chunk); | ||
439 | emit_op(OP_LD64I, reg_dst, src_addr, one, node, chunk); | ||
440 | emit_op(OP_ST64I, reg_dst, dst_addr, one, node, chunk); | ||
441 | } | ||
442 | |||
443 | void disassemble_chunk(Chunk chunk); | ||
444 | |||
445 | void | ||
446 | emit_compile_err(Compiler *compiler, Chunk *chunk, Node *node) { | ||
447 | disassemble_chunk(*chunk); | ||
448 | eprintln("%s:%d:%d: error: compilation error on: %s", compiler->file_name, | ||
449 | node->line, node->col, node_str[node->kind]); | ||
450 | exit(EXIT_FAILURE); | ||
451 | } | ||
279 | 452 | ||
280 | CompResult | 453 | CompResult |
281 | compile_binary(Chunk *chunk, Node *node) { | 454 | compile_binary(Compiler *compiler, Chunk *chunk, Node *node) { |
282 | OpCode op = OP_HALT; | 455 | OpCode op = OP_HALT; |
283 | OpCode opi = OP_HALT; | 456 | OpCode opi = OP_HALT; |
284 | OpCode ldop = OP_LD64K; | 457 | OpCode ldop = OP_LD64K; |
@@ -367,6 +540,10 @@ compile_binary(Chunk *chunk, Node *node) { | |||
367 | op = OP_BITOR; | 540 | op = OP_BITOR; |
368 | opi = OP_BITORI; | 541 | opi = OP_BITORI; |
369 | } break; | 542 | } break; |
543 | case NODE_BITXOR: { | ||
544 | op = OP_BITXOR; | ||
545 | opi = OP_BITXORI; | ||
546 | } break; | ||
370 | case NODE_BITAND: { | 547 | case NODE_BITAND: { |
371 | op = OP_BITAND; | 548 | op = OP_BITAND; |
372 | opi = OP_BITANDI; | 549 | opi = OP_BITANDI; |
@@ -381,19 +558,20 @@ compile_binary(Chunk *chunk, Node *node) { | |||
381 | } break; | 558 | } break; |
382 | default: break; | 559 | default: break; |
383 | } | 560 | } |
384 | CompResult comp_a = compile_expr(chunk, node->left); | 561 | CompResult comp_a = compile_expr(compiler, chunk, node->binary.left); |
385 | CompResult comp_b = compile_expr(chunk, node->right); | 562 | CompResult comp_b = compile_expr(compiler, chunk, node->binary.right); |
386 | sz reg_a; | 563 | sz reg_a; |
387 | sz reg_b; | 564 | sz reg_b; |
388 | switch (comp_a.type) { | 565 | switch (comp_a.type) { |
389 | case COMP_CONST: { | 566 | case COMP_CONST: { |
390 | reg_a = chunk->reg_idx++; | 567 | reg_a = chunk->reg_idx++; |
391 | EMIT_OP(ldop, reg_a, comp_a.idx, 0, node, chunk); | 568 | emit_op(ldop, reg_a, comp_a.idx, 0, node, chunk); |
392 | } break; | 569 | } break; |
393 | case COMP_REG: { | 570 | case COMP_REG: { |
394 | reg_a = comp_a.idx; | 571 | reg_a = comp_a.idx; |
395 | } break; | 572 | } break; |
396 | default: { | 573 | default: { |
574 | emit_compile_err(compiler, chunk, node); | ||
397 | return (CompResult){.type = COMP_ERR}; | 575 | return (CompResult){.type = COMP_ERR}; |
398 | } break; | 576 | } break; |
399 | } | 577 | } |
@@ -406,16 +584,17 @@ compile_binary(Chunk *chunk, Node *node) { | |||
406 | reg_b = comp_b.idx; | 584 | reg_b = comp_b.idx; |
407 | } break; | 585 | } break; |
408 | default: { | 586 | default: { |
587 | emit_compile_err(compiler, chunk, node); | ||
409 | return (CompResult){.type = COMP_ERR}; | 588 | return (CompResult){.type = COMP_ERR}; |
410 | } break; | 589 | } break; |
411 | } | 590 | } |
412 | sz reg_dst = chunk->reg_idx++; // Better for optimization | 591 | sz reg_dst = chunk->reg_idx++; // Better for optimization |
413 | EMIT_OP(op, reg_dst, reg_a, reg_b, node, chunk); | 592 | emit_op(op, reg_dst, reg_a, reg_b, node, chunk); |
414 | return (CompResult){.type = COMP_REG, .idx = reg_dst}; | 593 | return (CompResult){.type = COMP_REG, .idx = reg_dst}; |
415 | } | 594 | } |
416 | 595 | ||
417 | CompResult | 596 | CompResult |
418 | compile_unary(Chunk *chunk, Node *node) { | 597 | compile_unary(Compiler *compiler, Chunk *chunk, Node *node) { |
419 | OpCode op = OP_HALT; | 598 | OpCode op = OP_HALT; |
420 | OpCode opi = OP_HALT; | 599 | OpCode opi = OP_HALT; |
421 | switch (node->kind) { | 600 | switch (node->kind) { |
@@ -429,7 +608,7 @@ compile_unary(Chunk *chunk, Node *node) { | |||
429 | } break; | 608 | } break; |
430 | default: break; | 609 | default: break; |
431 | } | 610 | } |
432 | CompResult comp_a = compile_expr(chunk, node->left); | 611 | CompResult comp_a = compile_expr(compiler, chunk, node->binary.left); |
433 | sz reg_a; | 612 | sz reg_a; |
434 | switch (comp_a.type) { | 613 | switch (comp_a.type) { |
435 | case COMP_CONST: { | 614 | case COMP_CONST: { |
@@ -440,30 +619,18 @@ compile_unary(Chunk *chunk, Node *node) { | |||
440 | reg_a = comp_a.idx; | 619 | reg_a = comp_a.idx; |
441 | } break; | 620 | } break; |
442 | default: { | 621 | default: { |
622 | emit_compile_err(compiler, chunk, node); | ||
443 | return (CompResult){.type = COMP_ERR}; | 623 | return (CompResult){.type = COMP_ERR}; |
444 | } break; | 624 | } break; |
445 | } | 625 | } |
446 | sz reg_dst = chunk->reg_idx++; | 626 | sz reg_dst = chunk->reg_idx++; |
447 | EMIT_OP(op, reg_dst, reg_a, 0, node, chunk); | 627 | emit_op(op, reg_dst, reg_a, 0, node, chunk); |
448 | return (CompResult){.type = COMP_REG, .idx = reg_dst}; | 628 | return (CompResult){.type = COMP_REG, .idx = reg_dst}; |
449 | } | 629 | } |
450 | 630 | ||
451 | sz | ||
452 | add_constant(Chunk *chunk, sz value) { | ||
453 | IntIntMap *map = intintmap_lookup(&chunk->intmap, value); | ||
454 | // Make sure we don't have duplicated constants. | ||
455 | if (!map) { | ||
456 | map = intintmap_insert(&chunk->intmap, value, chunk->const_idx++, | ||
457 | chunk->storage); | ||
458 | Constant c = (Constant){.i = value}; | ||
459 | array_push(chunk->constants, c, chunk->storage); | ||
460 | } | ||
461 | return map->val; | ||
462 | } | ||
463 | |||
464 | CompResult | 631 | CompResult |
465 | compile_if(Chunk *chunk, Node *node) { | 632 | compile_if(Compiler *compiler, Chunk *chunk, Node *node) { |
466 | CompResult cond = compile_expr(chunk, node->cond_if); | 633 | CompResult cond = compile_expr(compiler, chunk, node->ifelse.cond); |
467 | OpCode jmpop; | 634 | OpCode jmpop; |
468 | switch (cond.type) { | 635 | switch (cond.type) { |
469 | case COMP_CONST: { | 636 | case COMP_CONST: { |
@@ -473,84 +640,904 @@ compile_if(Chunk *chunk, Node *node) { | |||
473 | jmpop = OP_JMPF; | 640 | jmpop = OP_JMPF; |
474 | } break; | 641 | } break; |
475 | default: { | 642 | default: { |
643 | emit_compile_err(compiler, chunk, node); | ||
476 | return (CompResult){.type = COMP_ERR}; | 644 | return (CompResult){.type = COMP_ERR}; |
477 | } break; | 645 | } break; |
478 | } | 646 | } |
479 | sz jump_a = array_size(chunk->code); | ||
480 | sz reg_dst = 255; | ||
481 | bool has_value = !str_eq(node->type, cstr("nil")); | ||
482 | if (has_value) { | ||
483 | reg_dst = chunk->reg_idx++; | ||
484 | } | ||
485 | 647 | ||
486 | // Jump to the `false` branch. | 648 | if (!str_eq(node->type, cstr("nil")) && |
487 | EMIT_OP(jmpop, cond.idx, 0xff, 0, node->cond_if, chunk); | 649 | !str_has_prefix(node->type, cstr("ret:")) && |
650 | !str_has_prefix(node->type, cstr("flow:"))) { | ||
651 | sz reg_dst = chunk->reg_idx++; | ||
488 | 652 | ||
489 | // Condition is true. | 653 | // Jump to the `false` branch. |
490 | CompResult then_expr = compile_expr(chunk, node->cond_expr); | 654 | sz lab0 = chunk->labels_idx++; |
491 | if (has_value) { | 655 | emit_op(jmpop, lab0, cond.idx, 0, node->ifelse.cond, chunk); |
656 | |||
657 | // Condition is true. | ||
658 | CompResult then_expr = | ||
659 | compile_expr(compiler, chunk, node->ifelse.expr_true); | ||
492 | switch (then_expr.type) { | 660 | switch (then_expr.type) { |
493 | case COMP_CONST: { | 661 | case COMP_CONST: { |
494 | EMIT_OP(OP_LD64K, reg_dst, then_expr.idx, 0, node->cond_if, | 662 | emit_op(OP_LD64K, reg_dst, then_expr.idx, 0, node->ifelse.cond, |
495 | chunk); | 663 | chunk); |
496 | } break; | 664 | } break; |
497 | case COMP_REG: { | 665 | case COMP_REG: { |
498 | EMIT_OP(OP_MOV64, reg_dst, then_expr.idx, 0, node->cond_if, | 666 | emit_op(OP_MOV64, reg_dst, then_expr.idx, 0, node->ifelse.cond, |
499 | chunk); | 667 | chunk); |
500 | } break; | 668 | } break; |
669 | case COMP_RET: break; | ||
670 | default: { | ||
671 | emit_compile_err(compiler, chunk, node); | ||
672 | return (CompResult){.type = COMP_ERR}; | ||
673 | } break; | ||
674 | } | ||
675 | |||
676 | // Jump to the end of the expression. | ||
677 | sz pos0 = array_size(chunk->code); | ||
678 | sz lab1 = chunk->labels_idx++; | ||
679 | emit_op(OP_JMP, lab1, 0, 0, node->ifelse.expr_else, chunk); | ||
680 | |||
681 | // Else expression. | ||
682 | CompResult else_expr = | ||
683 | compile_expr(compiler, chunk, node->ifelse.expr_else); | ||
684 | switch (else_expr.type) { | ||
685 | case COMP_CONST: { | ||
686 | emit_op(OP_LD64K, reg_dst, else_expr.idx, 0, | ||
687 | node->ifelse.expr_else, chunk); | ||
688 | } break; | ||
689 | case COMP_REG: { | ||
690 | emit_op(OP_MOV64, reg_dst, else_expr.idx, 0, | ||
691 | node->ifelse.expr_else, chunk); | ||
692 | } break; | ||
693 | case COMP_RET: break; | ||
501 | default: { | 694 | default: { |
695 | emit_compile_err(compiler, chunk, node); | ||
502 | return (CompResult){.type = COMP_ERR}; | 696 | return (CompResult){.type = COMP_ERR}; |
503 | } break; | 697 | } break; |
504 | } | 698 | } |
699 | sz pos1 = array_size(chunk->code); | ||
700 | |||
701 | // Update labels. | ||
702 | intintmap_insert(&chunk->labels, lab0, pos0 + 1, chunk->storage); | ||
703 | intintmap_insert(&chunk->labels, lab1, pos1, chunk->storage); | ||
704 | return (CompResult){.type = COMP_REG, .idx = reg_dst}; | ||
505 | } | 705 | } |
506 | 706 | ||
707 | // Jump to the `false` branch. | ||
708 | sz lab0 = chunk->labels_idx++; | ||
709 | emit_op(jmpop, lab0, cond.idx, 0, node->ifelse.cond, chunk); | ||
710 | |||
711 | // Condition is true. | ||
712 | compile_expr(compiler, chunk, node->ifelse.expr_true); | ||
713 | |||
507 | // Jump to the end of the expression. | 714 | // Jump to the end of the expression. |
508 | sz jump_b = array_size(chunk->code); | 715 | sz pos0 = array_size(chunk->code); |
509 | EMIT_OP(OP_JMPI, 0xff, 0, 0, node->cond_if, chunk); | 716 | sz lab1 = chunk->labels_idx++; |
717 | emit_op(OP_JMP, lab1, 0, 0, node->ifelse.expr_else, chunk); | ||
510 | 718 | ||
511 | // Else expression. | 719 | // Else expression. |
512 | CompResult else_expr = compile_expr(chunk, node->cond_else); | 720 | if (node->ifelse.expr_else) { |
513 | if (has_value) { | 721 | compile_expr(compiler, chunk, node->ifelse.expr_else); |
514 | switch (else_expr.type) { | 722 | } |
723 | sz pos1 = array_size(chunk->code); | ||
724 | |||
725 | // Update labels. | ||
726 | intintmap_insert(&chunk->labels, lab0, pos0 + 1, chunk->storage); | ||
727 | intintmap_insert(&chunk->labels, lab1, pos1, chunk->storage); | ||
728 | |||
729 | return (CompResult){.type = COMP_NIL}; | ||
730 | } | ||
731 | |||
732 | CompResult | ||
733 | compile_cond(Compiler *compiler, Chunk *chunk, Node *node) { | ||
734 | if (str_eq(node->type, cstr("nil"))) { | ||
735 | sz lab1 = chunk->labels_idx++; | ||
736 | for (sz i = 0; i < array_size(node->match.cases); i++) { | ||
737 | // condition = expression | ||
738 | Node *expr = node->match.cases[i]; | ||
739 | if (expr->case_entry.cond) { | ||
740 | CompResult cond = | ||
741 | compile_expr(compiler, chunk, expr->case_entry.cond); | ||
742 | OpCode jmpop; | ||
743 | switch (cond.type) { | ||
744 | case COMP_CONST: { | ||
745 | jmpop = OP_JMPFI; | ||
746 | } break; | ||
747 | case COMP_REG: { | ||
748 | jmpop = OP_JMPF; | ||
749 | } break; | ||
750 | default: { | ||
751 | emit_compile_err(compiler, chunk, node); | ||
752 | return (CompResult){.type = COMP_ERR}; | ||
753 | } break; | ||
754 | } | ||
755 | // Jump to the `next` branch. | ||
756 | sz lab0 = chunk->labels_idx++; | ||
757 | emit_op(jmpop, lab0, cond.idx, 0, expr->case_entry.expr, chunk); | ||
758 | |||
759 | // Condition is true. | ||
760 | compile_expr(compiler, chunk, expr->case_entry.expr); | ||
761 | if (i != array_size(node->match.cases) - 1) { | ||
762 | // Jump to the end of the expression. | ||
763 | sz pos0 = array_size(chunk->code); | ||
764 | emit_op(OP_JMP, lab1, 0, 0, node->ifelse.expr_else, chunk); | ||
765 | intintmap_insert(&chunk->labels, lab0, pos0 + 1, | ||
766 | chunk->storage); | ||
767 | } | ||
768 | } else { | ||
769 | compile_expr(compiler, chunk, expr->case_entry.expr); | ||
770 | break; | ||
771 | } | ||
772 | } | ||
773 | sz pos1 = array_size(chunk->code); | ||
774 | intintmap_insert(&chunk->labels, lab1, pos1, chunk->storage); | ||
775 | return (CompResult){.type = COMP_NIL}; | ||
776 | } | ||
777 | |||
778 | sz reg_dst = chunk->reg_idx++; | ||
779 | sz lab1 = chunk->labels_idx++; | ||
780 | for (sz i = 0; i < array_size(node->match.cases); i++) { | ||
781 | // condition = expression | ||
782 | Node *expr = node->match.cases[i]; | ||
783 | if (expr->case_entry.cond) { | ||
784 | CompResult cond = | ||
785 | compile_expr(compiler, chunk, expr->case_entry.cond); | ||
786 | OpCode jmpop; | ||
787 | switch (cond.type) { | ||
788 | case COMP_CONST: { | ||
789 | jmpop = OP_JMPFI; | ||
790 | } break; | ||
791 | case COMP_REG: { | ||
792 | jmpop = OP_JMPF; | ||
793 | } break; | ||
794 | default: { | ||
795 | emit_compile_err(compiler, chunk, node); | ||
796 | return (CompResult){.type = COMP_ERR}; | ||
797 | } break; | ||
798 | } | ||
799 | // Jump to the `next` branch. | ||
800 | sz lab0 = chunk->labels_idx++; | ||
801 | emit_op(jmpop, lab0, cond.idx, 0, expr->case_entry.expr, chunk); | ||
802 | |||
803 | // Condition is true. | ||
804 | CompResult then_expr = | ||
805 | compile_expr(compiler, chunk, expr->case_entry.expr); | ||
806 | switch (then_expr.type) { | ||
807 | case COMP_CONST: { | ||
808 | emit_op(OP_LD64K, reg_dst, then_expr.idx, 0, | ||
809 | expr->case_entry.expr, chunk); | ||
810 | } break; | ||
811 | case COMP_REG: { | ||
812 | emit_op(OP_MOV64, reg_dst, then_expr.idx, 0, | ||
813 | expr->case_entry.expr, chunk); | ||
814 | } break; | ||
815 | case COMP_RET: break; | ||
816 | default: { | ||
817 | emit_compile_err(compiler, chunk, node); | ||
818 | return (CompResult){.type = COMP_ERR}; | ||
819 | } break; | ||
820 | } | ||
821 | if (i != array_size(node->match.cases) - 1) { | ||
822 | // Jump to the end of the expression. | ||
823 | sz pos0 = array_size(chunk->code); | ||
824 | emit_op(OP_JMP, lab1, 0, 0, node->ifelse.expr_else, chunk); | ||
825 | intintmap_insert(&chunk->labels, lab0, pos0 + 1, | ||
826 | chunk->storage); | ||
827 | } | ||
828 | } else { | ||
829 | CompResult then_expr = | ||
830 | compile_expr(compiler, chunk, expr->case_entry.expr); | ||
831 | switch (then_expr.type) { | ||
832 | case COMP_CONST: { | ||
833 | emit_op(OP_LD64K, reg_dst, then_expr.idx, 0, | ||
834 | expr->case_entry.expr, chunk); | ||
835 | } break; | ||
836 | case COMP_REG: { | ||
837 | emit_op(OP_MOV64, reg_dst, then_expr.idx, 0, | ||
838 | expr->case_entry.expr, chunk); | ||
839 | } break; | ||
840 | case COMP_RET: break; | ||
841 | default: { | ||
842 | emit_compile_err(compiler, chunk, node); | ||
843 | return (CompResult){.type = COMP_ERR}; | ||
844 | } break; | ||
845 | } | ||
846 | break; | ||
847 | } | ||
848 | } | ||
849 | sz pos1 = array_size(chunk->code); | ||
850 | intintmap_insert(&chunk->labels, lab1, pos1, chunk->storage); | ||
851 | return (CompResult){.type = COMP_REG, .idx = reg_dst}; | ||
852 | } | ||
853 | |||
854 | CompResult | ||
855 | compile_break(Compiler *compiler, Chunk *chunk, Node *node) { | ||
856 | emit_op(OP_JMP, compiler->lab_post, 0, 0, node, chunk); | ||
857 | return (CompResult){.type = COMP_NIL}; | ||
858 | } | ||
859 | |||
860 | CompResult | ||
861 | compile_continue(Compiler *compiler, Chunk *chunk, Node *node) { | ||
862 | emit_op(OP_JMP, compiler->lab_pre, 0, 0, node, chunk); | ||
863 | return (CompResult){.type = COMP_NIL}; | ||
864 | } | ||
865 | |||
866 | CompResult | ||
867 | compile_while(Compiler *compiler, Chunk *chunk, Node *node) { | ||
868 | sz lab0 = chunk->labels_idx++; | ||
869 | sz lab1 = chunk->labels_idx++; | ||
870 | sz pos1 = array_size(chunk->code); | ||
871 | CompResult cond = compile_expr(compiler, chunk, node->loop.cond); | ||
872 | OpCode jmpop; | ||
873 | switch (cond.type) { | ||
874 | case COMP_CONST: { | ||
875 | jmpop = OP_JMPFI; | ||
876 | } break; | ||
877 | case COMP_REG: { | ||
878 | jmpop = OP_JMPF; | ||
879 | } break; | ||
880 | default: { | ||
881 | emit_compile_err(compiler, chunk, node); | ||
882 | return (CompResult){.type = COMP_ERR}; | ||
883 | } break; | ||
884 | } | ||
885 | |||
886 | // Jump to the `end of the loop` branch. | ||
887 | emit_op(jmpop, lab0, cond.idx, 0, node->loop.cond, chunk); | ||
888 | |||
889 | // Condition is true. | ||
890 | compiler->lab_pre = lab1; | ||
891 | compiler->lab_post = lab0; | ||
892 | compile_expr(compiler, chunk, node->loop.expr); | ||
893 | sz pos0 = array_size(chunk->code); | ||
894 | emit_op(OP_JMP, lab1, 0, 0, node, chunk); | ||
895 | |||
896 | // Update labels. | ||
897 | intintmap_insert(&chunk->labels, lab0, pos0 + 1, chunk->storage); | ||
898 | intintmap_insert(&chunk->labels, lab1, pos1, chunk->storage); | ||
899 | |||
900 | // Return. | ||
901 | return (CompResult){.type = COMP_NIL}; | ||
902 | } | ||
903 | |||
904 | CompResult | ||
905 | compile_tail_call(Compiler *compiler, Chunk *chunk, Node *node) { | ||
906 | // Update the local parameters. | ||
907 | for (sz i = 0; i < array_size(node->elements); i++) { | ||
908 | Node *expr = node->elements[i]; | ||
909 | CompResult result = compile_expr(compiler, chunk, expr); | ||
910 | switch (result.type) { | ||
515 | case COMP_CONST: { | 911 | case COMP_CONST: { |
516 | EMIT_OP(OP_LD64K, reg_dst, else_expr.idx, 0, node->cond_if, | 912 | emit_op(OP_STLVARI, i, result.idx, 0, node, chunk); |
517 | chunk); | 913 | } break; |
914 | case COMP_REG: { | ||
915 | if (str_eq(expr->type, cstr("str"))) { | ||
916 | sz var_addr = chunk->reg_idx++; | ||
917 | sz str_addr = result.idx; | ||
918 | emit_op(OP_LDLADDR, var_addr, i, 0, node, chunk); | ||
919 | emit_fat_copy(chunk, node, var_addr, str_addr); | ||
920 | } else { | ||
921 | emit_op(OP_STLVAR, i, result.idx, 0, node, chunk); | ||
922 | } | ||
923 | } break; | ||
924 | case COMP_STRING: { | ||
925 | sz var_addr = chunk->reg_idx++; | ||
926 | sz str_addr = chunk->reg_idx++; | ||
927 | emit_op(OP_LDLADDR, var_addr, i, 0, node, chunk); | ||
928 | emit_op(OP_LDSTR, str_addr, result.idx, 0, node, chunk); | ||
929 | emit_fat_copy(chunk, node, var_addr, str_addr); | ||
930 | } break; | ||
931 | default: { | ||
932 | emit_compile_err(compiler, chunk, node); | ||
933 | return (CompResult){.type = COMP_ERR}; | ||
934 | } break; | ||
935 | } | ||
936 | } | ||
937 | |||
938 | emit_op(OP_RECUR, 0, 0, 0, node, chunk); | ||
939 | return (CompResult){.type = COMP_NIL}; | ||
940 | } | ||
941 | |||
942 | CompResult | ||
943 | compile_funcall(Compiler *compiler, Chunk *chunk, Node *node) { | ||
944 | Str name = node->value.str; | ||
945 | |||
946 | // Builtins. | ||
947 | if (str_eq(name, cstr("print")) || str_eq(name, cstr("println"))) { | ||
948 | for (sz i = 0; i < array_size(node->elements); i++) { | ||
949 | Node *expr = node->elements[i]; | ||
950 | CompResult result = compile_expr(compiler, chunk, expr); | ||
951 | if (str_eq(expr->type, cstr("int"))) { | ||
952 | switch (result.type) { | ||
953 | case COMP_CONST: { | ||
954 | emit_op(OP_PRINTS64I, result.idx, 0, 0, expr, chunk); | ||
955 | } break; | ||
956 | case COMP_REG: { | ||
957 | emit_op(OP_PRINTS64, result.idx, 0, 0, expr, chunk); | ||
958 | } break; | ||
959 | default: { | ||
960 | emit_compile_err(compiler, chunk, node); | ||
961 | return (CompResult){.type = COMP_ERR}; | ||
962 | } break; | ||
963 | } | ||
964 | } else if (str_eq(expr->type, cstr("f64"))) { | ||
965 | switch (result.type) { | ||
966 | case COMP_CONST: { | ||
967 | emit_op(OP_PRINTF64I, result.idx, 0, 0, expr, chunk); | ||
968 | } break; | ||
969 | case COMP_REG: { | ||
970 | emit_op(OP_PRINTF64, result.idx, 0, 0, expr, chunk); | ||
971 | } break; | ||
972 | default: { | ||
973 | emit_compile_err(compiler, chunk, node); | ||
974 | return (CompResult){.type = COMP_ERR}; | ||
975 | } break; | ||
976 | } | ||
977 | } else if (str_eq(expr->type, cstr("str"))) { | ||
978 | switch (result.type) { | ||
979 | case COMP_STRING: { | ||
980 | emit_op(OP_PRINTSTRI, result.idx, 0, 0, expr, chunk); | ||
981 | } break; | ||
982 | case COMP_REG: { | ||
983 | emit_op(OP_PRINTSTR, result.idx, 0, 0, expr, chunk); | ||
984 | } break; | ||
985 | default: { | ||
986 | emit_compile_err(compiler, chunk, node); | ||
987 | return (CompResult){.type = COMP_ERR}; | ||
988 | } break; | ||
989 | } | ||
990 | } else if (str_eq(expr->type, cstr("bool"))) { | ||
991 | switch (result.type) { | ||
992 | case COMP_CONST: { | ||
993 | emit_op(OP_PRINTBOOLI, result.idx, 0, 0, expr, chunk); | ||
994 | } break; | ||
995 | case COMP_REG: { | ||
996 | emit_op(OP_PRINTBOOL, result.idx, 0, 0, expr, chunk); | ||
997 | } break; | ||
998 | default: { | ||
999 | emit_compile_err(compiler, chunk, node); | ||
1000 | return (CompResult){.type = COMP_ERR}; | ||
1001 | } break; | ||
1002 | } | ||
1003 | } | ||
1004 | } | ||
1005 | if (str_eq(name, cstr("println"))) { | ||
1006 | sz idx = add_string(chunk, cstr("\n")); | ||
1007 | emit_op(OP_PRINTSTRI, idx, 0, 0, node, chunk); | ||
1008 | } | ||
1009 | return (CompResult){.type = COMP_NIL}; | ||
1010 | } | ||
1011 | |||
1012 | FunctionMap *map = | ||
1013 | funcmap_lookup(&compiler->main_chunk.funmap, node->unique_name); | ||
1014 | if (!map) { | ||
1015 | emit_compile_err(compiler, chunk, node); | ||
1016 | return (CompResult){.type = COMP_ERR}; | ||
1017 | } | ||
1018 | Function fun = map->val; | ||
1019 | |||
1020 | // Check for tail recursive opportunities. | ||
1021 | if (str_eq(fun.name, node->unique_name) && | ||
1022 | str_eq(chunk->name, node->unique_name)) { | ||
1023 | Node *parent = node->parent; | ||
1024 | Node *current = node; | ||
1025 | bool tail_recursive = true; | ||
1026 | while (parent != NULL) { | ||
1027 | switch (parent->kind) { | ||
1028 | case NODE_BLOCK: { | ||
1029 | sz idx = array_size(parent->statements) - 1; | ||
1030 | if (parent->statements[idx] != node) { | ||
1031 | tail_recursive = false; | ||
1032 | break; | ||
1033 | } | ||
1034 | } break; | ||
1035 | case NODE_WHILE: { | ||
1036 | if (current == parent->loop.cond) { | ||
1037 | tail_recursive = false; | ||
1038 | break; | ||
1039 | } | ||
1040 | } break; | ||
1041 | case NODE_IF: { | ||
1042 | if (current == parent->ifelse.cond) { | ||
1043 | tail_recursive = false; | ||
1044 | break; | ||
1045 | } | ||
1046 | } break; | ||
1047 | case NODE_FUN: { | ||
1048 | sz idx = array_size(parent->func.body->statements) - 1; | ||
1049 | if (parent->func.body->statements[idx] != current) { | ||
1050 | tail_recursive = false; | ||
1051 | break; | ||
1052 | } | ||
1053 | break; | ||
1054 | } break; | ||
1055 | case NODE_MATCH: { | ||
1056 | if (current == parent->match.expr) { | ||
1057 | tail_recursive = false; | ||
1058 | break; | ||
1059 | } | ||
1060 | } break; | ||
1061 | case NODE_COND: break; | ||
1062 | case NODE_CASE_COND: { | ||
1063 | if (current == parent->case_entry.cond) { | ||
1064 | tail_recursive = false; | ||
1065 | break; | ||
1066 | } | ||
1067 | } break; | ||
1068 | default: { | ||
1069 | tail_recursive = false; | ||
1070 | break; | ||
1071 | } break; | ||
1072 | } | ||
1073 | parent = parent->parent; | ||
1074 | current = current->parent; | ||
1075 | } | ||
1076 | if (tail_recursive) { | ||
1077 | return compile_tail_call(compiler, chunk, node); | ||
1078 | } | ||
1079 | } | ||
1080 | |||
1081 | // Reserve space for the return value if needed. | ||
1082 | if (fun.return_arity > 0) { | ||
1083 | // Put the return data into a register | ||
1084 | sz ret_size = add_constant(chunk, 8); | ||
1085 | emit_op(OP_RESERVE, ret_size, 0, 0, node, chunk); | ||
1086 | } | ||
1087 | |||
1088 | // Send parameters to the stack. | ||
1089 | for (sz i = 0; i < array_size(node->elements); i++) { | ||
1090 | Node *expr = node->elements[i]; | ||
1091 | CompResult result = compile_expr(compiler, chunk, expr); | ||
1092 | switch (result.type) { | ||
1093 | case COMP_CONST: { | ||
1094 | emit_op(OP_PUSHI, result.idx, 0, 0, expr, chunk); | ||
518 | } break; | 1095 | } break; |
519 | case COMP_REG: { | 1096 | case COMP_REG: { |
520 | EMIT_OP(OP_MOV64, reg_dst, else_expr.idx, 0, node->cond_if, | 1097 | if (str_eq(expr->type, cstr("str"))) { |
1098 | sz str_addr = result.idx; | ||
1099 | // Store the fat string pointer into the stack. | ||
1100 | sz reg_dst = chunk->reg_idx++; | ||
1101 | sz zero = add_constant(chunk, 0); | ||
1102 | sz one = add_constant(chunk, 1); | ||
1103 | emit_op(OP_LD64I, reg_dst, str_addr, zero, node, chunk); | ||
1104 | emit_op(OP_PUSH, reg_dst, 0, 0, expr, chunk); | ||
1105 | emit_op(OP_LD64I, reg_dst, str_addr, one, node, chunk); | ||
1106 | emit_op(OP_PUSH, reg_dst, 0, 0, expr, chunk); | ||
1107 | } else { | ||
1108 | emit_op(OP_PUSH, result.idx, 0, 0, expr, chunk); | ||
1109 | } | ||
1110 | } break; | ||
1111 | case COMP_STRING: { | ||
1112 | // Get the address for the string value variable. | ||
1113 | sz str_addr = chunk->reg_idx++; | ||
1114 | emit_op(OP_LDSTR, str_addr, result.idx, 0, node->var.val, | ||
521 | chunk); | 1115 | chunk); |
1116 | |||
1117 | // Store the fat string pointer into the stack. | ||
1118 | sz reg_dst = chunk->reg_idx++; | ||
1119 | sz zero = add_constant(chunk, 0); | ||
1120 | sz one = add_constant(chunk, 1); | ||
1121 | emit_op(OP_LD64I, reg_dst, str_addr, zero, node, chunk); | ||
1122 | emit_op(OP_PUSH, reg_dst, 0, 0, expr, chunk); | ||
1123 | emit_op(OP_LD64I, reg_dst, str_addr, one, node, chunk); | ||
1124 | emit_op(OP_PUSH, reg_dst, 0, 0, expr, chunk); | ||
522 | } break; | 1125 | } break; |
523 | default: { | 1126 | default: { |
1127 | emit_compile_err(compiler, chunk, node); | ||
524 | return (CompResult){.type = COMP_ERR}; | 1128 | return (CompResult){.type = COMP_ERR}; |
525 | } break; | 1129 | } break; |
526 | } | 1130 | } |
527 | } | 1131 | } |
528 | sz end_expr = array_size(chunk->code); | ||
529 | 1132 | ||
530 | // Backpatch jumps. | 1133 | emit_op(OP_CALL, fun.index, 0, 0, node, chunk); |
531 | sz const_a = add_constant(chunk, jump_b + 1 - jump_a); | ||
532 | sz const_b = add_constant(chunk, end_expr - jump_b); | ||
533 | chunk->code[jump_a].a = const_a; | ||
534 | chunk->code[jump_b].dst = const_b; | ||
535 | // TODO: does it has an else or not? Moreover, should we enforce on the | ||
536 | // semantic level that if the `if` expression returns a value we must add an | ||
537 | // else? | ||
538 | 1134 | ||
539 | // Return. | 1135 | // Only one return parameter for now. |
540 | if (has_value) { | 1136 | if (fun.return_arity > 0) { |
1137 | // Put the return data into a register | ||
1138 | sz reg_dst = chunk->reg_idx++; | ||
1139 | emit_op(OP_POP, reg_dst, 0, 0, node, chunk); | ||
541 | return (CompResult){.type = COMP_REG, .idx = reg_dst}; | 1140 | return (CompResult){.type = COMP_REG, .idx = reg_dst}; |
542 | } | 1141 | } |
1142 | |||
1143 | return (CompResult){.type = COMP_NIL}; | ||
1144 | } | ||
1145 | |||
1146 | CompResult | ||
1147 | compile_return(Compiler *compiler, Chunk *chunk, Node *node) { | ||
1148 | for (sz i = 0; i < array_size(node->elements); i++) { | ||
1149 | Node *expr = node->elements[i]; | ||
1150 | CompResult res = compile_expr(compiler, chunk, expr); | ||
1151 | |||
1152 | // TODO: Only one return field for now, but this is the idea. | ||
1153 | // Put return values into memory. | ||
1154 | switch (res.type) { | ||
1155 | case COMP_CONST: { | ||
1156 | emit_op(OP_PUTRETI, res.idx, 0, 0, node, chunk); | ||
1157 | } break; | ||
1158 | case COMP_REG: { | ||
1159 | emit_op(OP_PUTRET, res.idx, 0, 0, node, chunk); | ||
1160 | } break; | ||
1161 | case COMP_NIL: break; | ||
1162 | default: { | ||
1163 | emit_compile_err(compiler, chunk, node); | ||
1164 | return (CompResult){.type = COMP_ERR}; | ||
1165 | } break; | ||
1166 | } | ||
1167 | break; | ||
1168 | } | ||
1169 | |||
1170 | emit_op(OP_RET, 0, 0, 0, node, chunk); | ||
1171 | return (CompResult){.type = COMP_RET}; | ||
1172 | } | ||
1173 | |||
1174 | Chunk * | ||
1175 | chunk_alloc(Chunk *parent) { | ||
1176 | static sz chunk_idx = 1; | ||
1177 | Chunk *chunk = arena_calloc((sz)sizeof(Chunk), parent->storage); | ||
1178 | chunk->parent = parent; | ||
1179 | chunk->id = chunk_idx++; | ||
1180 | chunk->storage = parent->storage; | ||
1181 | chunk->file_name = parent->file_name; | ||
1182 | return chunk; | ||
1183 | } | ||
1184 | |||
1185 | void | ||
1186 | verify_chunk(Chunk *chunk) { | ||
1187 | if (chunk->const_idx >= 256) { | ||
1188 | eprintln("too many constants on chunk %s", chunk->id); | ||
1189 | exit(EXIT_FAILURE); | ||
1190 | } | ||
1191 | if (chunk->str_idx >= 256) { | ||
1192 | eprintln("too many strings on chunk %s", chunk->id); | ||
1193 | exit(EXIT_FAILURE); | ||
1194 | } | ||
1195 | if (chunk->reg_idx >= 256) { | ||
1196 | eprintln("too many registers used on chunk %s", chunk->id); | ||
1197 | exit(EXIT_FAILURE); | ||
1198 | } | ||
1199 | if (chunk->labels_idx >= 256) { | ||
1200 | eprintln("too many labels used on chunk %s", chunk->id); | ||
1201 | exit(EXIT_FAILURE); | ||
1202 | } | ||
1203 | if (chunk->fun_idx >= 256) { | ||
1204 | eprintln("too many functions on chunk %s", chunk->id); | ||
1205 | exit(EXIT_FAILURE); | ||
1206 | } | ||
1207 | } | ||
1208 | |||
1209 | void | ||
1210 | declare_function(Chunk *chunk, Node *node) { | ||
1211 | Str name = node->unique_name; | ||
1212 | FunctionMap *map = funcmap_lookup(&chunk->funmap, node->unique_name); | ||
1213 | if (map) { | ||
1214 | return; | ||
1215 | } | ||
1216 | Function fun = (Function){ | ||
1217 | .name = name, | ||
1218 | .index = chunk->fun_idx++, | ||
1219 | .param_arity = array_size(node->func.params), | ||
1220 | .return_arity = array_size(node->func.ret), | ||
1221 | }; | ||
1222 | funcmap_insert(&chunk->funmap, node->unique_name, fun, chunk->storage); | ||
1223 | } | ||
1224 | |||
1225 | CompResult | ||
1226 | compile_function(Compiler *compiler, Chunk *chunk, Node *node) { | ||
1227 | // The current activation record procedure for the VM is as follows: | ||
1228 | // | ||
1229 | // [caller][callee ] | ||
1230 | // [ .... ][ RET VAL ][ PARAMS ][ LOCALS ][ REGISTERS ][ RET META ] | ||
1231 | // ^ | ||
1232 | // frame pointer | ||
1233 | // | ||
1234 | // The caller is responsible for allocating the return memory and the | ||
1235 | // parameter memory and filling the param data before OP_CALL. | ||
1236 | // | ||
1237 | chunk = &compiler->main_chunk; | ||
1238 | Chunk *func = chunk_alloc(chunk); | ||
1239 | func->name = node->unique_name; | ||
1240 | declare_function(chunk, node); | ||
1241 | array_push(chunk->functions, func, chunk->storage); | ||
1242 | |||
1243 | // Push arguments as locals. | ||
1244 | for (sz i = 0; i < array_size(node->func.params); i++) { | ||
1245 | Node *param = node->func.params[i]; | ||
1246 | Str name = param->unique_name; | ||
1247 | Str type = param->type; | ||
1248 | sz arr_size = 0; | ||
1249 | if (str_has_prefix(type, cstr("@"))) { | ||
1250 | if (param->var.type && param->var.type->kind == NODE_ARR_TYPE && | ||
1251 | param->var.type->sym.arr_size->value.i > 0) { | ||
1252 | arr_size = param->var.type->sym.arr_size->value.i; | ||
1253 | } | ||
1254 | } | ||
1255 | add_variable(func, name, type, arr_size); | ||
1256 | } | ||
1257 | func->param_off = func->var_off; | ||
1258 | |||
1259 | // Compiling the body. | ||
1260 | CompResult res = compile_expr(compiler, func, node->func.body); | ||
1261 | |||
1262 | // Put return values into memory. | ||
1263 | switch (res.type) { | ||
1264 | case COMP_CONST: { | ||
1265 | emit_op(OP_PUTRETI, res.idx, 0, 0, node, func); | ||
1266 | } break; | ||
1267 | case COMP_REG: { | ||
1268 | emit_op(OP_PUTRET, res.idx, 0, 0, node, func); | ||
1269 | } break; | ||
1270 | default: break; | ||
1271 | } | ||
1272 | |||
1273 | emit_op(OP_RET, 0, 0, 0, node, func); | ||
1274 | verify_chunk(func); | ||
1275 | return (CompResult){.type = COMP_NIL}; | ||
1276 | } | ||
1277 | |||
1278 | CompResult | ||
1279 | compile_let(Compiler *compiler, Chunk *chunk, Node *node) { | ||
1280 | sz op_ldaddr = OP_LDLADDR; | ||
1281 | sz op_stvari = OP_STLVARI; | ||
1282 | sz op_stvar = OP_STLVAR; | ||
1283 | if (chunk == &compiler->main_chunk) { | ||
1284 | op_ldaddr = OP_LDGADDR; | ||
1285 | op_stvari = OP_STGVARI; | ||
1286 | op_stvar = OP_STGVAR; | ||
1287 | } | ||
1288 | Str name = node->unique_name; | ||
1289 | Str type = node->var.name->type; | ||
1290 | sz arr_size = 0; | ||
1291 | if (str_has_prefix(type, cstr("@"))) { | ||
1292 | if (node->var.type && node->var.type->kind == NODE_ARR_TYPE && | ||
1293 | node->var.type->sym.arr_size->value.i > 0) { | ||
1294 | arr_size = node->var.type->sym.arr_size->value.i; | ||
1295 | } | ||
1296 | } | ||
1297 | |||
1298 | sz idx = add_variable(chunk, name, type, arr_size); | ||
1299 | |||
1300 | // Value. | ||
1301 | if (node->var.val) { | ||
1302 | CompResult res = compile_expr(compiler, chunk, node->var.val); | ||
1303 | switch (res.type) { | ||
1304 | case COMP_CONST: { | ||
1305 | emit_op(op_stvari, idx, res.idx, 0, node->var.val, chunk); | ||
1306 | } break; | ||
1307 | case COMP_REG: { | ||
1308 | if (str_eq(node->var.val->type, cstr("str"))) { | ||
1309 | // Get the address for the local/global storage | ||
1310 | // variable. | ||
1311 | sz var_addr = chunk->reg_idx++; | ||
1312 | emit_op(op_ldaddr, var_addr, idx, 0, node, chunk); | ||
1313 | |||
1314 | // Copy the fat pointer. | ||
1315 | emit_fat_copy(chunk, node, var_addr, res.idx); | ||
1316 | } else { | ||
1317 | emit_op(op_stvar, idx, res.idx, 0, node->var.val, chunk); | ||
1318 | } | ||
1319 | } break; | ||
1320 | case COMP_STRING: { | ||
1321 | // Get the address for the string value variable. | ||
1322 | sz str_addr = chunk->reg_idx++; | ||
1323 | emit_op(OP_LDSTR, str_addr, res.idx, 0, node->var.val, chunk); | ||
1324 | |||
1325 | // Get the address for the local/global storage | ||
1326 | // variable. | ||
1327 | sz var_addr = chunk->reg_idx++; | ||
1328 | emit_op(op_ldaddr, var_addr, idx, 0, node, chunk); | ||
1329 | |||
1330 | // Copy the fat pointer. | ||
1331 | emit_fat_copy(chunk, node, var_addr, str_addr); | ||
1332 | } break; | ||
1333 | default: { | ||
1334 | emit_compile_err(compiler, chunk, node); | ||
1335 | return (CompResult){.type = COMP_ERR}; | ||
1336 | } break; | ||
1337 | } | ||
1338 | } | ||
1339 | |||
1340 | return (CompResult){.type = COMP_NIL}; | ||
1341 | } | ||
1342 | |||
1343 | CompResult | ||
1344 | compile_set(Compiler *compiler, Chunk *chunk, Node *node) { | ||
1345 | Str name = node->unique_name; | ||
1346 | StrVarMap *map = NULL; | ||
1347 | Chunk *next = chunk; | ||
1348 | while (next) { | ||
1349 | map = varmap_lookup(&next->varmap, name); | ||
1350 | if (map) { | ||
1351 | break; | ||
1352 | } | ||
1353 | next = chunk->parent; | ||
1354 | } | ||
1355 | if (!map) { | ||
1356 | emit_compile_err(compiler, chunk, node); | ||
1357 | return (CompResult){.type = COMP_ERR}; | ||
1358 | } | ||
1359 | sz op_ldaddr = OP_LDLADDR; | ||
1360 | sz op_ldvar = OP_LDLVAR; | ||
1361 | sz op_stvari = OP_STLVARI; | ||
1362 | sz op_stvar = OP_STLVAR; | ||
1363 | if (next == &compiler->main_chunk) { | ||
1364 | op_ldaddr = OP_LDGADDR; | ||
1365 | op_ldvar = OP_LDGVAR; | ||
1366 | op_stvari = OP_STGVARI; | ||
1367 | op_stvar = OP_STGVAR; | ||
1368 | } | ||
1369 | CompResult res = compile_expr(compiler, chunk, node->var.val); | ||
1370 | if (node->var.name->kind == NODE_SYMBOL_IDX) { | ||
1371 | // Value. | ||
1372 | sz reg_val; | ||
1373 | switch (res.type) { | ||
1374 | case COMP_CONST: { | ||
1375 | reg_val = chunk->reg_idx++; | ||
1376 | emit_op(OP_LD64K, reg_val, res.idx, 0, node, chunk); | ||
1377 | } break; | ||
1378 | case COMP_REG: { | ||
1379 | reg_val = res.idx; | ||
1380 | } break; | ||
1381 | default: { | ||
1382 | emit_compile_err(compiler, chunk, node); | ||
1383 | return (CompResult){.type = COMP_ERR}; | ||
1384 | } break; | ||
1385 | } | ||
1386 | |||
1387 | // Address. | ||
1388 | sz reg_addr = chunk->reg_idx++; | ||
1389 | // Is this a pointer access or an array access? | ||
1390 | if (str_has_prefix(map->val.type, cstr("[]"))) { | ||
1391 | emit_op(op_ldaddr, reg_addr, map->val.idx, 0, node->var.val, chunk); | ||
1392 | } else { | ||
1393 | emit_op(op_ldvar, reg_addr, map->val.idx, 0, node->var.val, chunk); | ||
1394 | } | ||
1395 | |||
1396 | // Index. | ||
1397 | CompResult idx = | ||
1398 | compile_expr(compiler, chunk, node->var.name->sym.arr_size); | ||
1399 | switch (idx.type) { | ||
1400 | case COMP_CONST: { | ||
1401 | emit_op(OP_ST64I, reg_val, reg_addr, idx.idx, node, chunk); | ||
1402 | } break; | ||
1403 | case COMP_REG: { | ||
1404 | emit_op(OP_ST64, reg_val, reg_addr, idx.idx, node, chunk); | ||
1405 | } break; | ||
1406 | default: { | ||
1407 | emit_compile_err(compiler, chunk, node); | ||
1408 | return (CompResult){.type = COMP_ERR}; | ||
1409 | } break; | ||
1410 | } | ||
1411 | // TODO: offset should be in bytes, in this case we are assuming | ||
1412 | // 64bit types, hence ST64 | ||
1413 | return (CompResult){.type = COMP_NIL}; | ||
1414 | } | ||
1415 | switch (res.type) { | ||
1416 | case COMP_CONST: { | ||
1417 | emit_op(op_stvari, map->val.idx, res.idx, 0, node->var.val, chunk); | ||
1418 | } break; | ||
1419 | case COMP_REG: { | ||
1420 | if (str_eq(node->var.val->type, cstr("str"))) { | ||
1421 | // Get the address for the local/global storage | ||
1422 | // variable. | ||
1423 | sz var_addr = chunk->reg_idx++; | ||
1424 | emit_op(op_ldaddr, var_addr, map->val.idx, 0, node, chunk); | ||
1425 | |||
1426 | // Copy the fat pointer. | ||
1427 | emit_fat_copy(chunk, node, var_addr, res.idx); | ||
1428 | } else { | ||
1429 | emit_op(op_stvar, map->val.idx, res.idx, 0, node->var.val, | ||
1430 | chunk); | ||
1431 | } | ||
1432 | } break; | ||
1433 | default: { | ||
1434 | emit_compile_err(compiler, chunk, node); | ||
1435 | return (CompResult){.type = COMP_ERR}; | ||
1436 | } break; | ||
1437 | } | ||
543 | return (CompResult){.type = COMP_NIL}; | 1438 | return (CompResult){.type = COMP_NIL}; |
544 | } | 1439 | } |
545 | 1440 | ||
546 | CompResult | 1441 | CompResult |
547 | compile_expr(Chunk *chunk, Node *node) { | 1442 | compile_symbol(Compiler *compiler, Chunk *chunk, Node *node) { |
1443 | Str name = node->unique_name; | ||
1444 | StrVarMap *map = NULL; | ||
1445 | Chunk *next = chunk; | ||
1446 | while (next) { | ||
1447 | map = varmap_lookup(&next->varmap, name); | ||
1448 | if (map) { | ||
1449 | break; | ||
1450 | } | ||
1451 | next = next->parent; | ||
1452 | } | ||
1453 | if (!map) { | ||
1454 | emit_compile_err(compiler, chunk, node); | ||
1455 | return (CompResult){.type = COMP_ERR}; | ||
1456 | } | ||
1457 | sz op_ldaddr = OP_LDLADDR; | ||
1458 | sz op_ldvar = OP_LDLVAR; | ||
1459 | if (next == &compiler->main_chunk) { | ||
1460 | op_ldaddr = OP_LDGADDR; | ||
1461 | op_ldvar = OP_LDGVAR; | ||
1462 | } | ||
1463 | Variable var = map->val; | ||
1464 | u8 reg_dst = chunk->reg_idx++; | ||
1465 | if (node->is_ptr || str_has_prefix(var.type, cstr("[]")) || | ||
1466 | str_eq(var.type, cstr("str"))) { | ||
1467 | emit_op(op_ldaddr, reg_dst, var.idx, 0, node, chunk); | ||
1468 | } else { | ||
1469 | emit_op(op_ldvar, reg_dst, var.idx, 0, node, chunk); | ||
1470 | } | ||
1471 | return (CompResult){.type = COMP_REG, .idx = reg_dst}; | ||
1472 | } | ||
1473 | |||
1474 | CompResult | ||
1475 | compile_symbol_idx(Compiler *compiler, Chunk *chunk, Node *node) { | ||
1476 | Str name = node->unique_name; | ||
1477 | StrVarMap *map = NULL; | ||
1478 | Chunk *next = chunk; | ||
1479 | while (next) { | ||
1480 | map = varmap_lookup(&next->varmap, name); | ||
1481 | if (map) { | ||
1482 | break; | ||
1483 | } | ||
1484 | next = next->parent; | ||
1485 | } | ||
1486 | if (!map) { | ||
1487 | eprintln("couldn't resolve symbol name: %s", name); | ||
1488 | emit_compile_err(compiler, chunk, node); | ||
1489 | return (CompResult){.type = COMP_ERR}; | ||
1490 | } | ||
1491 | sz op_ldaddr = OP_LDLADDR; | ||
1492 | sz op_ldvar = OP_LDLVAR; | ||
1493 | if (next == &compiler->main_chunk) { | ||
1494 | op_ldaddr = OP_LDGADDR; | ||
1495 | op_ldvar = OP_LDGVAR; | ||
1496 | } | ||
1497 | |||
1498 | // Destination. | ||
1499 | u8 reg_dst = chunk->reg_idx++; | ||
1500 | |||
1501 | // Address. | ||
1502 | sz reg_addr = chunk->reg_idx++; | ||
1503 | if (str_has_prefix(map->val.type, cstr("[]"))) { | ||
1504 | emit_op(op_ldaddr, reg_addr, map->val.idx, 0, node->var.val, chunk); | ||
1505 | } else { | ||
1506 | emit_op(op_ldvar, reg_addr, map->val.idx, 0, node->var.val, chunk); | ||
1507 | } | ||
1508 | |||
1509 | // Index. | ||
1510 | CompResult idx = compile_expr(compiler, chunk, node->sym.arr_size); | ||
1511 | switch (idx.type) { | ||
1512 | case COMP_CONST: { | ||
1513 | emit_op(OP_LD64I, reg_dst, reg_addr, idx.idx, node, chunk); | ||
1514 | } break; | ||
1515 | case COMP_REG: { | ||
1516 | emit_op(OP_LD64, reg_dst, reg_addr, idx.idx, node, chunk); | ||
1517 | } break; | ||
1518 | default: { | ||
1519 | emit_compile_err(compiler, chunk, node); | ||
1520 | return (CompResult){.type = COMP_ERR}; | ||
1521 | } break; | ||
1522 | } | ||
1523 | // TODO: hardcoding the type size for now (LD64/LD64I). | ||
1524 | return (CompResult){.type = COMP_REG, .idx = reg_dst}; | ||
1525 | } | ||
1526 | |||
1527 | CompResult | ||
1528 | compile_expr(Compiler *compiler, Chunk *chunk, Node *node) { | ||
548 | switch (node->kind) { | 1529 | switch (node->kind) { |
549 | case NODE_IF: return compile_if(chunk, node); | 1530 | case NODE_BREAK: return compile_break(compiler, chunk, node); |
1531 | case NODE_CONTINUE: return compile_continue(compiler, chunk, node); | ||
1532 | case NODE_RETURN: return compile_return(compiler, chunk, node); | ||
1533 | case NODE_FUN: return compile_function(compiler, chunk, node); | ||
1534 | case NODE_FUNCALL: return compile_funcall(compiler, chunk, node); | ||
1535 | case NODE_WHILE: return compile_while(compiler, chunk, node); | ||
1536 | case NODE_IF: return compile_if(compiler, chunk, node); | ||
1537 | case NODE_COND: return compile_cond(compiler, chunk, node); | ||
550 | // Logic. | 1538 | // Logic. |
551 | // case NODE_XOR: | ||
552 | case NODE_BITNOT: | 1539 | case NODE_BITNOT: |
553 | case NODE_NOT: return compile_unary(chunk, node); break; | 1540 | case NODE_NOT: return compile_unary(compiler, chunk, node); break; |
554 | case NODE_AND: | 1541 | case NODE_AND: |
555 | case NODE_OR: | 1542 | case NODE_OR: |
556 | case NODE_EQ: | 1543 | case NODE_EQ: |
@@ -561,6 +1548,7 @@ compile_expr(Chunk *chunk, Node *node) { | |||
561 | // Bitwise ops. | 1548 | // Bitwise ops. |
562 | case NODE_BITAND: | 1549 | case NODE_BITAND: |
563 | case NODE_BITOR: | 1550 | case NODE_BITOR: |
1551 | case NODE_BITXOR: | ||
564 | case NODE_BITLSHIFT: | 1552 | case NODE_BITLSHIFT: |
565 | case NODE_BITRSHIFT: | 1553 | case NODE_BITRSHIFT: |
566 | // Arithmetic. | 1554 | // Arithmetic. |
@@ -569,7 +1557,7 @@ compile_expr(Chunk *chunk, Node *node) { | |||
569 | case NODE_SUB: | 1557 | case NODE_SUB: |
570 | case NODE_MUL: | 1558 | case NODE_MUL: |
571 | case NODE_DIV: | 1559 | case NODE_DIV: |
572 | case NODE_MOD: return compile_binary(chunk, node); break; | 1560 | case NODE_MOD: return compile_binary(compiler, chunk, node); break; |
573 | case NODE_TRUE: | 1561 | case NODE_TRUE: |
574 | case NODE_FALSE: | 1562 | case NODE_FALSE: |
575 | case NODE_NUM_FLOAT: | 1563 | case NODE_NUM_FLOAT: |
@@ -584,83 +1572,30 @@ compile_expr(Chunk *chunk, Node *node) { | |||
584 | } break; | 1572 | } break; |
585 | case NODE_STRING: { | 1573 | case NODE_STRING: { |
586 | Str string = node->value.str; | 1574 | Str string = node->value.str; |
587 | // Make sure we don't have duplicated strings. | 1575 | sz str_idx = add_string(chunk, string); |
588 | StrIntMap *map = strintmap_lookup(&chunk->strmap, string); | ||
589 | if (!map) { | ||
590 | map = strintmap_insert(&chunk->strmap, string, chunk->str_idx++, | ||
591 | chunk->storage); | ||
592 | array_push(chunk->strings, string, chunk->storage); | ||
593 | } | ||
594 | return (CompResult){ | 1576 | return (CompResult){ |
595 | .type = COMP_STRING, | 1577 | .type = COMP_STRING, |
596 | .idx = map->val, | 1578 | .idx = str_idx, |
597 | }; | 1579 | }; |
598 | } break; | 1580 | } break; |
599 | case NODE_LET: { | 1581 | case NODE_LET: return compile_let(compiler, chunk, node); |
600 | sz idx = array_size(chunk->vars); | 1582 | case NODE_SET: return compile_set(compiler, chunk, node); |
601 | Str name = node->unique_name; | 1583 | case NODE_SYMBOL: return compile_symbol(compiler, chunk, node); |
602 | Str type = node->var_name->type; | 1584 | case NODE_SYMBOL_IDX: return compile_symbol_idx(compiler, chunk, node); |
603 | sz size = 8; | ||
604 | // TODO: get type storage from a table to consider all the basic | ||
605 | // types as well as user defined ones. | ||
606 | if (str_eq(type, cstr("str"))) { | ||
607 | size = 16; | ||
608 | } | ||
609 | Variable var = (Variable){ | ||
610 | .name = name, | ||
611 | .type = type, | ||
612 | .size = size, | ||
613 | .offset = chunk->var_off, | ||
614 | .idx = idx, | ||
615 | }; | ||
616 | varmap_insert(&chunk->varmap, name, var, chunk->storage); | ||
617 | array_push(chunk->vars, var, chunk->storage); | ||
618 | chunk->var_off += size; | ||
619 | |||
620 | // Value. | ||
621 | if (node->var_val) { | ||
622 | CompResult res = compile_expr(chunk, node->var_val); | ||
623 | switch (res.type) { | ||
624 | case COMP_CONST: { | ||
625 | EMIT_OP(OP_STGVARI, idx, res.idx, 0, node->var_val, | ||
626 | chunk); | ||
627 | } break; | ||
628 | case COMP_REG: { | ||
629 | EMIT_OP(OP_STGVAR, idx, res.idx, 0, node->var_val, | ||
630 | chunk); | ||
631 | } break; | ||
632 | default: { | ||
633 | return (CompResult){.type = COMP_ERR}; | ||
634 | } break; | ||
635 | } | ||
636 | } | ||
637 | |||
638 | return (CompResult){.type = COMP_NIL}; | ||
639 | } break; | ||
640 | case NODE_SYMBOL: { | ||
641 | Str name = node->unique_name; | ||
642 | StrVarMap *map = varmap_lookup(&chunk->varmap, name); | ||
643 | if (!map) { | ||
644 | println("error: unreachable... name: %s", name); | ||
645 | exit(EXIT_FAILURE); | ||
646 | } | ||
647 | Variable var = map->val; | ||
648 | u8 reg_dst = chunk->reg_idx++; | ||
649 | EMIT_OP(OP_LDGVAR, reg_dst, var.idx, 0, node, chunk); | ||
650 | return (CompResult){.type = COMP_REG, .idx = reg_dst}; | ||
651 | } break; | ||
652 | case NODE_BLOCK: { | 1585 | case NODE_BLOCK: { |
653 | CompResult res; | 1586 | CompResult res; |
654 | for (sz i = 0; i < array_size(node->elements); i++) { | 1587 | for (sz i = 0; i < array_size(node->elements); i++) { |
655 | Node *root = node->elements[i]; | 1588 | Node *root = node->elements[i]; |
656 | res = compile_expr(chunk, root); | 1589 | res = compile_expr(compiler, chunk, root); |
657 | } | 1590 | } |
658 | return res; | 1591 | return res; |
659 | } break; | 1592 | } break; |
1593 | case NODE_NIL: return (CompResult){.type = COMP_NIL}; | ||
660 | default: { | 1594 | default: { |
661 | eprintln("error: compilation not implemented for node %s", | 1595 | eprintln("error: compilation not implemented for node %s", |
662 | node_str[node->kind]); | 1596 | node_str[node->kind]); |
663 | exit(EXIT_FAILURE); | 1597 | emit_compile_err(compiler, chunk, node); |
1598 | return (CompResult){.type = COMP_ERR}; | ||
664 | } break; | 1599 | } break; |
665 | } | 1600 | } |
666 | return (CompResult){.type = COMP_ERR}; | 1601 | return (CompResult){.type = COMP_ERR}; |
@@ -669,6 +1604,10 @@ compile_expr(Chunk *chunk, Node *node) { | |||
669 | void | 1604 | void |
670 | disassemble_instruction(Instruction instruction) { | 1605 | disassemble_instruction(Instruction instruction) { |
671 | switch (instruction.op) { | 1606 | switch (instruction.op) { |
1607 | case OP_CALL: | ||
1608 | println("%s f%d", op_str[instruction.op], instruction.dst, | ||
1609 | instruction.a, instruction.b); | ||
1610 | break; | ||
672 | case OP_MOV8: | 1611 | case OP_MOV8: |
673 | case OP_MOV16: | 1612 | case OP_MOV16: |
674 | case OP_MOV32: | 1613 | case OP_MOV32: |
@@ -676,12 +1615,15 @@ disassemble_instruction(Instruction instruction) { | |||
676 | println("%s r%d, r%d", op_str[instruction.op], instruction.dst, | 1615 | println("%s r%d, r%d", op_str[instruction.op], instruction.dst, |
677 | instruction.a, instruction.b); | 1616 | instruction.a, instruction.b); |
678 | break; | 1617 | break; |
1618 | case OP_JMPF: | ||
1619 | case OP_JMPT: | ||
1620 | println("%s l%d, r%d", op_str[instruction.op], instruction.dst, | ||
1621 | instruction.a, instruction.b); | ||
1622 | break; | ||
679 | case OP_LD8K: | 1623 | case OP_LD8K: |
680 | case OP_LD16K: | 1624 | case OP_LD16K: |
681 | case OP_LD32K: | 1625 | case OP_LD32K: |
682 | case OP_LD64K: | 1626 | case OP_LD64K: |
683 | case OP_JMPF: | ||
684 | case OP_JMPT: | ||
685 | println("%s r%d, c%d", op_str[instruction.op], instruction.dst, | 1627 | println("%s r%d, c%d", op_str[instruction.op], instruction.dst, |
686 | instruction.a, instruction.b); | 1628 | instruction.a, instruction.b); |
687 | break; | 1629 | break; |
@@ -715,6 +1657,7 @@ disassemble_instruction(Instruction instruction) { | |||
715 | case OP_BITRSHIFTI: | 1657 | case OP_BITRSHIFTI: |
716 | case OP_BITANDI: | 1658 | case OP_BITANDI: |
717 | case OP_BITORI: | 1659 | case OP_BITORI: |
1660 | case OP_BITXORI: | ||
718 | println("%s r%d, r%d, c%d", op_str[instruction.op], instruction.dst, | 1661 | println("%s r%d, r%d, c%d", op_str[instruction.op], instruction.dst, |
719 | instruction.a, instruction.b); | 1662 | instruction.a, instruction.b); |
720 | break; | 1663 | break; |
@@ -748,18 +1691,28 @@ disassemble_instruction(Instruction instruction) { | |||
748 | case OP_BITRSHIFT: | 1691 | case OP_BITRSHIFT: |
749 | case OP_BITAND: | 1692 | case OP_BITAND: |
750 | case OP_BITOR: | 1693 | case OP_BITOR: |
1694 | case OP_BITXOR: | ||
751 | println("%s r%d, r%d, r%d", op_str[instruction.op], instruction.dst, | 1695 | println("%s r%d, r%d, r%d", op_str[instruction.op], instruction.dst, |
752 | instruction.a, instruction.b); | 1696 | instruction.a, instruction.b); |
753 | break; | 1697 | break; |
754 | case OP_LDGVAR: | 1698 | case OP_LDGVAR: |
1699 | case OP_LDGADDR: | ||
1700 | case OP_LDLVAR: | ||
1701 | case OP_LDLADDR: | ||
755 | println("%s r%d, v%d", op_str[instruction.op], instruction.dst, | 1702 | println("%s r%d, v%d", op_str[instruction.op], instruction.dst, |
756 | instruction.a, instruction.b); | 1703 | instruction.a, instruction.b); |
757 | break; | 1704 | break; |
1705 | case OP_LDSTR: | ||
1706 | println("%s r%d, s%d", op_str[instruction.op], instruction.dst, | ||
1707 | instruction.a, instruction.b); | ||
1708 | break; | ||
758 | case OP_STGVAR: | 1709 | case OP_STGVAR: |
1710 | case OP_STLVAR: | ||
759 | println("%s v%d, r%d", op_str[instruction.op], instruction.dst, | 1711 | println("%s v%d, r%d", op_str[instruction.op], instruction.dst, |
760 | instruction.a, instruction.b); | 1712 | instruction.a, instruction.b); |
761 | break; | 1713 | break; |
762 | case OP_STGVARI: | 1714 | case OP_STGVARI: |
1715 | case OP_STLVARI: | ||
763 | println("%s v%d, c%d", op_str[instruction.op], instruction.dst, | 1716 | println("%s v%d, c%d", op_str[instruction.op], instruction.dst, |
764 | instruction.a, instruction.b); | 1717 | instruction.a, instruction.b); |
765 | break; | 1718 | break; |
@@ -773,19 +1726,37 @@ disassemble_instruction(Instruction instruction) { | |||
773 | println("%s r%d, r%d", op_str[instruction.op], instruction.dst, | 1726 | println("%s r%d, r%d", op_str[instruction.op], instruction.dst, |
774 | instruction.a, instruction.b); | 1727 | instruction.a, instruction.b); |
775 | break; | 1728 | break; |
776 | case OP_JMPI: | ||
777 | println("%s c%d", op_str[instruction.op], instruction.dst, | ||
778 | instruction.a, instruction.b); | ||
779 | break; | ||
780 | case OP_JMP: | 1729 | case OP_JMP: |
781 | println("%s r%d", op_str[instruction.op], instruction.dst, | 1730 | println("%s l%d", op_str[instruction.op], instruction.dst, |
782 | instruction.a, instruction.b); | 1731 | instruction.a, instruction.b); |
783 | break; | 1732 | break; |
784 | case OP_JMPFI: | 1733 | case OP_JMPFI: |
785 | case OP_JMPTI: | 1734 | case OP_JMPTI: |
786 | println("%s c%d, c%d", op_str[instruction.op], instruction.dst, | 1735 | println("%s l%d, c%d", op_str[instruction.op], instruction.dst, |
787 | instruction.a, instruction.b); | 1736 | instruction.a, instruction.b); |
788 | break; | 1737 | break; |
1738 | case OP_PRINTS64: | ||
1739 | case OP_PRINTF64: | ||
1740 | case OP_PRINTSTR: | ||
1741 | case OP_PRINTBOOL: | ||
1742 | case OP_PUSH: | ||
1743 | case OP_POP: | ||
1744 | case OP_PUTRET: | ||
1745 | println("%s r%d", op_str[instruction.op], instruction.dst, | ||
1746 | instruction.a, instruction.b); | ||
1747 | break; | ||
1748 | case OP_PRINTSTRI: | ||
1749 | case OP_PRINTS64I: | ||
1750 | case OP_PRINTF64I: | ||
1751 | case OP_PRINTBOOLI: | ||
1752 | case OP_RESERVE: | ||
1753 | case OP_PUSHI: | ||
1754 | case OP_PUTRETI: | ||
1755 | println("%s c%d", op_str[instruction.op], instruction.dst, | ||
1756 | instruction.a, instruction.b); | ||
1757 | break; | ||
1758 | case OP_RET: | ||
1759 | case OP_RECUR: | ||
789 | case OP_HALT: println("%s", op_str[instruction.op]); break; | 1760 | case OP_HALT: println("%s", op_str[instruction.op]); break; |
790 | default: println("Unknown opcode %d", instruction.op); break; | 1761 | default: println("Unknown opcode %d", instruction.op); break; |
791 | } | 1762 | } |
@@ -793,36 +1764,120 @@ disassemble_instruction(Instruction instruction) { | |||
793 | 1764 | ||
794 | void | 1765 | void |
795 | disassemble_chunk(Chunk chunk) { | 1766 | disassemble_chunk(Chunk chunk) { |
796 | println("%s: ============== code ==============", chunk.file_name); | 1767 | println("CHUNK %d: %s%s", chunk.id, chunk.file_name, chunk.name); |
1768 | println("n_regs: %d, n_vars: %d, n_strings: %d, n_consts: %d", | ||
1769 | chunk.reg_idx, array_size(chunk.vars), chunk.str_idx, | ||
1770 | chunk.const_idx); | ||
1771 | println("================== code =================="); | ||
1772 | println(" LINE:COL INUM LABELS OP OPERANDS "); | ||
1773 | println("------------------------------------------"); | ||
797 | for (sz i = 0; i < array_size(chunk.code); i++) { | 1774 | for (sz i = 0; i < array_size(chunk.code); i++) { |
798 | print("%s: %x{4}: ", chunk.file_name, i); | 1775 | printf(" %.4ld:%.4ld %.4lx ", chunk.linecol[i].line, |
1776 | chunk.linecol[i].col, i); | ||
1777 | IntIntMapIter lab_it = intintmap_iterator(chunk.labels, chunk.storage); | ||
1778 | IntIntMap *m = intintmap_next(&lab_it, chunk.storage); | ||
1779 | Str labs = cstr(""); | ||
1780 | while (m) { | ||
1781 | if (m->val == i) { | ||
1782 | labs = str_concat(labs, cstr(".L"), chunk.storage); | ||
1783 | labs = str_concat(labs, str_from_int(m->key, chunk.storage), | ||
1784 | chunk.storage); | ||
1785 | break; | ||
1786 | } | ||
1787 | m = intintmap_next(&lab_it, chunk.storage); | ||
1788 | } | ||
1789 | if (labs.size) { | ||
1790 | print("%s", labs); | ||
1791 | for (sz i = 0; i < 7 - labs.size; i++) { | ||
1792 | print(" "); | ||
1793 | } | ||
1794 | } else { | ||
1795 | printf(" "); | ||
1796 | } | ||
799 | disassemble_instruction(chunk.code[i]); | 1797 | disassemble_instruction(chunk.code[i]); |
800 | } | 1798 | } |
801 | if (array_size(chunk.constants) > 0) { | 1799 | if (array_size(chunk.constants) > 0) { |
802 | println("%s: ============ constants ===========", chunk.file_name); | 1800 | println("================ constants ===============", chunk.file_name); |
803 | for (sz i = 0; i < array_size(chunk.constants); i++) { | 1801 | for (sz i = 0; i < array_size(chunk.constants); i++) { |
804 | println("%s: %x{2}: %x{8}", chunk.file_name, i, | 1802 | println(" %x{2}: %x{8}", i, chunk.constants[i]); |
805 | chunk.constants[i]); | ||
806 | } | 1803 | } |
807 | } | 1804 | } |
808 | if (array_size(chunk.strings) > 0) { | 1805 | if (array_size(chunk.strings) > 0) { |
809 | println("%s: ============= strings ============", chunk.file_name); | 1806 | println("================= strings ================", chunk.file_name); |
810 | for (sz i = 0; i < array_size(chunk.strings); i++) { | 1807 | for (sz i = 0; i < array_size(chunk.strings); i++) { |
811 | println("%s: %x{2}: %s", chunk.file_name, i, chunk.strings[i]); | 1808 | println(" %x{2}: %s", i, chunk.strings[i]); |
812 | } | 1809 | } |
813 | } | 1810 | } |
814 | if (array_size(chunk.vars) > 0) { | 1811 | if (array_size(chunk.vars) > 0) { |
815 | println("%s: ============ variables ===========", chunk.file_name); | 1812 | println("================ variables ===============", chunk.file_name); |
816 | for (sz i = 0; i < array_size(chunk.vars); i++) { | 1813 | for (sz i = 0; i < array_size(chunk.vars); i++) { |
817 | println("%s: %x{2}: [%x{4}:%x{4}] %s: %s", chunk.file_name, i, | 1814 | println(" %x{2}: [%x{4}:%x{4}] %s: %s", i, chunk.vars[i].offset, |
818 | chunk.vars[i].offset, | ||
819 | chunk.vars[i].offset + chunk.vars[i].size, | 1815 | chunk.vars[i].offset + chunk.vars[i].size, |
820 | chunk.vars[i].name, chunk.vars[i].type); | 1816 | chunk.vars[i].name, chunk.vars[i].type); |
821 | } | 1817 | } |
822 | } | 1818 | } |
823 | println("n_regs: %d, n_vars: %d, n_strings: %d, n_consts: %d", | 1819 | if (array_size(chunk.functions) > 0) { |
824 | chunk.reg_idx, array_size(chunk.vars), chunk.str_idx, | 1820 | println("================ functions ===============", chunk.file_name); |
825 | chunk.const_idx); | 1821 | for (sz i = 0; i < array_size(chunk.functions); i++) { |
1822 | Chunk *func = chunk.functions[i]; | ||
1823 | println(" %x{2}: func%d: %s", i, func->id, func->name); | ||
1824 | } | ||
1825 | } | ||
1826 | println("=========================================="); | ||
1827 | for (sz i = 0; i < array_size(chunk.functions); i++) { | ||
1828 | Chunk *func = chunk.functions[i]; | ||
1829 | disassemble_chunk(*func); | ||
1830 | } | ||
1831 | } | ||
1832 | |||
1833 | void | ||
1834 | bytecode_compiler(Compiler *compiler, Parser parser) { | ||
1835 | compiler->main_chunk = (Chunk){ | ||
1836 | .file_name = compiler->file_name, | ||
1837 | .storage = compiler->storage, | ||
1838 | .name = cstr(".main"), | ||
1839 | }; | ||
1840 | // TODO: Fill up builtin types and tables. | ||
1841 | array_zero(compiler->main_chunk.constants, 256, compiler->storage); | ||
1842 | array_zero(compiler->main_chunk.code, 0xffff, compiler->storage); | ||
1843 | sz n_roots = array_size(parser.nodes); | ||
1844 | CompResult res = {0}; | ||
1845 | |||
1846 | // Do a first pass to setup the function declarations on the main scope. | ||
1847 | Chunk *chunk = &compiler->main_chunk; | ||
1848 | for (sz i = 0; i < n_roots; i++) { | ||
1849 | Node *root = parser.nodes[i]; | ||
1850 | if (root->kind == NODE_FUN) { | ||
1851 | declare_function(chunk, root); | ||
1852 | } | ||
1853 | } | ||
1854 | |||
1855 | // Compile all root expressions. | ||
1856 | for (sz i = 0; i < n_roots; i++) { | ||
1857 | Node *root = parser.nodes[i]; | ||
1858 | res = compile_expr(compiler, chunk, root); | ||
1859 | } | ||
1860 | |||
1861 | // Make sure the last result is on r0. | ||
1862 | sz res_reg = 0; | ||
1863 | bool is_nil = false; | ||
1864 | switch (res.type) { | ||
1865 | case COMP_CONST: { | ||
1866 | res_reg = chunk->reg_idx++; | ||
1867 | Instruction inst = | ||
1868 | (Instruction){.op = OP_LD64K, .dst = res_reg, .a = res.idx}; | ||
1869 | array_push(chunk->code, inst, chunk->storage); | ||
1870 | } break; | ||
1871 | case COMP_REG: { | ||
1872 | res_reg = res.idx; | ||
1873 | } break; | ||
1874 | case COMP_NIL: { | ||
1875 | is_nil = true; | ||
1876 | } break; | ||
1877 | default: break; | ||
1878 | } | ||
1879 | emit_op(OP_HALT, res_reg, !is_nil, 0, NULL, chunk); | ||
1880 | verify_chunk(chunk); | ||
826 | } | 1881 | } |
827 | 1882 | ||
828 | #endif // COMPILER_C | 1883 | #endif // COMPILER_C |