← Commit history

Read the CHANGELOG.md for infos

lennoxrose committed on Oct 7, 2026 at 18:44 UTC

9b697a8parente195044

3 files changed+190−0

kiln/src/parser/C/statements/attempt_handle.cadded

+86−0
@@ -0,0 +1,86 @@
1+#include "parser/H/core/parser.h"
2+#include "parser/H/core/parser_internal.h"
3+#include "parser/H/declarations/vars.h"
4+#include "codegen/H/emit/emit.h"
5+#include "codegen/H/runtime/errors.h"
6+#include "codegen/H/emit/layout.h"
7+#include "codegen/H/emit/runtime_layout.h"
8+
9+// attempt { A } handle (e) { B }
10+//
11+// A handler stack living in the fixed globals region (see
12+// elf/H/elf_writer.h) holds, per active attempt, the (rsp, rbp) to restore
13+// and the (target address, error-variable rbp-offset) to resume at --
14+// kiln's own setjmp/longjmp equivalent, hand-rolled since this is a
15+// freestanding binary with no libc to call into (see
16+// codegen/C/runtime/errors.c's raise routine for the runtime half of this).
17+//
18+// The handle variable's slot has to be reserved BEFORE compiling A: its
19+// rbp-relative offset must be embedded into the handler struct at
20+// attempt-entry, which runs before A's body -- and therefore before `e`'s
21+// real name, which only appears after A -- is even parsed. A placeholder
22+// slot is declared up front and retroactively renamed once `handle (e)`
23+// is actually seen. attempt/handle is inline (like given/during), not a
24+// new call frame, so this works the same way given/during's own bodies
25+// share the enclosing function's variable table.
26+void attempt_statement(void) {
27+ VarBlockScope saved_scope = vars_scope_begin();
28+ advance_token(); // consume 'attempt'
29+
30+ int handle_slot = declare_var("$handle", 7);
31+
32+ // --- push handler (bounds-checked -- overflowing here would
33+ // silently corrupt the code segment right after the handler table) ---
34+ emit_mov_reg_imm64(code, REG_RAX, kiln_try_depth_addr());
35+ emit_load_mem_disp32(code, REG_RAX, REG_RAX, 0);
36+ emit_cmp_reg_imm32(code, REG_RAX, MAX_KILN_TRY_DEPTH);
37+ int overflow = emit_jcc_rel32(code, COND_GE);
38+
39+ emit_mov_reg_imm64(code, REG_RCX, KILN_TRY_HANDLER_SIZE);
40+ emit_imul_reg_reg(code, REG_RAX, REG_RCX); // RAX = index * handler size
41+ emit_mov_reg_imm64(code, REG_RDI, kiln_try_handlers_addr());
42+ emit_add_reg_reg(code, REG_RDI, REG_RAX); // RDI = this handler's slot address
43+
44+ emit_store_mem_disp32(code, REG_RDI, 0, REG_RSP);
45+ emit_store_mem_disp32(code, REG_RDI, 8, REG_RBP);
46+ int patch_offset = emit_mov_reg_imm64_patchable(code, REG_RCX); // handle target, patched once known
47+ emit_store_mem_disp32(code, REG_RDI, 16, REG_RCX);
48+ emit_mov_reg_imm64(code, REG_RCX, (uint64_t)(int64_t)var_slot_tag_offset(handle_slot));
49+ emit_store_mem_disp32(code, REG_RDI, 24, REG_RCX);
50+
51+ emit_mov_reg_imm64(code, REG_RAX, kiln_try_depth_addr());
52+ emit_load_mem_disp32(code, REG_RCX, REG_RAX, 0);
53+ emit_add_reg_imm8(code, REG_RCX, 1);
54+ emit_store_mem_disp32(code, REG_RAX, 0, REG_RCX);
55+
56+ int skip_overflow_die = emit_jmp_rel32(code);
57+ emit_patch_jump(code, overflow);
58+ errors_emit_fatal(code, "runtime error: too many nested try blocks");
59+ emit_patch_jump(code, skip_overflow_die);
60+
61+ block(); // compiles A
62+
63+ // Normal completion (no error was raised): pop the handler -- past
64+ // this point, an error inside the enclosing scope should NOT be
65+ // caught by this attempt anymore.
66+ emit_mov_reg_imm64(code, REG_RAX, kiln_try_depth_addr());
67+ emit_load_mem_disp32(code, REG_RCX, REG_RAX, 0);
68+ emit_dec_reg(code, REG_RCX);
69+ emit_store_mem_disp32(code, REG_RAX, 0, REG_RCX);
70+
71+ int skip_handle = emit_jmp_rel32(code);
72+ int handle_code_offset = code->count;
73+ uint64_t handle_addr = kiln_code_base() + (uint64_t)handle_code_offset;
74+ emit_patch_imm64(code, patch_offset, handle_addr);
75+
76+ expect(TOKEN_HANDLE, "expected 'handle' after attempt block");
77+ expect(TOKEN_LPAREN, "expected '(' after 'handle'");
78+ expect(TOKEN_IDENTIFIER, "expected error variable name");
79+ rename_var(handle_slot, previous.start, previous.length);
80+ expect(TOKEN_RPAREN, "expected ')' after handle variable");
81+
82+ block(); // compiles B -- `e` already declared at handle_slot above
83+
84+ emit_patch_jump(code, skip_handle);
85+ vars_scope_end(saved_scope);
86+}

kiln/src/parser/C/statements/each_loop.cadded

+84−0
@@ -0,0 +1,84 @@
1+#include "parser/H/statements/each_loop.h"
2+#include "parser/H/core/parser.h"
3+#include "parser/H/core/parser_internal.h"
4+#include "parser/H/declarations/vars.h"
5+#include "parser/H/statements/loop_stack.h"
6+#include "codegen/H/expressions/expr.h"
7+#include "codegen/H/emit/emit.h"
8+
9+static int each_nesting_depth = 0;
10+
11+void each_depth_save(int *out) { *out = each_nesting_depth; }
12+void each_depth_reset(void) { each_nesting_depth = 0; }
13+void each_depth_restore(int saved) { each_nesting_depth = saved; }
14+
15+// each (x in array) { block } -- parens optional, matching
16+// given/during's own optional-paren conditions.
17+//
18+// The array expression is evaluated once, up front; x is bound to each
19+// element in turn via the same var-slot mechanism `local` uses, so it's a
20+// perfectly ordinary variable inside the body (readable, reassignable,
21+// though reassigning it doesn't affect iteration -- the next iteration
22+// overwrites it again from the array, matching a plain for-each's usual
23+// semantics). data_ptr/count/i live in this nesting level's fixed
24+// RBP-relative slot (see vars.h's each_level_offset) so they survive
25+// whatever the body's own codegen does, including calls.
26+void each_statement(void) {
27+ VarBlockScope saved_scope = vars_scope_begin();
28+ advance_token(); // consume 'each'
29+ int parenthesized = 0;
30+ if (current.type == TOKEN_LPAREN) { parenthesized = 1; advance_token(); }
31+ expect(TOKEN_IDENTIFIER, "expected loop variable name");
32+ const char *var_name = previous.start;
33+ int var_len = previous.length;
34+ expect(TOKEN_IN, "expected 'in' after loop variable");
35+ codegen_expression(); // pushes the array (tag, payload)
36+ if (parenthesized) expect(TOKEN_RPAREN, "expected ')' after loop expression");
37+
38+ if (each_nesting_depth >= MAX_KILN_EACH_DEPTH) parse_error("too many nested each loops");
39+ int32_t level = each_level_offset(each_nesting_depth);
40+ each_nesting_depth++;
41+
42+ emit_pop_reg(code, REG_RAX); // array payload (object address)
43+ emit_pop_reg(code, REG_RBX); // array tag (ignored, assumed ARRAY)
44+ emit_load_mem_disp32(code, REG_RCX, REG_RAX, 8); // count
45+ emit_load_mem_disp32(code, REG_RDX, REG_RAX, 16); // data_ptr
46+ emit_store_mem_disp32(code, REG_RBP, level + 0, REG_RDX);
47+ emit_store_mem_disp32(code, REG_RBP, level + 8, REG_RCX);
48+ emit_mov_reg_imm64(code, REG_RAX, 0);
49+ emit_store_mem_disp32(code, REG_RBP, level + 16, REG_RAX); // i = 0
50+
51+ int slot = resolve_var_in_current_scope(var_name, var_len);
52+ if (slot == -1) slot = declare_var(var_name, var_len);
53+
54+ int loop_start = code->count;
55+ emit_load_mem_disp32(code, REG_RCX, REG_RBP, level + 16); // i
56+ emit_load_mem_disp32(code, REG_RDX, REG_RBP, level + 8); // count
57+ emit_cmp_reg_reg(code, REG_RCX, REG_RDX);
58+ int loop_exit = emit_jcc_rel32(code, COND_GE);
59+
60+ emit_load_mem_disp32(code, REG_RSI, REG_RBP, level + 0); // data_ptr
61+ emit_mov_reg_reg(code, REG_RAX, REG_RCX);
62+ emit_mov_reg_imm64(code, REG_RDX, 16);
63+ emit_imul_reg_reg(code, REG_RAX, REG_RDX);
64+ emit_add_reg_reg(code, REG_RSI, REG_RAX); // element address
65+
66+ emit_load_mem_disp32(code, REG_RBX, REG_RSI, 0); // element tag
67+ emit_load_mem_disp32(code, REG_RAX, REG_RSI, 8); // element payload
68+ emit_store_mem_disp32(code, REG_RBP, var_slot_tag_offset(slot), REG_RBX);
69+ emit_store_mem_disp32(code, REG_RBP, var_slot_payload_offset(slot), REG_RAX);
70+
71+ loop_push();
72+ block();
73+ loop_patch_continues(); // continue lands here, right before i++
74+
75+ emit_load_mem_disp32(code, REG_RCX, REG_RBP, level + 16);
76+ emit_add_reg_imm8(code, REG_RCX, 1);
77+ emit_store_mem_disp32(code, REG_RBP, level + 16, REG_RCX); // i++
78+ emit_jmp_back(code, loop_start);
79+ emit_patch_jump(code, loop_exit);
80+ loop_pop_and_patch_breaks(); // break lands here, after the loop
81+
82+ each_nesting_depth--;
83+ vars_scope_end(saved_scope);
84+}

kiln/src/parser/H/statements/each_loop.hadded

+20−0
@@ -0,0 +1,20 @@
1+#ifndef KILN_EACH_LOOP_H
2+#define KILN_EACH_LOOP_H
3+
4+// each_statement itself is declared in parser_internal.h alongside
5+// given_statement/during_statement/forge_statement/attempt_statement (the
6+// other statement-compiler cross-file links) -- this header exists only
7+// for the depth counter below, which forge_statement/lambda.c also need.
8+//
9+// The compile-time each-nesting-depth counter's save/reset/restore, for
10+// forge_statement/lambda.c to wrap around a called body's compilation
11+// (same spirit as parser/H/declarations/vars.h's
12+// vars_save/vars_clear/vars_restore): a function or lambda body gets its
13+// own RBP at runtime, so its own `each` loops can safely reuse the same
14+// offsets independently of how deep the enclosing code's nesting was --
15+// see vars.h's each_level_offset comment.
16+void each_depth_save(int *out);
17+void each_depth_reset(void);
18+void each_depth_restore(int saved);
19+
20+#endif