$ cd /home/martin/workspace/prototypes/boot-to-claude git add docs/toolchain.md toolchain/ld/ld.h toolchain/ld/link_flat.c toolchain/ld/main.c git commit -m "$(cat <<'EOF' Retire Phase T6: fold flat-binary output into LD, drop OBJCOPY OBJCOPY's only job in this project was `objcopy -O binary` on the kernel ELF LD's link_flat mode produces. LD already has those loadable bytes laid out in memory before write_flat_elf wraps them in ELF headers, so a standalone extractor tool was never actually needed - add write_flat_bin (LD's --format bin) as a second writer sharing the same layout instead. Verified byte-identical to write_flat_elf's output piped through GNU objcopy -O binary, and separately boot-tested to SELFTEST PASS with no objcopy in the pipeline at all. docs/toolchain.md and the toolchain plan updated to drop OBJCOPY from the tool list and bootstrap dependencies. EOF )"
[main 0f82a8d] Retire Phase T6: fold flat-binary output into LD, drop OBJCOPY 4 files changed, 66 insertions(+), 17 deletions(-)
1 AS := nasm 2 CC := gcc 3 HOSTCC := gcc 4 LD := ld 5 OBJCOPY := objcopy 6 QEMU := qemu-system-i386 7 BUILD := build 8 9 CFLAGS := -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib \ 10 -Wall -Wextra -O2 -std=c11 -Ikernel 11 LDFLAGS := -m elf_i386 -T kernel/link.ld -nostdlib 12 13 # Must match TINYFS_DISK_SECTORS / TINYFS_FS_START_LBA in kernel/fs/tinyfs.h 14 # and KERNEL_START_LBA / KERNEL_SECTORS in boot/stage2.S 15 DISK_SECTORS := 4096 16 KERNEL_START_SECTOR := 5 17 KERNEL_MAX_SECTORS := 512 18 19 BOOT_BIN := $(BUILD)/boot.bin 20 STAGE2_BIN := $(BUILD)/stage2.bin 21 DISK_IMG := $(BUILD)/disk.img 22 23 KERNEL_C_SRCS := kernel/kmain.c kernel/console.c kernel/serial.c kernel/gdt.c \ 24 kernel/idt.c kernel/string.c kernel/memory.c kernel/selftest.c \ 25 kernel/ata.c kernel/fs/tinyfs.c kernel/fs/tinyfs_disk.c \ 26 kernel/keyboard.c kernel/shell/shell.c kernel/builtins_shim.c \ 27 kernel/lang/lexer.c kernel/lang/script/ast.c \ 28 kernel/lang/script/parser.c kernel/lang/script/eval.c \ 29 kernel/lang/compiler/parser.c kernel/lang/compiler/codegen.c \ 30 kernel/lang/vm.c 31 KERNEL_ASM_SRCS := kernel/entry.S kernel/idt_stubs.S 32 KERNEL_OBJS := $(patsubst kernel/%.S,$(BUILD)/%.o,$(KERNEL_ASM_SRCS)) \ 33 $(patsubst kernel/%.c,$(BUILD)/%.o,$(KERNEL_C_SRCS)) 34 KERNEL_ELF := $(BUILD)/kernel.elf 35 KERNEL_BIN := $(BUILD)/kernel.bin 36 37 HOSTCFLAGS := -m32 -Wall -Wextra -O2 -Ikernel 38 39 MKFS_BIN := $(BUILD)/mkfs_tinyfs 40 # host_string.o: kernel/string.c compiled natively - host tools are 41 # libc-free (see docs/toolchain.md), same as the kernel itself. 42 # host_syscalls_shim.o: real int 0x80 bodies for the interim GCC build 43 # only, dropped once Phase T7 switches to CC (see tools/host_syscalls.h). 44 MKFS_OBJS := $(BUILD)/host_tinyfs.o $(BUILD)/host_string.o \ 45 $(BUILD)/host_syscalls_shim.o $(BUILD)/host_mkfs_tinyfs.o 46 # host-path:fs-name pairs seeded into every disk image; the host paths 47 # alone are listed separately so they can be used as make prerequisites 48 MKFS_SEED_FILES := tests/hello.tsh tests/hello.tc 49 MKFS_SEED_SPECS := tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc 50 51 .PHONY: all run run-gui clean 52 53 all: $(DISK_IMG) 54 55 BUILD_SUBDIRS := $(BUILD) $(BUILD)/fs $(BUILD)/shell $(BUILD)/lang $(BUILD)/lang/script $(BUILD)/lang/compiler 56 57 $(BUILD_SUBDIRS): 58 mkdir -p $@ 59 60 $(BOOT_BIN): boot/boot.S | $(BUILD) 61 $(AS) -f bin -o $@ $< 62 63 $(STAGE2_BIN): boot/stage2.S | $(BUILD) 64 $(AS) -f bin -o $@ $< 65 66 $(BUILD)/%.o: kernel/%.S | $(BUILD_SUBDIRS) 67 $(AS) -f elf32 -o $@ $< 68 69 $(BUILD)/%.o: kernel/%.c | $(BUILD_SUBDIRS) 70 $(CC) $(CFLAGS) -c -o $@ $< 71 72 $(KERNEL_ELF): $(KERNEL_OBJS) kernel/link.ld 73 $(LD) $(LDFLAGS) -o $@ $(KERNEL_OBJS) 74 75 $(KERNEL_BIN): $(KERNEL_ELF) 76 $(OBJCOPY) -O binary $< $@ 77 @size=$$(stat -c%s $@); budget=$$(( $(KERNEL_MAX_SECTORS) * 512 )); \ 78 if [ "$$size" -gt "$$budget" ]; then \ 79 echo "error: $@ is $$size bytes, exceeds the $$budget-byte ($(KERNEL_MAX_SECTORS)-sector) boot budget" >&2; \ 80 echo " bump KERNEL_SECTORS in boot/stage2.S, KERNEL_MAX_SECTORS here, and TINYFS_FS_START_LBA in kernel/fs/tinyfs.h together" >&2; \ 81 exit 1; \ 82 fi 83 84 # --- host-side mkfs tool (build-time infrastructure, not kernel code) --- 85 # -m32: matches CC's x86-32-only codegen, so tools/host_syscalls_shim.c's 86 # int 0x80 convention is correct and this build stays consistent with 87 # what Phase T7 will produce. 88 89 $(BUILD)/host_tinyfs.o: kernel/fs/tinyfs.c | $(BUILD) 90 $(HOSTCC) $(HOSTCFLAGS) -c -o $@ $< 91 92 $(BUILD)/host_string.o: kernel/string.c | $(BUILD) 93 $(HOSTCC) $(HOSTCFLAGS) -c -o $@ $< 94 95 $(BUILD)/host_syscalls_shim.o: tools/host_syscalls_shim.c | $(BUILD) 96 $(HOSTCC) $(HOSTCFLAGS) -c -o $@ $< 97 98 $(BUILD)/host_mkfs_tinyfs.o: tools/mkfs_tinyfs.c | $(BUILD) 99 $(HOSTCC) $(HOSTCFLAGS) -c -o $@ $< 100 101 $(MKFS_BIN): $(MKFS_OBJS) 102 $(HOSTCC) -m32 -o $@ $(MKFS_OBJS) 103 104 $(DISK_IMG): $(BOOT_BIN) $(STAGE2_BIN) $(KERNEL_BIN) $(MKFS_BIN) $(MKFS_SEED_FILES) 105 dd if=/dev/zero of=$@ bs=512 count=$(DISK_SECTORS) status=none 106 dd if=$(BOOT_BIN) of=$@ conv=notrunc status=none 107 dd if=$(STAGE2_BIN) of=$@ bs=512 seek=1 conv=notrunc status=none 108 dd if=$(KERNEL_BIN) of=$@ bs=512 seek=$(KERNEL_START_SECTOR) conv=notrunc status=none 109 $(MKFS_BIN) $@ $(MKFS_SEED_SPECS) 110 111 # Headless: no window at all, so there's no input surface for the emulated 112 # PS/2 keyboard - useful for a quick boot sanity check or scripting, but 113 # you cannot type into the shell this way. Use `make run-gui` to interact. 114 run: $(DISK_IMG) 115 $(QEMU) -drive file=$(DISK_IMG),format=raw -display none -serial stdio -no-reboot 116 117 # Opens a real window - click into it, then type. Serial output is also 118 # echoed to this terminal, but keystrokes only reach the guest via the 119 # window. 120 run-gui: $(DISK_IMG) 121 $(QEMU) -drive file=$(DISK_IMG),format=raw -serial stdio -no-reboot 122 123 clean: 124 rm -rf $(BUILD) 125
$ cd /home/martin/workspace/prototypes/boot-to-claude ls toolchain/include/ echo "---" grep -rn "#include <" kernel/ tools/ --include=*.c --include=*.h | sort -u
stddef.h stdint.h --- kernel/ata.h:3:#include <stdint.h> kernel/builtins.h:13:#include <stdint.h> kernel/console.c:3:#include <stddef.h> kernel/console.c:4:#include <stdint.h> kernel/fs/tinyfs.h:8:#include <stdint.h> kernel/gdt.c:3:#include <stdint.h> kernel/idt.c:3:#include <stdint.h> kernel/keyboard.c:3:#include <stddef.h> kernel/keyboard.c:4:#include <stdint.h> kernel/lang/bytecode.h:6:#include <stdint.h> kernel/lang/compiler/codegen.h:3:#include <stdint.h> kernel/lang/lexer.h:8:#include <stdint.h> kernel/lang/vm.h:3:#include <stdint.h> kernel/memory.c:3:#include <stdint.h> kernel/memory.h:3:#include <stddef.h> kernel/selftest.c:3:#include <stdint.h> kernel/serial.c:3:#include <stdint.h> kernel/shell/shell.c:3:#include <stdint.h> kernel/string.c:3:#include <stdint.h> kernel/string.h:3:#include <stddef.h>
$ cd /home/martin/workspace/prototypes/boot-to-claude mkdir -p build/toolchain gcc -Wall -Wextra -O2 -o build/toolchain/as toolchain/as/*.c 2>&1 | tail -30 gcc -Wall -Wextra -O2 -o build/toolchain/cc toolchain/cc/*.c 2>&1 | tail -30 gcc -Wall -Wextra -O2 -o build/toolchain/ld toolchain/ld/*.c 2>&1 | tail -30 echo "bootstrap build: OK"
bootstrap build: OK
$ cd /home/martin/workspace/prototypes/boot-to-claude
KSRCS="kernel/kmain.c kernel/console.c kernel/serial.c kernel/gdt.c kernel/idt.c kernel/string.c kernel/memory.c kernel/selftest.c kernel/ata.c kernel/fs/tinyfs.c kernel/fs/tinyfs_disk.c kernel/keyboard.c kernel/shell/shell.c kernel/lang/lexer.c kernel/lang/script/ast.c kernel/lang/script/parser.c kernel/lang/script/eval.c kernel/lang/compiler/parser.c kernel/lang/compiler/codegen.c kernel/lang/vm.c"
fail=0
for f in $KSRCS; do
out=$(build/toolchain/cc -Itoolchain/include -Ikernel --parse-only "$f" 2>&1)
rc=$?
if [ $rc -ne 0 ]; then echo "FAIL $f: $out"; fail=1; else echo "OK $f"; fi
done
echo "===="
[ $fail -eq 0 ] && echo "ALL KERNEL C SOURCES PARSE OK" || echo "SOME FAILED"OK kernel/kmain.c OK kernel/console.c OK kernel/serial.c OK kernel/gdt.c OK kernel/idt.c OK kernel/string.c FAIL kernel/memory.c: cc: kernel/memory.c:14: unknown struct/union tag (near '?') OK kernel/selftest.c OK kernel/ata.c OK kernel/fs/tinyfs.c OK kernel/fs/tinyfs_disk.c OK kernel/keyboard.c FAIL kernel/shell/shell.c: cc: kernel/lang/compiler/../script/ast.h:21: unknown struct/union tag (near 'ast_node_t') FAIL kernel/lang/lexer.c: cc: kernel/lang/lexer.c:97: unexpected token (near '?') FAIL kernel/lang/script/ast.c: cc: kernel/lang/script/ast.h:21: unknown struct/union tag (near 'ast_node_t') FAIL kernel/lang/script/parser.c: cc: kernel/lang/script/ast.h:21: unknown struct/union tag (near 'ast_node_t') FAIL kernel/lang/script/eval.c: cc: kernel/lang/script/ast.h:21: unknown struct/union tag (near 'ast_node_t') FAIL kernel/lang/compiler/parser.c: cc: kernel/lang/compiler/../script/ast.h:21: unknown struct/union tag (near 'ast_node_t') FAIL kernel/lang/compiler/codegen.c: cc: kernel/lang/compiler/../script/ast.h:21: unknown struct/union tag (near 'ast_node_t') FAIL kernel/lang/vm.c: cc: kernel/lang/vm.c:97: unexpected token (near '?') ==== SOME FAILED
$ cd /home/martin/workspace/prototypes/boot-to-claude sed -n '1,20p' kernel/memory.c
#include "memory.h"
#include <stdint.h>
/* Fixed heap region. Starts at 1MB to stay clear of the kernel image
* (loaded at 0x10000), the boot stack (0x90000), and the legacy BIOS/VGA
* memory holes below 1MB. QEMU's default RAM is well above this. */
#define HEAP_START 0x100000u
#define HEAP_SIZE (1u * 1024u * 1024u)
typedef struct block_header {
size_t size; /* usable size, excluding this header */
int free;
struct block_header *next;
} block_header_t;
static block_header_t *heap_head;
void memory_init(void) {
heap_head = (block_header_t *)HEAP_START;$ cd /home/martin/workspace/prototypes/boot-to-claude sed -n '1,30p' kernel/lang/script/ast.h
#pragma once
#include "../lexer.h"
typedef enum {
AST_NUM,
AST_STR,
AST_VAR,
AST_ASSIGN,
AST_BINOP,
AST_UNOP,
AST_CALL,
AST_IF,
AST_WHILE,
AST_FUNC_DEF,
AST_RETURN,
AST_BLOCK,
AST_EXPR_STMT,
} ast_kind_t;
typedef struct ast_node ast_node_t;
/* One generic node shape for the whole tree, in the tradition of tiny
* interpreters: `a`/`b`/`c` are kind-specific children, `list` is a
* singly-linked chain (block statements, call args, function params),
* and `next` links siblings within such a chain. See eval.c/parser.c for
* which fields each AST_* kind actually uses. */
struct ast_node {
ast_kind_t kind;
token_type_t op; /* AST_BINOP / AST_UNOP */$ cd /home/martin/workspace/prototypes/boot-to-claude sed -n '90,100p' kernel/lang/lexer.c echo "---vm.c---" sed -n '90,100p' kernel/lang/vm.c
}
tok.string = s;
tok.type = TOK_STRING;
return tok;
}
lx->pos++;
switch (c) {
case '(': tok.type = TOK_LPAREN; break;
case ')': tok.type = TOK_RPAREN; break;
case '{': tok.type = TOK_LBRACE; break;
---vm.c---
for (;;) {
if (pc >= code_size) {
report_error("program counter ran off the end of code");
return;
}
uint8_t op = code[pc++];
switch ((bc_opcode_t)op) {
case OP_HALT:
return;$ cd /home/martin/workspace/prototypes/boot-to-claude wc -l kernel/lang/lexer.c for n in 50 100 150 200 250 300; do head -n $n kernel/lang/lexer.c > /tmp/lexer_trunc.c echo "---- $n lines ----" build/toolchain/cc -Itoolchain/include -Ikernel --parse-only /tmp/lexer_trunc.c 2>&1 | tail -3 done
138 kernel/lang/lexer.c ---- 50 lines ---- cc: /tmp/lexer_trunc.c:1: cannot find include file 'lexer.h' ---- 100 lines ---- cc: /tmp/lexer_trunc.c:1: cannot find include file 'lexer.h' ---- 150 lines ---- cc: /tmp/lexer_trunc.c:1: cannot find include file 'lexer.h' ---- 200 lines ---- cc: /tmp/lexer_trunc.c:1: cannot find include file 'lexer.h' ---- 250 lines ---- cc: /tmp/lexer_trunc.c:1: cannot find include file 'lexer.h' ---- 300 lines ---- cc: /tmp/lexer_trunc.c:1: cannot find include file 'lexer.h'
$ cd /home/martin/workspace/prototypes/boot-to-claude wc -l kernel/lang/lexer.c for n in 40 60 80 97 100 120 138; do head -n $n kernel/lang/lexer.c > kernel/lang/lexer_trunc.c echo "---- $n lines ----" build/toolchain/cc -Itoolchain/include -Ikernel --parse-only kernel/lang/lexer_trunc.c 2>&1 | tail -3 done rm -f kernel/lang/lexer_trunc.c
138 kernel/lang/lexer.c ---- 40 lines ---- cc: ?:0: expected expression (near '?') ---- 60 lines ---- cc: ?:0: expected expression (near '?') ---- 80 lines ---- cc: ?:0: expected expression (near '?') ---- 97 lines ---- cc: kernel/lang/lexer_trunc.c:97: unexpected token (near '?') ---- 100 lines ---- cc: kernel/lang/lexer_trunc.c:97: unexpected token (near '?') ---- 120 lines ---- cc: kernel/lang/lexer_trunc.c:97: unexpected token (near '?') ---- 138 lines ---- cc: kernel/lang/lexer_trunc.c:97: unexpected token (near '?')
$ cd /home/martin/workspace/prototypes/boot-to-claude for n in 82 85 88 90 92 94 96 97; do head -n $n kernel/lang/lexer.c > kernel/lang/lexer_trunc.c echo "---- $n lines ----" build/toolchain/cc -Itoolchain/include -Ikernel --parse-only kernel/lang/lexer_trunc.c 2>&1 | tail -2 done rm -f kernel/lang/lexer_trunc.c
---- 82 lines ---- cc: ?:0: expected expression (near '?') ---- 85 lines ---- cc: ?:0: expected expression (near '?') ---- 88 lines ---- cc: ?:0: expected expression (near '?') ---- 90 lines ---- cc: ?:0: expected expression (near '?') ---- 92 lines ---- cc: ?:0: expected expression (near '?') ---- 94 lines ---- cc: ?:0: expected expression (near '?') ---- 96 lines ---- cc: ?:0: expected expression (near '?') ---- 97 lines ---- cc: kernel/lang/lexer_trunc.c:97: unexpected token (near '?')
96 lx->pos++;
97 switch (c) {
98 case '(': tok.type = TOK_LPAREN; break;
99 case ')': tok.type = TOK_RPAREN; break;
100 case '{': tok.type = TOK_LBRACE; break;
101 case '}': tok.type = TOK_RBRACE; break;
102 case ',': tok.type = TOK_COMMA; break;
103 case ';': tok.type = TOK_SEMI; break;
104 case '+': tok.type = TOK_PLUS; break;
105 case '-': tok.type = TOK_MINUS; break;
106 case '*': tok.type = TOK_STAR; break;
107 case '/': tok.type = TOK_SLASH; break;
108 case '%': tok.type = TOK_PERCENT; break;
109 case '=':
110 if (lx->src[lx->pos] == '=') { lx->pos++; tok.type = TOK_EQ; }
111 else { tok.type = TOK_ASSIGN; }
112 break;
113 case '!':
114 if (lx->src[lx->pos] == '=') { lx->pos++; tok.type = TOK_NEQ; }
115 else { tok.type = TOK_NOT; }
116 break;
117 case '<':
118 if (lx->src[lx->pos] == '=') { lx->pos++; tok.type = TOK_LE; }
119 else { tok.type = TOK_LT; }
120 break;
121 case '>':
122 if (lx->src[lx->pos] == '=') { lx->pos++; tok.type = TOK_GE; }
123 else { tok.type = TOK_GT; }
124 break;
125 case '&':
126 if (lx->src[lx->pos] == '&') { lx->pos++; tok.type = TOK_AND; }
127 else { tok.type = TOK_ERROR; }
128 break;
129 case '|':
130 if (lx->src[lx->pos] == '|') { lx->pos++; tok.type = TOK_OR; }
131 else { tok.type = TOK_ERROR; }
132 break;
133 default:
134 tok.type = TOK_ERROR;
135 break;$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n '"case"\|"switch"\|"default"\|ND_CASE\|ND_SWITCH' toolchain/cc/parser.c | head -20
(Bash completed with no output)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "g_loop\|ND_BREAK\|ND_CONTINUE\|\"break\"\|\"continue\"" toolchain/cc/parser.c toolchain/cc/codegen.c
toolchain/cc/codegen.c:25:static loop_ctx_t *g_loop;
toolchain/cc/codegen.c:367: loop_ctx_t lc = { "", "", g_loop };
toolchain/cc/codegen.c:370: g_loop = &lc;
toolchain/cc/codegen.c:376: g_loop = lc.parent;
toolchain/cc/codegen.c:382: loop_ctx_t lc = { "", "", g_loop };
toolchain/cc/codegen.c:385: g_loop = &lc;
toolchain/cc/codegen.c:392: g_loop = lc.parent;
toolchain/cc/codegen.c:405: loop_ctx_t lc = { "", "", g_loop };
toolchain/cc/codegen.c:408: g_loop = &lc;
toolchain/cc/codegen.c:415: g_loop = lc.parent;
toolchain/cc/codegen.c:422: case ND_BREAK:
toolchain/cc/codegen.c:423: out(" jmp %s\n", g_loop->break_label);
toolchain/cc/codegen.c:425: case ND_CONTINUE:
toolchain/cc/codegen.c:426: out(" jmp %s\n", g_loop->continue_label);
toolchain/cc/codegen.c:529: g_loop = NULL;
toolchain/cc/parser.c:812: if (match_kw(p, "break")) { expect(p, TK_SEMI); return node_new(ND_BREAK); }
toolchain/cc/parser.c:813: if (match_kw(p, "continue")) { expect(p, TK_SEMI); return node_new(ND_CONTINUE); }1 #include "cc.h"
2
3 #include <stdio.h>
4 #include <stdarg.h>
5 #include <string.h>
6 #include <stdlib.h>
7
8 /* Naive, non-optimizing codegen: every intermediate expression value
9 * lives in EAX; binary operators spill the left operand to the real
10 * machine stack (push/pop) while the right operand is computed - no
11 * register allocation. Emits AS-syntax assembly text (mirrors
12 * `gcc -S | as`). See docs/toolchain.md for the calling convention and
13 * builtin mechanism. */
14
15 static FILE *g_out;
16 static cc_target_t g_target;
17 static int g_label_id;
18 static char g_cur_func[MAX_NAME];
19
20 typedef struct loop_ctx {
21 char break_label[MAX_NAME];
22 char continue_label[MAX_NAME];
23 struct loop_ctx *parent;
24 } loop_ctx_t;
25 static loop_ctx_t *g_loop;
26
27 static void out(const char *fmt, ...) {
28 va_list ap;
29 va_start(ap, fmt);
30 vfprintf(g_out, fmt, ap);340 /* Brace-init lists (`{1,2,3}`) are only supported for
341 * globals/statics (compile-time .data, see emit_data_bytes)
342 * - this codebase never brace-initializes a plain (stack)
343 * local, only scalars. */
344 if (n->init) {
345 out(" lea eax, [ebp%+d]\n", n->frame_offset);
346 out(" push eax\n");
347 codegen_expr(n->init);
348 out(" pop ecx\n");
349 store_value(n->type);
350 }
351 return;
352 case ND_IF: {
353 char lelse[MAX_NAME], lend[MAX_NAME];
354 new_label(lelse); new_label(lend);
355 codegen_expr(n->a);
356 out(" cmp eax, 0\n je %s\n", lelse);
357 codegen_stmt(n->b);
358 out(" jmp %s\n", lend);
359 out("%s:\n", lelse);
360 if (n->c) codegen_stmt(n->c);
361 out("%s:\n", lend);
362 return;
363 }
364 case ND_WHILE: {
365 char ltop[MAX_NAME], lend[MAX_NAME];
366 new_label(ltop); new_label(lend);
367 loop_ctx_t lc = { "", "", g_loop };
368 safe_strcpy(lc.break_label, lend, MAX_NAME);
369 safe_strcpy(lc.continue_label, ltop, MAX_NAME);
370 g_loop = &lc;
371 out("%s:\n", ltop);
372 codegen_expr(n->a);
373 out(" cmp eax, 0\n je %s\n", lend);
374 codegen_stmt(n->b);
375 out(" jmp %s\n%s:\n", ltop, lend);
376 g_loop = lc.parent;
377 return;
378 }
379 case ND_DOWHILE: {
380 char ltop[MAX_NAME], lend[MAX_NAME], lcont[MAX_NAME];
381 new_label(ltop); new_label(lend); new_label(lcont);
382 loop_ctx_t lc = { "", "", g_loop };
383 safe_strcpy(lc.break_label, lend, MAX_NAME);
384 safe_strcpy(lc.continue_label, lcont, MAX_NAME);
385 g_loop = &lc;
386 out("%s:\n", ltop);
387 codegen_stmt(n->b);
388 out("%s:\n", lcont);
389 codegen_expr(n->a);
390 out(" cmp eax, 0\n jne %s\n", ltop);
391 out("%s:\n", lend);
392 g_loop = lc.parent;
393 return;
394 }
395 case ND_FOR: {
396 char ltop[MAX_NAME], lend[MAX_NAME], lcont[MAX_NAME];
397 new_label(ltop); new_label(lend); new_label(lcont);
398 if (n->a) {
399 if (n->a->kind == ND_VAR_DECL) {
400 for (node_t *d = n->a; d; d = d->next) codegen_stmt(d);
401 } else {
402 codegen_expr(n->a);
403 }
404 }
405 loop_ctx_t lc = { "", "", g_loop };
406 safe_strcpy(lc.break_label, lend, MAX_NAME);
407 safe_strcpy(lc.continue_label, lcont, MAX_NAME);
408 g_loop = &lc;
409 out("%s:\n", ltop);
410 if (n->b) { codegen_expr(n->b); out(" cmp eax, 0\n je %s\n", lend); }
411 codegen_stmt(n->d);
412 out("%s:\n", lcont);
413 if (n->c) codegen_expr(n->c);
414 out(" jmp %s\n%s:\n", ltop, lend);
415 g_loop = lc.parent;
416 return;
417 }
418 case ND_RETURN:
419 if (n->a) codegen_expr(n->a);
420 out(" mov esp, ebp\n pop ebp\n ret\n");
421 return;
422 case ND_BREAK:
423 out(" jmp %s\n", g_loop->break_label);
424 return;
425 case ND_CONTINUE:
426 out(" jmp %s\n", g_loop->continue_label);
427 return;
428 case ND_NOP:
429 return;$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "typedef enum.*node_kind\|ND_WHILE\|ND_FOR\|ND_BREAK\|} node_kind_t" toolchain/cc/cc.h
126: ND_IF, ND_WHILE, ND_DOWHILE, ND_FOR, ND_RETURN, ND_BREAK, ND_CONTINUE, 129:} node_kind_t;
118
119 /* ---------------- AST ---------------- */
120
121 typedef enum {
122 ND_NUM, ND_STR, ND_VAR, ND_ASSIGN, ND_COMPOUND_ASSIGN, ND_BINOP, ND_LOGAND, ND_LOGOR,
123 ND_UNARY_MINUS, ND_NOT, ND_BITNOT, ND_ADDR, ND_DEREF,
124 ND_PREINC, ND_PREDEC, ND_POSTINC, ND_POSTDEC,
125 ND_CALL, ND_INDEX, ND_MEMBER, ND_ARROW, ND_CAST, ND_SIZEOF, ND_TERNARY, ND_COMMA,
126 ND_IF, ND_WHILE, ND_DOWHILE, ND_FOR, ND_RETURN, ND_BREAK, ND_CONTINUE,
127 ND_BLOCK, ND_EXPR_STMT, ND_NOP,
128 ND_FUNC_DEF, ND_VAR_DECL,
129 } node_kind_t;
130
131 typedef struct node node_t;
132 struct node {- ND_IF, ND_WHILE, ND_DOWHILE, ND_FOR, ND_RETURN, ND_BREAK, ND_CONTINUE, - ND_BLOCK, ND_EXPR_STMT, ND_NOP, + ND_IF, ND_WHILE, ND_DOWHILE, ND_FOR, ND_RETURN, ND_BREAK, ND_CONTINUE, + ND_SWITCH, ND_CASE, ND_DEFAULT, + ND_BLOCK, ND_EXPR_STMT, ND_NOP,
The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/cc.h has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "static node_t \*parse_stmt\|match_kw(p, \"while\")\|match_kw(p, \"for\")\|match_kw(p, \"if\")\|match_kw(p, \"break\")\|match_kw(p, \"continue\")\|match_kw(p, \"do\")" toolchain/cc/parser.c
127:static node_t *parse_stmt(parser_t *p);
762:static node_t *parse_stmt(parser_t *p) {
765: if (match_kw(p, "if")) {
774: if (match_kw(p, "while")) {
782: if (match_kw(p, "do")) {
785: if (!match_kw(p, "while")) perr(p, "expected 'while'");
792: if (match_kw(p, "for")) {
812: if (match_kw(p, "break")) { expect(p, TK_SEMI); return node_new(ND_BREAK); }
813: if (match_kw(p, "continue")) { expect(p, TK_SEMI); return node_new(ND_CONTINUE); }762 static node_t *parse_stmt(parser_t *p) {
763 if (check(p, TK_LBRACE)) return parse_block(p);
764
765 if (match_kw(p, "if")) {
766 expect(p, TK_LPAREN);
767 node_t *n = node_new(ND_IF);
768 n->a = parse_expr(p);
769 expect(p, TK_RPAREN);
770 n->b = parse_stmt(p);
771 if (match_kw(p, "else")) n->c = parse_stmt(p);
772 return n;
773 }
774 if (match_kw(p, "while")) {
775 expect(p, TK_LPAREN);
776 node_t *n = node_new(ND_WHILE);
777 n->a = parse_expr(p);
778 expect(p, TK_RPAREN);
779 n->b = parse_stmt(p);
780 return n;
781 }
782 if (match_kw(p, "do")) {
783 node_t *n = node_new(ND_DOWHILE);
784 n->b = parse_stmt(p);
785 if (!match_kw(p, "while")) perr(p, "expected 'while'");
786 expect(p, TK_LPAREN);
787 n->a = parse_expr(p);
788 expect(p, TK_RPAREN);
789 expect(p, TK_SEMI);
790 return n;
791 }
792 if (match_kw(p, "for")) {
793 expect(p, TK_LPAREN);
794 node_t *n = node_new(ND_FOR);
795 if (!check(p, TK_SEMI)) {
796 n->a = is_type_start(p) ? parse_local_decl_stmt(p) : NULL;
797 if (!n->a) { n->a = parse_expr(p); expect(p, TK_SEMI); }
798 } else { advance(p); }
799 if (!check(p, TK_SEMI)) n->b = parse_expr(p);
800 expect(p, TK_SEMI);
801 if (!check(p, TK_RPAREN)) n->c = parse_expr(p);
802 expect(p, TK_RPAREN);
803 n->d = parse_stmt(p);
804 return n;
805 }
806 if (match_kw(p, "return")) {
807 node_t *n = node_new(ND_RETURN);
808 if (!check(p, TK_SEMI)) n->a = parse_expr(p);
809 expect(p, TK_SEMI);
810 return n;
811 }
812 if (match_kw(p, "break")) { expect(p, TK_SEMI); return node_new(ND_BREAK); }
813 if (match_kw(p, "continue")) { expect(p, TK_SEMI); return node_new(ND_CONTINUE); }
814 if (match(p, TK_SEMI)) return node_new(ND_NOP);
815
816 if (is_type_start(p)) return parse_local_decl_stmt(p);
817
818 node_t *n = node_new(ND_EXPR_STMT);
819 n->a = parse_expr(p);
820 expect(p, TK_SEMI);
821 return n;
822 }
823
824 static node_t *parse_block(parser_t *p) {
825 expect(p, TK_LBRACE);
826 node_t *blk = node_new(ND_BLOCK);
827 node_t *tail = NULL;
828 while (!check(p, TK_RBRACE)) {
829 node_t *s = parse_stmt(p);
830 if (!blk->list) blk->list = s; else tail->next = s;
831 tail = s;$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "frame_size" toolchain/cc/parser.c
36: int frame_size; /* current function's local variable area size (grows as locals are declared) */ 111: p->frame_size += type_size(type) < 4 ? 4 : type_size(type); /* min 4-byte slots - simplicity over density */ 112: p->frame_size = (p->frame_size + 3) & ~3; 113: s->frame_offset = -p->frame_size; 859: p->frame_size = 0; 890: n->num = p->frame_size;
103 static sym_t *add_local(parser_t *p, const char *name, type_t *type, int is_static) {
104 sym_t *s = calloc(1, sizeof(sym_t));
105 safe_strcpy(s->name, name, MAX_NAME);
106 s->type = type;
107 if (is_static) {
108 s->is_static_local = 1;
109 snprintf(s->asm_label, MAX_NAME, "__static_%d", p->next_static_id++);
110 } else {
111 p->frame_size += type_size(type) < 4 ? 4 : type_size(type); /* min 4-byte slots - simplicity over density */
112 p->frame_size = (p->frame_size + 3) & ~3;
113 s->frame_offset = -p->frame_size;
114 }
115 s->next = p->locals;
116 p->locals = s;
117 return s;
118 }
119
120 /* ---------------- expressions (forward decls) ---------------- */
121
122 static node_t *parse_expr(parser_t *p);418 case ND_RETURN:
419 if (n->a) codegen_expr(n->a);
420 out(" mov esp, ebp\n pop ebp\n ret\n");
421 return;
422 case ND_BREAK:
423 out(" jmp %s\n", g_loop->break_label);
424 return;
425 case ND_CONTINUE:
426 out(" jmp %s\n", g_loop->continue_label);
427 return;
428 case ND_NOP:
429 return;
430 default:
431 fprintf(stderr, "cc: internal error: unsupported statement kind %d\n", n->kind);
432 exit(1);
433 }
434 }
435
436 /* ---------------- top level ---------------- */
437
438 static void emit_data_bytes(node_t *init, type_t *t);
439
440 static void emit_scalar_bytes(node_t *v, type_t *t) {
441 if (t->kind == TY_CHAR) out(" db %lld\n", (long long)v->num);
442 else out(" dd %lld\n", (long long)v->num);- case ND_BREAK:
- out(" jmp %s\n", g_loop->break_label);
- return;
- case ND_CONTINUE:
- out(" jmp %s\n", g_loop->continue_label);
- return;
- case ND_NOP:
- return;
+ case ND_BREAK:
+ out(" jmp %s\n", g_loop->break_label);
+ return;
+ case ND_CONTINUE:
+ out(" jmp %s\n", g_loop->continue_label);
+ return;
+ case ND_SWITCH: {
+ /* n->b is an ND_BLOCK whose ->list is a flat chain of
+ * case/default markers interleaved with ordinary statements -
+ * parsed via the regular parse_block, since `case`/`default`
+ * are just zero-width label statements (see parser.c). Walking
+ * that chain linearly and falling through unless a `break`
+ * jumps out reproduces C's fallthrough semantics for free. */
+ char lend[MAX_NAME];
+ new_label(lend);
+ codegen_expr(n->a);
+ out(" mov [ebp%+d], eax\n", n->frame_offset);
+
+ char default_label[MAX_NAME] = "";
+ for (node_t *s = n->b->list; s; s = s->next) {
+ if (s->kind == ND_CASE || s->kind == ND_DEFAULT) new_label(s->asm_label);
+ if (s->kind == ND_DEFAULT) safe_strcpy(default_label, s->asm_label, MAX_NAME);
+ }
+ for (node_t *s = n->b->list; s; s = s->next) {
+ if (s->kind != ND_CASE) continue;
+ out(" mov eax, [ebp%+d]\n", n->frame_offset);
+ out(" cmp eax, %lld\n", (long long)s->num);
+ out(" je %s\n", s->asm_label);
+ }
+ out(" jmp %s\n", default_label[0] ? default_label : lend);
+
+ loop_ctx_t lc = { "", "", g_loop };
+ safe_strcpy(lc.break_label, lend, MAX_NAME);
+ /* `continue` inside a switch targets the nearest *enclosing
+ * loop*, not the switch itself - inherit the parent frame's
+ * continue_label rather than giving this frame its own. */
+ safe_strcpy(lc.continue_label, g_loop ? g_loop->continue_label : "", MAX_NAME);
+ g_loop = &lc;
+ for (node_t *s = n->b->list; s; s = s->next) {
+ if (s->kind == ND_CASE || s->kind == ND_DEFAULT) out("%s:\n", s->asm_label);
+ else codegen_stmt(s);
+ }
+ g_loop = lc.parent;
+ out("%s:\n", lend);
+ return;
+ }
+ case ND_NOP:
+ return;The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
- if (match_kw(p, "break")) { expect(p, TK_SEMI); return node_new(ND_BREAK); }
- if (match_kw(p, "continue")) { expect(p, TK_SEMI); return node_new(ND_CONTINUE); }
+ if (match_kw(p, "break")) { expect(p, TK_SEMI); return node_new(ND_BREAK); }
+ if (match_kw(p, "continue")) { expect(p, TK_SEMI); return node_new(ND_CONTINUE); }
+ if (match_kw(p, "switch")) {
+ expect(p, TK_LPAREN);
+ node_t *n = node_new(ND_SWITCH);
+ n->a = parse_expr(p);
+ expect(p, TK_RPAREN);
+ /* Hidden frame slot to hold the switch value across codegen's
+ * compare chain - same "min 4-byte slot" convention as
+ * add_local, but with no symbol-table entry (never referenced
+ * by name, so nothing else needs to find it). */
+ p->frame_size += 4;
+ p->frame_size = (p->frame_size + 3) & ~3;
+ n->frame_offset = -p->frame_size;
+ n->b = parse_block(p);
+ return n;
+ }
+ /* `case`/`default` are zero-width label statements, not statements
+ * with their own body (see codegen.c's ND_SWITCH) - parse_block's
+ * ordinary statement loop naturally makes the *next* statement a
+ * separate list entry, which is exactly what C's fallthrough
+ * semantics need. */
+ if (match_kw(p, "case")) {
+ node_t *n = node_new(ND_CASE);
+ n->num = parse_const_expr(p);
+ expect(p, TK_COLON);
+ return n;
+ }
+ if (match_kw(p, "default")) {
+ node_t *n = node_new(ND_DEFAULT);
+ expect(p, TK_COLON);
+ return n;
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/parser.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "static int64_t parse_const_expr\|parse_const_expr(parser_t" toolchain/cc/parser.c | head -5
162:static int64_t parse_const_expr(parser_t *p) {130 /* Array bounds and enum values in this codebase are often macro
131 * constants that expand to a small arithmetic expression (e.g.
132 * `TINYFS_INODE_BLOCK` -> `(1 + 1)`), not always a single literal
133 * token, so bounds need real constant folding rather than reading one
134 * token's ->num. */
135 static int64_t eval_const_node(parser_t *p, node_t *n) {
136 switch (n->kind) {
137 case ND_NUM: return n->num;
138 case ND_UNARY_MINUS: return -eval_const_node(p, n->a);
139 case ND_BITNOT: return ~eval_const_node(p, n->a);
140 case ND_NOT: return !eval_const_node(p, n->a);
141 case ND_BINOP: {
142 int64_t l = eval_const_node(p, n->a), r = eval_const_node(p, n->b);
143 switch (n->op) {
144 case TK_PLUS: return l + r;
145 case TK_MINUS: return l - r;
146 case TK_STAR: return l * r;
147 case TK_SLASH: return r != 0 ? l / r : 0;
148 case TK_PERCENT: return r != 0 ? l % r : 0;
149 case TK_SHL: return l << r;
150 case TK_SHR: return l >> r;
151 case TK_AMP: return l & r;
152 case TK_PIPE: return l | r;
153 case TK_CARET: return l ^ r;
154 default: perr(p, "unsupported operator in constant expression"); return 0;
155 }
156 }
157 default:
158 perr(p, "expected a constant expression");
159 return 0;
160 }
161 }162 static int64_t parse_const_expr(parser_t *p) {
163 return eval_const_node(p, parse_assign(p));
164 }
165
166 /* ---------------- type specifiers & declarators ---------------- */
167
168 static member_t *parse_struct_members(parser_t *p);
169
170 static int is_type_start(parser_t *p) {
171 if (!check(p, TK_IDENT)) return 0;$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "TK_COLON" toolchain/cc/*.h toolchain/cc/lexer.c | head -10
toolchain/cc/cc.h:27: TK_SEMI, TK_COMMA, TK_COLON, TK_QUESTION, TK_DOT, TK_ARROW, toolchain/cc/lexer.c:164: case ':': k = TK_COLON; break;
193 if (match_kw(p, "int")) { saw_int = 1; continue; }
194 /* long == int on this 32-bit target (both 4 bytes) - no `long
195 * long`/`unsigned long` anywhere in the codebase (see
196 * docs/toolchain.md), so a same-size alias is enough. */
197 if (match_kw(p, "long")) { saw_long = 1; continue; }
198 if (match_kw(p, "signed")) { saw_signed = 1; continue; }
199 if (match_kw(p, "unsigned")) { saw_unsigned = 1; continue; }
200 if (is_kw(p, "struct") || is_kw(p, "union")) {
201 int is_union = is_kw(p, "union");
202 advance(p);
203 char tagname[MAX_NAME] = {0};
204 if (check(p, TK_IDENT)) { safe_strcpy(tagname, cur(p).text, MAX_NAME); advance(p); }
205 type_t *st;
206 tag_t *existing = tagname[0] ? tag_find(p, tagname) : NULL;
207 if (check(p, TK_LBRACE)) {
208 advance(p);
209 st = type_new(is_union ? TY_UNION : TY_STRUCT);
210 safe_strcpy(st->tag, tagname, MAX_NAME);
211 st->members = parse_struct_members(p);
212 expect(p, TK_RBRACE);
213 if (is_kw(p, "__attribute__")) {
214 advance(p); expect(p, TK_LPAREN); expect(p, TK_LPAREN);
215 if (is_kw(p, "packed")) { advance(p); st->is_packed = 1; }
216 expect(p, TK_RPAREN); expect(p, TK_RPAREN);
217 }
218 /* Compute member offsets now, not lazily on first
219 * type_size() call - find_member() (used for `.`/`->`
220 * anywhere later in this file) needs real offsets
221 * immediately, and nothing else is guaranteed to trigger
222 * layout before then. */
223 type_size(st);
224 if (tagname[0]) {
225 tag_t *tg = calloc(1, sizeof(tag_t));
226 safe_strcpy(tg->name, tagname, MAX_NAME);
227 tg->type = st;227 tg->type = st;
228 tg->next = p->tags;
229 p->tags = tg;
230 }
231 } else if (existing) {
232 st = existing->type;
233 } else {
234 perr(p, "unknown struct/union tag");
235 return NULL;
236 }
237 named = st;
238 continue;
239 }
240 if (is_kw(p, "enum")) {
241 advance(p);
242 char tagname[MAX_NAME] = {0};
243 if (check(p, TK_IDENT)) { safe_strcpy(tagname, cur(p).text, MAX_NAME); advance(p); }
244 type_t *et;$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "int type_size\|TY_PTR" toolchain/cc/types.c | head -10
19: type_t *t = type_new(TY_PTR);
37: case TY_INT: case TY_ENUM: case TY_PTR: t->align = 4; break;
53:int type_size(type_t *t) {
59: case TY_INT: case TY_ENUM: case TY_PTR: t->size = 4; break;- type_t *st;
- tag_t *existing = tagname[0] ? tag_find(p, tagname) : NULL;
- if (check(p, TK_LBRACE)) {
- advance(p);
- st = type_new(is_union ? TY_UNION : TY_STRUCT);
- safe_strcpy(st->tag, tagname, MAX_NAME);
- st->members = parse_struct_members(p);
- expect(p, TK_RBRACE);
- if (is_kw(p, "__attribute__")) {
- advance(p); expect(p, TK_LPAREN); expect(p, TK_LPAREN);
- if (is_kw(p, "packed")) { advance(p); st->is_packed = 1; }
- expect(p, TK_RPAREN); expect(p, TK_RPAREN);
- }
- /* Compute member offsets now, not lazily on first
- * type_size() call - find_member() (used for `.`/`->`
- * anywhere later in this file) needs real offsets
- * immediately, and nothing else is guaranteed to trigger
- * layout before then. */
- type_size(st);
- if (tagname[0]) {
- tag_t *tg = calloc(1, sizeof(tag_t));
- safe_strcpy(tg->name, tagname, MAX_NAME);
- tg->type = st;
- tg->next = p->tags;
- p->tags = tg;
- }
- } else if (existing) {
- st = existing->type;
- } else {
- perr(p, "unknown struct/union tag");
- return NULL;
- }
- named = st;
- continue;
- }
+ type_t *st;
+ tag_t *existing = tagname[0] ? tag_find(p, tagname) : NULL;
+ if (check(p, TK_LBRACE)) {
+ advance(p);
+ /* Reuse the existing (possibly still-incomplete, from an
+ * earlier forward declaration) type_t instance rather than
+ * allocating a new one, so any typedef/pointer that
+ * already captured its address sees the real members once
+ * this definition completes it - see the forward-decl
+ * branch below. */
+ st = existing ? existing->type : type_new(is_union ? TY_UNION : TY_STRUCT);
+ safe_strcpy(st->tag, tagname, MAX_NAME);
+ /* Register the tag *before* parsing the body, not after -
+ * a self-referential member (`struct node *next;` inside
+ * `struct node { ... }`) needs to find its own tag while
+ * still inside its own member list. */
+ if (tagname[0] && !existing) {
+ tag_t *tg = calloc(1, sizeof(tag_t));
+ safe_strcpy(tg->name, tagname, MAX_NAME);
+ tg->type = st;
+ tg->next = p->tags;
+ p->tags = tg;
+ }
+ st->members = parse_struct_members(p);
+ expect(p, TK_RBRACE);
+ if (is_kw(p, "__attribute__")) {
+ advance(p); expect(p, TK_LPAREN); expect(p, TK_LPAREN);
+ if (is_kw(p, "packed")) { advance(p); st->is_packed = 1; }
+ expect(p, TK_RPAREN); expect(p, TK_RPAREN);
+ }
+ /* Compute member offsets now, not lazily on first
+ * type_size() call - find_member() (used for `.`/`->`
+ * anywhere later in this file) needs real offsets
+ * immediately, and nothing else is guaranteed to trigger
+ * layout before then. */
+ type_size(st);
+ } else if (existing) {
+ st = existing->type; /* complete or still-incomplete - fine as long as only used via pointer */
+ } else if (tagname[0]) {
+ /* Forward declaration (`struct Foo;` or a typedef like
+ * `typedef struct ast_node ast_node_t;` before the real
+ * definition) - register an incomplete tag now so later
+ * pointer-only uses (and the eventual real definition,
+ * via the `existing` branch above) resolve to the same
+ * type_t instance. Only valid as `struct Foo *` until
+ * completed; type_size on a bare incomplete struct/union
+ * would still be meaningless, same as real C. */
+ st = type_new(is_union ? TY_UNION : TY_STRUCT);
+ safe_strcpy(st->tag, tagname, MAX_NAME);
+ tag_t *tg = calloc(1, sizeof(tag_t));
+ safe_strcpy(tg->name, tagname, MAX_NAME);
+ tg->type = st;
+ tg->next = p->tags;
+ p->tags = tg;
+ } else {
+ perr(p, "anonymous struct/union with no body");
+ return NULL;
+ }
+ named = st;
+ continue;
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/parser.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -o build/toolchain/cc toolchain/cc/*.c -Wall 2>&1 | tail -40 && ./tools/run_toolchain_tests.sh 2>&1 | tail -10
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) run_toolchain_tests: PASS
$ cd /home/martin/workspace/prototypes/boot-to-claude
KSRCS="kernel/kmain.c kernel/console.c kernel/serial.c kernel/gdt.c kernel/idt.c kernel/string.c kernel/memory.c kernel/selftest.c kernel/ata.c kernel/fs/tinyfs.c kernel/fs/tinyfs_disk.c kernel/keyboard.c kernel/shell/shell.c kernel/lang/lexer.c kernel/lang/script/ast.c kernel/lang/script/parser.c kernel/lang/script/eval.c kernel/lang/compiler/parser.c kernel/lang/compiler/codegen.c kernel/lang/vm.c"
fail=0
for f in $KSRCS; do
out=$(build/toolchain/cc -Itoolchain/include -Ikernel --parse-only "$f" 2>&1)
rc=$?
if [ $rc -ne 0 ]; then echo "FAIL $f: $out"; fail=1; else echo "OK $f"; fi
done
echo "===="
[ $fail -eq 0 ] && echo "ALL KERNEL C SOURCES PARSE OK" || echo "SOME FAILED"OK kernel/kmain.c OK kernel/console.c OK kernel/serial.c OK kernel/gdt.c OK kernel/idt.c OK kernel/string.c OK kernel/memory.c OK kernel/selftest.c OK kernel/ata.c OK kernel/fs/tinyfs.c OK kernel/fs/tinyfs_disk.c OK kernel/keyboard.c OK kernel/shell/shell.c OK kernel/lang/lexer.c OK kernel/lang/script/ast.c FAIL kernel/lang/script/parser.c: cc: kernel/lang/script/parser.c:131: unexpected token (near '?') FAIL kernel/lang/script/eval.c: cc: kernel/lang/script/eval.c:48: expected expression (near '?') FAIL kernel/lang/compiler/parser.c: cc: kernel/lang/compiler/parser.c:130: unexpected token (near '?') OK kernel/lang/compiler/codegen.c OK kernel/lang/vm.c ==== SOME FAILED
120 if (check(p, TOK_MINUS) || check(p, TOK_NOT)) {
121 token_type_t op = p->cur.type;
122 advance(p);
123 ast_node_t *n = ast_new(AST_UNOP);
124 n->op = op;
125 n->a = parse_unary(p);
126 return n;
127 }
128 return parse_primary(p);
129 }
130
131 static ast_node_t *parse_binop_level(parser_t *p, ast_node_t *(*next)(parser_t *),
132 const token_type_t *ops, int nops) {
133 ast_node_t *left = next(p);
134 for (;;) {
135 int matched = 0;
136 for (int i = 0; i < nops; i++) {
137 if (check(p, ops[i])) {
138 token_type_t op = p->cur.type;
139 advance(p);$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "static type_t \*parse_declarator\|static type_t \*parse_param_list" toolchain/cc/parser.c
355:static type_t *parse_param_list(parser_t *p, char names[][MAX_NAME], int *n_names) {
391:static type_t *parse_declarator(parser_t *p, type_t *base, char *name_out,355 static type_t *parse_param_list(parser_t *p, char names[][MAX_NAME], int *n_names) {
356 type_t *head = NULL, *tail = NULL;
357 int n = 0;
358 if (check(p, TK_RPAREN)) return NULL;
359 if (is_kw(p, "void") && p->toks[p->pos + 1].kind == TK_RPAREN) { advance(p); return NULL; }
360 for (;;) {
361 type_t *base = parse_type_spec(p, NULL, NULL);
362 type_t *pt = base;
363 char nm[MAX_NAME] = {0};
364 while (match(p, TK_STAR)) pt = ptr_to(pt);
365 if (check(p, TK_IDENT)) { safe_strcpy(nm, cur(p).text, MAX_NAME); advance(p); }
366 while (match(p, TK_LBRACKET)) {
367 if (!check(p, TK_RBRACKET)) advance(p);
368 expect(p, TK_RBRACKET);
369 pt = ptr_to(pt); /* array param decays to pointer */
370 }
371 type_t *node = type_new(pt->kind);
372 *node = *pt;
373 node->next = NULL;
374 if (tail) tail->next = node; else head = node;
375 tail = node;
376 if (names && n_names) safe_strcpy(names[n], nm, MAX_NAME);
377 n++;
378 if (!match(p, TK_COMMA)) break;
379 }
380 if (n_names) *n_names = n;
381 return head;
382 }
383
384 /* Parses a declarator (the part after the base type specifier) and
385 * returns the full type, writing the declared name into name_out.
386 * Scoped to what this codebase's declarators actually need (see
387 * docs/toolchain.md): `*`-prefix pointers, `name`, `name[N]`/`name[]`,
388 * `name(params)` (function), and the one parenthesized case,
389 * `(*name)(params)` (function pointer) - not the fully general/
390 * arbitrarily-nested C declarator grammar. */
391 static type_t *parse_declarator(parser_t *p, type_t *base, char *name_out,
392 char param_names[][MAX_NAME], int *n_params) {
393 while (match(p, TK_STAR)) base = ptr_to(base);
394 391 static type_t *parse_declarator(parser_t *p, type_t *base, char *name_out,
392 char param_names[][MAX_NAME], int *n_params) {
393 while (match(p, TK_STAR)) base = ptr_to(base);
394
395 if (match(p, TK_LPAREN)) {
396 expect(p, TK_STAR);
397 token_t id = expect(p, TK_IDENT);
398 safe_strcpy(name_out, id.text, MAX_NAME);
399 expect(p, TK_RPAREN);
400 expect(p, TK_LPAREN);
401 type_t *functy = type_new(TY_FUNC);
402 functy->base = base;
403 functy->param = parse_param_list(p, param_names, n_params);
404 expect(p, TK_RPAREN);
405 return ptr_to(functy);
406 }
407
408 token_t id = expect(p, TK_IDENT);
409 safe_strcpy(name_out, id.text, MAX_NAME);
410
411 if (match(p, TK_LBRACKET)) {
412 int n = -1;
413 if (!check(p, TK_RBRACKET)) n = (int)parse_const_expr(p);
414 expect(p, TK_RBRACKET);
415 type_t *arr = type_new(TY_ARRAY);
416 arr->base = base;
417 arr->array_len = n;
418 return arr;
419 }
420 if (match(p, TK_LPAREN)) {
421 type_t *functy = type_new(TY_FUNC);
422 functy->base = base;
423 functy->param = parse_param_list(p, param_names, n_params);
424 expect(p, TK_RPAREN);
425 return functy;
426 }
427 return base;
428 }
429
430 static type_t *parse_type_name(parser_t *p) {
431 type_t *base = parse_type_spec(p, NULL, NULL);
432 while (match(p, TK_STAR)) base = ptr_to(base);
433 if (match(p, TK_LBRACKET)) {
434 int n = -1;
435 if (!check(p, TK_RBRACKET)) n = (int)parse_const_expr(p);
436 expect(p, TK_RBRACKET);
437 type_t *arr = type_new(TY_ARRAY);
438 arr->base = base;
439 arr->array_len = n;
440 base = arr;
441 }
442 return base;
443 }
444
445 /* ---------------- expressions ---------------- */
446
447 static type_t *g_int_type;
448 static type_t *g_char_type;
449
450 static type_t *decay(type_t *t) {- for (;;) {
- type_t *base = parse_type_spec(p, NULL, NULL);
- type_t *pt = base;
- char nm[MAX_NAME] = {0};
- while (match(p, TK_STAR)) pt = ptr_to(pt);
- if (check(p, TK_IDENT)) { safe_strcpy(nm, cur(p).text, MAX_NAME); advance(p); }
- while (match(p, TK_LBRACKET)) {
- if (!check(p, TK_RBRACKET)) advance(p);
- expect(p, TK_RBRACKET);
- pt = ptr_to(pt); /* array param decays to pointer */
- }
- type_t *node = type_new(pt->kind);
+ for (;;) {
+ type_t *base = parse_type_spec(p, NULL, NULL);
+ type_t *pt = base;
+ char nm[MAX_NAME] = {0};
+ while (match(p, TK_STAR)) pt = ptr_to(pt);
+ if (check(p, TK_LPAREN) && p->toks[p->pos + 1].kind == TK_STAR) {
+ /* function-pointer parameter: TYPE (*name)(PARAMS), e.g.
+ * parse_binop_level's `ast_node_t *(*next)(parser_t *)` -
+ * mirrors parse_declarator's identical form, duplicated here
+ * (rather than delegating to it) because plain parameters in
+ * this loop tolerate an unnamed parameter and parse_declarator
+ * doesn't. */
+ advance(p);
+ expect(p, TK_STAR);
+ if (check(p, TK_IDENT)) { safe_strcpy(nm, cur(p).text, MAX_NAME); advance(p); }
+ expect(p, TK_RPAREN);
+ expect(p, TK_LPAREN);
+ type_t *functy = type_new(TY_FUNC);
+ functy->base = pt;
+ functy->param = parse_param_list(p, NULL, NULL);
+ expect(p, TK_RPAREN);
+ pt = ptr_to(functy);
+ } else {
+ if (check(p, TK_IDENT)) { safe_strcpy(nm, cur(p).text, MAX_NAME); advance(p); }
+ while (match(p, TK_LBRACKET)) {
+ if (!check(p, TK_RBRACKET)) advance(p);
+ expect(p, TK_RBRACKET);
+ pt = ptr_to(pt); /* array param decays to pointer */
+ }
+ }
+ type_t *node = type_new(pt->kind);The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/parser.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -o build/toolchain/cc toolchain/cc/*.c -Wall 2>&1 | tail -40 build/toolchain/cc -Itoolchain/include -Ikernel --parse-only kernel/lang/script/parser.c build/toolchain/cc -Itoolchain/include -Ikernel --parse-only kernel/lang/compiler/parser.c build/toolchain/cc -Itoolchain/include -Ikernel --parse-only kernel/lang/script/eval.c
Exit code 1 cc: parsed kernel/lang/script/parser.c OK cc: parsed kernel/lang/compiler/parser.c OK cc: kernel/lang/script/eval.c:48: expected expression (near '?')
1 #include "eval.h"
2
3 #include "../../console.h"
4 #include "../../fs/tinyfs.h"
5 #include "../../memory.h"
6 #include "../../serial.h"
7 #include "../../string.h"
8 #include "ast.h"
9 #include "parser.h"
10
11 typedef enum { VAL_INT, VAL_STR } value_type_t;
12
13 typedef struct {
14 value_type_t type;
15 union {
16 int32_t i;
17 char *s;
18 } as;
19 } value_t;
20
21 #define MAX_VARS 32
22
23 typedef struct {
24 char names[MAX_VARS][TOKEN_TEXT_MAX];
25 value_t values[MAX_VARS];
26 int count;
27 } env_t;
28
29 #define MAX_FUNCS 16
30
31 typedef struct {
32 char name[TOKEN_TEXT_MAX];
33 ast_node_t *def;
34 } func_entry_t;
35
36 typedef struct {
37 func_entry_t funcs[MAX_FUNCS];
38 int func_count;
39 } interp_t;
40
41 typedef enum { EXEC_NORMAL, EXEC_RETURN } exec_signal_t;
42
43 typedef struct {
44 exec_signal_t signal;
45 value_t return_value;
46 } exec_result_t;
47
48 static const value_t VAL_ZERO = { VAL_INT, { .i = 0 } };
49
50 /* Constructors used in place of compound literals (`(value_t){...}`) -
51 * kept out of tinyC's dialect deliberately (see docs/toolchain.md): the
52 * feature was used in ~22 places in this file alone and implementing it
53 * in CC would mean parsing/lowering anonymous-temporary initializer
54 * lists for one file's convenience. Plain aggregate initialization of a
55 * file-scope variable (VAL_ZERO above) is a different, more basic55 * file-scope variable (VAL_ZERO above) is a different, more basic
56 * feature and stays as-is - CC supports brace-init for globals/statics
57 * directly. */
58 static value_t mkint(int32_t i) {
59 value_t v;
60 v.type = VAL_INT;
61 v.as.i = i;
62 return v;
63 }
64
65 static value_t mkstr(char *s) {
66 value_t v;
67 v.type = VAL_STR;
68 v.as.s = s;
69 return v;$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "parse_global_var\|TK_DOT.*init\|designat" toolchain/cc/parser.c | head -10
916:static node_t *parse_global_var(parser_t *p, const char *name, type_t *t, int is_extern) {
1012: decl = parse_global_var(&p, name, t, is_extern);
1016: node_t *more = parse_global_var(&p, name2, t2, is_extern);916 static node_t *parse_global_var(parser_t *p, const char *name, type_t *t, int is_extern) {
917 add_global(p, name, t);
918 node_t *n = node_new(ND_VAR_DECL);
919 safe_strcpy(n->name, name, MAX_NAME);
920 n->type = t;
921 n->is_global = 1;
922 n->is_extern_decl = is_extern;
923 safe_strcpy(n->asm_label, name, MAX_NAME);
924 if (match(p, TK_ASSIGN)) n->init = parse_initializer(p);
925 return n;
926 }
927
928 static node_t *parse_func(parser_t *p, const char *name, type_t *functy,
929 char param_names[][MAX_NAME], int n_params, int is_static) {
930 sym_t *fs = add_global(p, name, functy);
931
932 if (match(p, TK_SEMI)) return NULL; /* prototype only */
933
934 p->locals = NULL;
935 p->frame_size = 0;
936 p->cur_func_ret = functy->base;
937
938 node_t *n = node_new(ND_FUNC_DEF);
939 safe_strcpy(n->name, name, MAX_NAME);
940 n->type = functy->base;
941 n->is_global = !is_static; /* controls whether codegen emits `global` - C's static limits linkage */
942 safe_strcpy(n->asm_label, name, MAX_NAME);
943
944 /* cdecl: EBP+8, +12, ... for params, in order */
945 type_t *pt = functy->param;
946 int off = 8;
947 node_t *ptail = NULL;
948 for (int i = 0; i < n_params && pt; i++, pt = pt->next) {
949 sym_t *s = calloc(1, sizeof(sym_t));
950 safe_strcpy(s->name, param_names[i], MAX_NAME);
951 s->type = pt;
952 s->frame_offset = off;
953 s->next = p->locals;
954 p->locals = s;
955 off += 4;$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "static node_t \*parse_initializer" toolchain/cc/parser.c
760:static node_t *parse_initializer(parser_t *p) {745 n->a = lhs; n->b = rhs; n->type = lhs->type;
746 return n;
747 }
748 return lhs;
749 }
750
751 static node_t *parse_expr(parser_t *p) { return parse_assign(p); }
752
753 /* ---------------- initializers ---------------- */
754
755 /* `{ v1, v2, ... }` or a single scalar expression. Brace lists are only
756 * meaningful for globals/statics (emitted directly as .data bytes) -
757 * this codebase never initializes a plain (stack) local array/struct
758 * with a brace list, only scalars, so codegen never needs to lower a
759 * brace list into runtime store instructions. */
760 static node_t *parse_initializer(parser_t *p) {
761 if (check(p, TK_LBRACE)) {
762 advance(p);
763 node_t *list = node_new(ND_BLOCK); /* reused as a plain node chain holder */
764 node_t *tail = NULL;760 static node_t *parse_initializer(parser_t *p) {
761 if (check(p, TK_LBRACE)) {
762 advance(p);
763 node_t *list = node_new(ND_BLOCK); /* reused as a plain node chain holder */
764 node_t *tail = NULL;
765 while (!check(p, TK_RBRACE)) {
766 node_t *v = parse_initializer(p);
767 if (!list->list) list->list = v; else tail->next = v;
768 tail = v;
769 if (!match(p, TK_COMMA)) break;
770 }
771 expect(p, TK_RBRACE);
772 return list;
773 }
774 return parse_assign(p);
775 }
776
777 /* ---------------- statements ---------------- */
778
779 static node_t *parse_one_local_decl(parser_t *p, type_t *base, int is_static) {
780 char name[MAX_NAME];
781 type_t *t = parse_declarator(p, base, name, NULL, NULL);
782 sym_t *s = add_local(p, name, t, is_static);
783 node_t *n = node_new(ND_VAR_DECL);
784 safe_strcpy(n->name, name, MAX_NAME);
785 n->type = t;
786 n->is_static_local = is_static;
787 safe_strcpy(n->asm_label, s->asm_label, MAX_NAME);
788 n->frame_offset = s->frame_offset;
789 if (match(p, TK_ASSIGN)) n->init = parse_initializer(p);$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "VAL_ZERO" kernel/lang/script/*.c kernel/lang/script/*.h
kernel/lang/script/eval.c:48:static const value_t VAL_ZERO = { VAL_INT, { .i = 0 } };
kernel/lang/script/eval.c:55: * file-scope variable (VAL_ZERO above) is a different, more basic
kernel/lang/script/eval.c:83: return i < 0 ? VAL_ZERO : env->values[i];
kernel/lang/script/eval.c:165: default: return VAL_ZERO;
kernel/lang/script/eval.c:196: exec_result_t r = { EXEC_NORMAL, VAL_ZERO };
kernel/lang/script/eval.c:223: return VAL_ZERO;
kernel/lang/script/eval.c:259: return VAL_ZERO; /* unknown function - tiny scope, no error propagation */
kernel/lang/script/eval.c:266: env_set(&local, pn->name, i < argc ? args[i] : VAL_ZERO);
kernel/lang/script/eval.c:270: return r.signal == EXEC_RETURN ? r.return_value : VAL_ZERO;
kernel/lang/script/eval.c:297: return VAL_ZERO;
kernel/lang/script/eval.c:317: return VAL_ZERO;
kernel/lang/script/eval.c:322: exec_result_t normal = { EXEC_NORMAL, VAL_ZERO };
kernel/lang/script/eval.c:351: exec_result_t r = { EXEC_RETURN, VAL_ZERO };- static const value_t VAL_ZERO = { VAL_INT, { .i = 0 } };
+ /* Positional, not `{ .i = 0 }` - CC's initializer parser doesn't support
+ * designated initializers (a real gap, but a one-site tweak here is
+ * simpler than adding the feature - see docs/toolchain.md). Equivalent:
+ * `i` is the union's first member, and both members are zeroed either
+ * way since nothing here ever reads VAL_ZERO through `.s`. */
+ static const value_t VAL_ZERO = { VAL_INT, { 0 } };The file /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/script/eval.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude
KSRCS="kernel/kmain.c kernel/console.c kernel/serial.c kernel/gdt.c kernel/idt.c kernel/string.c kernel/memory.c kernel/selftest.c kernel/ata.c kernel/fs/tinyfs.c kernel/fs/tinyfs_disk.c kernel/keyboard.c kernel/shell/shell.c kernel/lang/lexer.c kernel/lang/script/ast.c kernel/lang/script/parser.c kernel/lang/script/eval.c kernel/lang/compiler/parser.c kernel/lang/compiler/codegen.c kernel/lang/vm.c"
fail=0
for f in $KSRCS; do
out=$(build/toolchain/cc -Itoolchain/include -Ikernel --parse-only "$f" 2>&1)
rc=$?
if [ $rc -ne 0 ]; then echo "FAIL $f: $out"; fail=1; else echo "OK $f"; fi
done
echo "===="
[ $fail -eq 0 ] && echo "ALL KERNEL C SOURCES PARSE OK" || echo "SOME FAILED"
echo "---tools---"
build/toolchain/cc -Itoolchain/include -Ikernel -Itools --parse-only tools/mkfs_tinyfs.cOK kernel/kmain.c OK kernel/console.c OK kernel/serial.c OK kernel/gdt.c OK kernel/idt.c OK kernel/string.c OK kernel/memory.c OK kernel/selftest.c OK kernel/ata.c OK kernel/fs/tinyfs.c OK kernel/fs/tinyfs_disk.c OK kernel/keyboard.c OK kernel/shell/shell.c OK kernel/lang/lexer.c OK kernel/lang/script/ast.c OK kernel/lang/script/parser.c FAIL kernel/lang/script/eval.c: cc: kernel/lang/script/eval.c:270: unexpected token (near '?') OK kernel/lang/compiler/parser.c OK kernel/lang/compiler/codegen.c OK kernel/lang/vm.c ==== SOME FAILED ---tools--- cc: parsed tools/mkfs_tinyfs.c OK
$ cd /home/martin/workspace/prototypes/boot-to-claude wc -l kernel/lang/script/eval.c for n in 230 240 250 255 260 265 268 270; do head -n $n kernel/lang/script/eval.c > kernel/lang/script/eval_trunc.c echo "---- $n lines ----" build/toolchain/cc -Itoolchain/include -Ikernel --parse-only kernel/lang/script/eval_trunc.c 2>&1 | tail -2 done rm -f kernel/lang/script/eval_trunc.c
403 kernel/lang/script/eval.c ---- 230 lines ---- cc: ?:0: expected expression (near '?') ---- 240 lines ---- cc: ?:0: expected expression (near '?') ---- 250 lines ---- cc: ?:0: expected expression (near '?') ---- 255 lines ---- cc: ?:0: expected expression (near '?') ---- 260 lines ---- cc: ?:0: expected expression (near '?') ---- 265 lines ---- cc: ?:0: expected expression (near '?') ---- 268 lines ---- cc: ?:0: expected expression (near '?') ---- 270 lines ---- cc: kernel/lang/script/eval_trunc.c:270: unexpected token (near '?')
225 }
226 console_write("\n");
227 serial_write("\n");
228 return VAL_ZERO;
229 }
230
231 if (strcmp(node->name, "fread") == 0 && argc >= 1 && args[0].type == VAL_STR) {
232 static char buf[TINYFS_MAX_FILE_SIZE + 1];
233 uint32_t len = 0;
234 if (tinyfs_read(args[0].as.s, buf, sizeof(buf) - 1, &len) == 0) {
235 buf[len] = '\0';
236 char *out = kmalloc(len + 1);
237 memcpy(out, buf, len + 1);
238 return mkstr(out);
239 }
240 return mkstr("");
241 }
242
243 if (strcmp(node->name, "fwrite") == 0 && argc >= 2 && args[0].type == VAL_STR) {
244 const char *content = (args[1].type == VAL_STR) ? args[1].as.s : "";
245 tinyfs_create(args[0].as.s); /* ignored if it already exists */
246 int rc = tinyfs_write(args[0].as.s, content, (uint32_t)strlen(content));
247 return mkint(rc == 0);
248 }
249
250 if (strcmp(node->name, "flist") == 0) {
251 tinyfs_dirent_t entries[TINYFS_NUM_INODES];
252 int n = tinyfs_list(entries, TINYFS_NUM_INODES);
253 for (int i = 0; i < n; i++) {
254 console_write(entries[i].name);
255 console_write("\n");
256 serial_write(entries[i].name);
257 serial_write("\n");
258 }
259 return mkint(n);
260 }
261
262 ast_node_t *def = find_func(interp, node->name);
263 if (!def) {
264 return VAL_ZERO; /* unknown function - tiny scope, no error propagation */
265 }
266
267 env_t local;
268 local.count = 0;
269 int i = 0;
270 for (ast_node_t *pn = def->list; pn; pn = pn->next, i++) {
271 env_set(&local, pn->name, i < argc ? args[i] : VAL_ZERO);
272 }
273
274 exec_result_t r = exec_block(interp, &local, def->b);$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "ND_COMMA\|TK_COMMA" toolchain/cc/parser.c | head -20
287: if (!match(p, TK_COMMA)) break;
348: if (!match(p, TK_COMMA)) break;
397: if (!match(p, TK_COMMA)) break;
528: if (!match(p, TK_COMMA)) break;
769: if (!match(p, TK_COMMA)) break;
798: while (match(p, TK_COMMA)) {
1013: while (match(&p, TK_COMMA)) {$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "ND_COMMA" toolchain/cc/codegen.c
(Bash completed with no output)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "parse_assign(p)\|parse_expr(p)" toolchain/cc/parser.c | head -40
163: return eval_const_node(p, parse_assign(p));
525: node_t *arg = parse_assign(p);
556: node_t *n = parse_expr(p);
568: node_t *idx = parse_expr(p);
719: t->b = parse_expr(p);
721: t->c = parse_assign(p);
737: node_t *rhs = parse_assign(p);
751:static node_t *parse_expr(parser_t *p) { return parse_assign(p); }
774: return parse_assign(p);
813: n->a = parse_expr(p);
822: n->a = parse_expr(p);
832: n->a = parse_expr(p);
842: if (!n->a) { n->a = parse_expr(p); expect(p, TK_SEMI); }
844: if (!check(p, TK_SEMI)) n->b = parse_expr(p);
846: if (!check(p, TK_RPAREN)) n->c = parse_expr(p);
853: if (!check(p, TK_SEMI)) n->a = parse_expr(p);
862: n->a = parse_expr(p);
895: n->a = parse_expr(p);- static node_t *parse_expr(parser_t *p) { return parse_assign(p); }
+ /* The comma *operator* (evaluate left, discard, evaluate right - e.g. a
+ * for-loop increment clause like `pn = pn->next, i++`), distinct from
+ * comma as a plain separator (call args, declarator lists, initializer
+ * elements) - those call parse_assign directly for each element, so
+ * this only ever fires where a full expression was asked for. */
+ static node_t *parse_expr(parser_t *p) {
+ node_t *n = parse_assign(p);
+ while (match(p, TK_COMMA)) {
+ node_t *rhs = parse_assign(p);
+ node_t *c = node_new(ND_COMMA);
+ c->a = n;
+ c->b = rhs;
+ c->type = rhs->type;
+ n = c;
+ }
+ return n;
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/parser.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "case ND_MEMBER: case ND_ARROW:" toolchain/cc/codegen.c
287: case ND_MEMBER: case ND_ARROW:
276 case ND_TERNARY: {
277 char lfalse[MAX_NAME], lend[MAX_NAME];
278 new_label(lfalse); new_label(lend);
279 codegen_expr(n->a);
280 out(" cmp eax, 0\n je %s\n", lfalse);
281 codegen_expr(n->b);
282 out(" jmp %s\n%s:\n", lend, lfalse);
283 codegen_expr(n->c);
284 out("%s:\n", lend);
285 return;
286 }
287 case ND_MEMBER: case ND_ARROW:
288 codegen_addr(n);
289 load_value(n->type);
290 return;
291 default:
292 fprintf(stderr, "cc: internal error: unsupported expression kind %d\n", n->kind);
293 exit(1);- case ND_MEMBER: case ND_ARROW: - codegen_addr(n); - load_value(n->type); - return; + case ND_MEMBER: case ND_ARROW: + codegen_addr(n); + load_value(n->type); + return; + case ND_COMMA: + /* Evaluate and discard the left operand, then evaluate the + * right - both land in eax by convention, so evaluating `b` + * second naturally overwrites `a`'s (unused) result. */ + codegen_expr(n->a); + codegen_expr(n->b); + return;
The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -o build/toolchain/cc toolchain/cc/*.c -Wall 2>&1 | tail -40 && ./tools/run_toolchain_tests.sh 2>&1 | tail -10
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) run_toolchain_tests: PASS
$ cd /home/martin/workspace/prototypes/boot-to-claude
KSRCS="kernel/kmain.c kernel/console.c kernel/serial.c kernel/gdt.c kernel/idt.c kernel/string.c kernel/memory.c kernel/selftest.c kernel/ata.c kernel/fs/tinyfs.c kernel/fs/tinyfs_disk.c kernel/keyboard.c kernel/shell/shell.c kernel/lang/lexer.c kernel/lang/script/ast.c kernel/lang/script/parser.c kernel/lang/script/eval.c kernel/lang/compiler/parser.c kernel/lang/compiler/codegen.c kernel/lang/vm.c"
fail=0
for f in $KSRCS; do
out=$(build/toolchain/cc -Itoolchain/include -Ikernel --parse-only "$f" 2>&1)
rc=$?
if [ $rc -ne 0 ]; then echo "FAIL $f: $out"; fail=1; else echo "OK $f"; fi
done
build/toolchain/cc -Itoolchain/include -Ikernel -Itools --parse-only tools/mkfs_tinyfs.c
build/toolchain/cc -Itoolchain/include -Ikernel --parse-only kernel/entry.S 2>&1 | head -3
echo "===="
[ $fail -eq 0 ] && echo "ALL KERNEL C SOURCES PARSE OK" || echo "SOME FAILED"OK kernel/kmain.c OK kernel/console.c OK kernel/serial.c OK kernel/gdt.c OK kernel/idt.c OK kernel/string.c OK kernel/memory.c OK kernel/selftest.c OK kernel/ata.c OK kernel/fs/tinyfs.c OK kernel/fs/tinyfs_disk.c OK kernel/keyboard.c OK kernel/shell/shell.c OK kernel/lang/lexer.c OK kernel/lang/script/ast.c OK kernel/lang/script/parser.c OK kernel/lang/script/eval.c OK kernel/lang/compiler/parser.c OK kernel/lang/compiler/codegen.c OK kernel/lang/vm.c cc: parsed tools/mkfs_tinyfs.c OK cc: kernel/entry.S:1: unexpected token (near 'entry') ==== ALL KERNEL C SOURCES PARSE OK
$ cd /home/martin/workspace/prototypes/boot-to-claude ls tests/toolchain/ cat tests/toolchain/t05_structs.c
t01_basic.c
t02_control.c
t02_types.c
t03_recursion.c
t04_pointers.c
t05_structs.c
typedef struct {
int x;
int y;
} point_t;
typedef struct {
char used;
int size;
} __attribute__((packed)) packed_t;
int dist_manhattan(point_t *a, point_t *b) {
int dx = a->x - b->x;
int dy = a->y - b->y;
if (dx < 0) dx = -dx;
if (dy < 0) dy = -dy;
return dx + dy;
}
static point_t g_origin;
int main(void) {
point_t p1;
p1.x = 3;
p1.y = 4;
point_t p2;
p2.x = 0;
p2.y = 0;
g_origin.x = 1;
g_origin.y = 1;
int d1 = dist_manhattan(&p1, &p2); /* 3+4=7 */
int d2 = dist_manhattan(&p1, &g_origin); /* 2+3=5 */
packed_t pk;
pk.used = 1;
pk.size = 100;
int psize = sizeof(packed_t); /* packed: 1 + 4 = 5 bytes, no padding */
return d1 + d2 + psize - pk.size + 100; /* 7+5+5-100+100 = 17 */
}$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "static node_t \*parse_local_decl_stmt" toolchain/cc/parser.c
809:static node_t *parse_local_decl_stmt(parser_t *p) {809 static node_t *parse_local_decl_stmt(parser_t *p) {
810 int is_static = 0, is_extern = 0;
811 type_t *base = parse_type_spec(p, &is_static, &is_extern);
812 node_t *head = parse_one_local_decl(p, base, is_static);
813 node_t *tail = head;
814 while (match(p, TK_COMMA)) {
815 node_t *n = parse_one_local_decl(p, base, is_static);
816 tail->next = n;
817 tail = n;
818 }
819 expect(p, TK_SEMI);
820 return head;
821 }
822
823 static node_t *parse_stmt(parser_t *p) {int classify(int x) {
int r = 0;
switch (x) {
case 1:
case 2:
r = 10;
break;
case 3:
r = 20;
/* fallthrough */
case 4:
r = r + 1;
break;
default:
r = 99;
break;
}
return r;
}
int loop_with_switch(void) {
int total = 0;
for (int i = 0; i < 5; i++) {
switch (i) {
case 2:
continue; /* continues the enclosing for loop, not the switch */
case 4:
break; /* breaks only the switch - falls through to total += i below */
}
total += i;
}
return total;
}
int main(void) {
int a = classify(1); /* case 1 falls into case 2's body -> 10 */
int b = classify(3); /* r=20, falls through into case 4 -> 21 */
int c = classify(4); /* r=0+1 -> 1 */
int d = classify(100); /* no match -> default -> 99 */
int e = loop_with_switch(); /* 0+1+3+4 (i=2 skipped via continue) -> 8 */
return a + b + c + d + e; /* 10+21+1+99+8 = 139 */
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/tests/toolchain/t06_switch.c (file state is current in your context — no need to Read it back)
int main(void) {
int sum = 0;
int i, j;
for (i = 0, j = 10; i < 5; i++, j--) {
sum += i + j;
}
/* pairs (0,10)(1,9)(2,8)(3,7)(4,6), each sums to 10 -> 50 */
int x = (1, 2, 3); /* comma operator: discards 1 and 2, x = 3 */
return sum + x; /* 50 + 3 = 53 */
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/tests/toolchain/t07_comma.c (file state is current in your context — no need to Read it back)
/* Forward-declared tag (typedef before the real definition) plus a
* self-referential member inside that definition - the pattern
* kernel/lang/script/ast.h uses for its AST node type. */
typedef struct node node_t2;
struct node {
int value;
struct node *next;
};
int sum_list(node_t2 *head) {
int total = 0;
for (node_t2 *n = head; n; n = n->next) total += n->value;
return total;
}
int main(void) {
struct node c, b, a;
c.value = 3; c.next = 0;
b.value = 2; b.next = &c;
a.value = 1; a.next = &b;
return sum_list(&a); /* 1+2+3 = 6 */
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/tests/toolchain/t08_forward_struct.c (file state is current in your context — no need to Read it back)
27 fi 28 29 declare -A EXPECTED=( 30 [t01_basic]=5 31 [t02_control]=7 32 [t02_types]=42 33 [t03_recursion]=55 34 [t04_pointers]=31 35 [t05_structs]=17 36 )
- [t04_pointers]=31 - [t05_structs]=17 - ) + [t04_pointers]=31 + [t05_structs]=17 + [t06_switch]=139 + [t07_comma]=53 + [t08_forward_struct]=6 + )
The file /home/martin/workspace/prototypes/boot-to-claude/tools/run_toolchain_tests.sh has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude ./tools/run_toolchain_tests.sh 2>&1 | tail -15
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) run_toolchain_tests: PASS
1 #!/usr/bin/env bash
2 # Compiles each tests/toolchain/*.c fixture through CC (--target=host) +
3 # AS, links with GNU ld (interim - LD doesn't exist until Phase T5), runs
4 # it directly, and checks the exit code. Fast host-side iteration for the
5 # compiler; kernel-target builtins (port I/O etc) are verified separately
6 # via a QEMU boot, since they can't run on the host.
7 set -euo pipefail
8
9 cd "$(dirname "$0")/.."
10
11 CC=${CC_BIN:-build/toolchain/cc}
12 AS=${AS_BIN:-build/toolchain/as}
13 BUILD=build/toolchain_tests
14 mkdir -p "$BUILD" build/toolchain
15
16 # Not wired into the main Makefile yet (that's Phase T7) - bootstrap with
17 # the host compiler if the tools haven't been built already.
18 if [ ! -x "$CC" ]; then
19 gcc -Wall -Wextra -O2 -o "$CC" \
20 toolchain/cc/lexer.c toolchain/cc/preprocessor.c toolchain/cc/types.c \
21 toolchain/cc/parser.c toolchain/cc/codegen.c toolchain/cc/builtins.c toolchain/cc/main.c
22 fi
23 if [ ! -x "$AS" ]; then
24 gcc -Wall -Wextra -O2 -o "$AS" \
25 toolchain/as/lexer.c toolchain/as/parser.c toolchain/as/encode.c \
26 toolchain/as/bin_writer.c toolchain/as/elf_writer.c toolchain/as/main.c
27 fi
28
29 declare -A EXPECTED=(
30 [t01_basic]=5
31 [t02_control]=7
32 [t02_types]=42
33 [t03_recursion]=55
34 [t04_pointers]=31
35 [t05_structs]=17
36 [t06_switch]=139
37 [t07_comma]=53
38 [t08_forward_struct]=6
39 )
40
41 fail=0
42 for src in tests/toolchain/*.c; do
43 name=$(basename "$src" .c)
44 exp=${EXPECTED[$name]:-}
45 if [ -z "$exp" ]; then
46 echo "SKIP $name (no expected value registered)"
47 continue
48 fi
49
50 "$CC" -I toolchain/include --target=host -o "$BUILD/$name.S" "$src"
51 "$AS" -f elf32 -o "$BUILD/$name.o" "$BUILD/$name.S"
52 ld -m elf_i386 -o "$BUILD/$name" "$BUILD/$name.o"
53
54 set +e
55 "$BUILD/$name"
56 got=$?
57 set -e
58
59 if [ "$got" -eq "$exp" ]; then
60 echo "PASS $name ($got)"
61 else
62 echo "FAIL $name: expected $exp, got $got"
63 fail=1
64 fi
65 done
66
67 if [ "$fail" -eq 0 ]; then
68 echo "run_toolchain_tests: PASS"
69 else
70 echo "run_toolchain_tests: FAIL"
71 exit 1
72 fi
73 - # Compiles each tests/toolchain/*.c fixture through CC (--target=host) +
- # AS, links with GNU ld (interim - LD doesn't exist until Phase T5), runs
- # it directly, and checks the exit code. Fast host-side iteration for the
- # compiler; kernel-target builtins (port I/O etc) are verified separately
- # via a QEMU boot, since they can't run on the host.
- set -euo pipefail
-
- cd "$(dirname "$0")/.."
-
- CC=${CC_BIN:-build/toolchain/cc}
- AS=${AS_BIN:-build/toolchain/as}
- BUILD=build/toolchain_tests
- mkdir -p "$BUILD" build/toolchain
-
- # Not wired into the main Makefile yet (that's Phase T7) - bootstrap with
- # the host compiler if the tools haven't been built already.
- if [ ! -x "$CC" ]; then
- gcc -Wall -Wextra -O2 -o "$CC" \
- toolchain/cc/lexer.c toolchain/cc/preprocessor.c toolchain/cc/types.c \
- toolchain/cc/parser.c toolchain/cc/codegen.c toolchain/cc/builtins.c toolchain/cc/main.c
- fi
- if [ ! -x "$AS" ]; then
- gcc -Wall -Wextra -O2 -o "$AS" \
- toolchain/as/lexer.c toolchain/as/parser.c toolchain/as/encode.c \
- toolchain/as/bin_writer.c toolchain/as/elf_writer.c toolchain/as/main.c
- fi
+ # Compiles each tests/toolchain/*.c fixture through CC (--target=host) +
+ # AS + LD (all our own tools as of Phase T5/T7), runs it directly, and
+ # checks the exit code. Fast host-side iteration for the compiler;
+ # kernel-target builtins (port I/O etc) are verified separately via a
+ # QEMU boot, since they can't run on the host.
+ set -euo pipefail
+
+ cd "$(dirname "$0")/.."
+
+ CC=${CC_BIN:-build/toolchain/cc}
+ AS=${AS_BIN:-build/toolchain/as}
+ LD=${LD_BIN:-build/toolchain/ld}
+ BUILD=build/toolchain_tests
+ mkdir -p "$BUILD" build/toolchain
+
+ # Not wired into the main Makefile yet - bootstrap with the host compiler
+ # if the tools haven't been built already (see docs/toolchain.md on the
+ # one-time host-gcc bootstrap dependency).
+ if [ ! -x "$CC" ]; then
+ gcc -Wall -Wextra -O2 -o "$CC" toolchain/cc/*.c
+ fi
+ if [ ! -x "$AS" ]; then
+ gcc -Wall -Wextra -O2 -o "$AS" toolchain/as/*.c
+ fi
+ if [ ! -x "$LD" ]; then
+ gcc -Wall -Wextra -O2 -o "$LD" toolchain/ld/*.c
+ fiThe file /home/martin/workspace/prototypes/boot-to-claude/tools/run_toolchain_tests.sh has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude ls toolchain/cc/*.c toolchain/as/*.c toolchain/ld/*.c
toolchain/as/bin_writer.c toolchain/as/elf_writer.c toolchain/as/encode.c toolchain/as/lexer.c toolchain/as/main.c toolchain/as/parser.c toolchain/cc/builtins.c toolchain/cc/codegen.c toolchain/cc/lexer.c toolchain/cc/main.c toolchain/cc/parser.c toolchain/cc/preprocessor.c toolchain/cc/types.c toolchain/ld/elf_reader.c toolchain/ld/link_flat.c toolchain/ld/link_static_exec.c toolchain/ld/main.c
- "$CC" -I toolchain/include --target=host -o "$BUILD/$name.S" "$src" - "$AS" -f elf32 -o "$BUILD/$name.o" "$BUILD/$name.S" - ld -m elf_i386 -o "$BUILD/$name" "$BUILD/$name.o" + "$CC" -I toolchain/include --target=host -o "$BUILD/$name.S" "$src" + "$AS" -f elf32 -o "$BUILD/$name.o" "$BUILD/$name.S" + "$LD" -o "$BUILD/$name" "$BUILD/$name.o"
The file /home/martin/workspace/prototypes/boot-to-claude/tools/run_toolchain_tests.sh has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude ./tools/run_toolchain_tests.sh 2>&1 | tail -15
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) run_toolchain_tests: PASS
1 AS := nasm 2 CC := gcc 3 HOSTCC := gcc 4 LD := ld 5 OBJCOPY := objcopy 6 QEMU := qemu-system-i386 7 BUILD := build 8 9 CFLAGS := -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib \ 10 -Wall -Wextra -O2 -std=c11 -Ikernel 11 LDFLAGS := -m elf_i386 -T kernel/link.ld -nostdlib 12 13 # Must match TINYFS_DISK_SECTORS / TINYFS_FS_START_LBA in kernel/fs/tinyfs.h 14 # and KERNEL_START_LBA / KERNEL_SECTORS in boot/stage2.S 15 DISK_SECTORS := 4096 16 KERNEL_START_SECTOR := 5 17 KERNEL_MAX_SECTORS := 512 18 19 BOOT_BIN := $(BUILD)/boot.bin 20 STAGE2_BIN := $(BUILD)/stage2.bin 21 DISK_IMG := $(BUILD)/disk.img 22 23 KERNEL_C_SRCS := kernel/kmain.c kernel/console.c kernel/serial.c kernel/gdt.c \ 24 kernel/idt.c kernel/string.c kernel/memory.c kernel/selftest.c \ 25 kernel/ata.c kernel/fs/tinyfs.c kernel/fs/tinyfs_disk.c \ 26 kernel/keyboard.c kernel/shell/shell.c kernel/builtins_shim.c \ 27 kernel/lang/lexer.c kernel/lang/script/ast.c \ 28 kernel/lang/script/parser.c kernel/lang/script/eval.c \ 29 kernel/lang/compiler/parser.c kernel/lang/compiler/codegen.c \ 30 kernel/lang/vm.c 31 KERNEL_ASM_SRCS := kernel/entry.S kernel/idt_stubs.S 32 KERNEL_OBJS := $(patsubst kernel/%.S,$(BUILD)/%.o,$(KERNEL_ASM_SRCS)) \ 33 $(patsubst kernel/%.c,$(BUILD)/%.o,$(KERNEL_C_SRCS)) 34 KERNEL_ELF := $(BUILD)/kernel.elf 35 KERNEL_BIN := $(BUILD)/kernel.bin 36 37 HOSTCFLAGS := -m32 -Wall -Wextra -O2 -Ikernel 38 39 MKFS_BIN := $(BUILD)/mkfs_tinyfs 40 # host_string.o: kernel/string.c compiled natively - host tools are 41 # libc-free (see docs/toolchain.md), same as the kernel itself. 42 # host_syscalls_shim.o: real int 0x80 bodies for the interim GCC build 43 # only, dropped once Phase T7 switches to CC (see tools/host_syscalls.h). 44 MKFS_OBJS := $(BUILD)/host_tinyfs.o $(BUILD)/host_string.o \ 45 $(BUILD)/host_syscalls_shim.o $(BUILD)/host_mkfs_tinyfs.o 46 # host-path:fs-name pairs seeded into every disk image; the host paths 47 # alone are listed separately so they can be used as make prerequisites 48 MKFS_SEED_FILES := tests/hello.tsh tests/hello.tc 49 MKFS_SEED_SPECS := tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc 50 51 .PHONY: all run run-gui clean 52 53 all: $(DISK_IMG) 54 55 BUILD_SUBDIRS := $(BUILD) $(BUILD)/fs $(BUILD)/shell $(BUILD)/lang $(BUILD)/lang/script $(BUILD)/lang/compiler 56 57 $(BUILD_SUBDIRS): 58 mkdir -p $@ 59 60 $(BOOT_BIN): boot/boot.S | $(BUILD) 61 $(AS) -f bin -o $@ $< 62 63 $(STAGE2_BIN): boot/stage2.S | $(BUILD) 64 $(AS) -f bin -o $@ $< 65 66 $(BUILD)/%.o: kernel/%.S | $(BUILD_SUBDIRS) 67 $(AS) -f elf32 -o $@ $< 68 69 $(BUILD)/%.o: kernel/%.c | $(BUILD_SUBDIRS) 70 $(CC) $(CFLAGS) -c -o $@ $< 71 72 $(KERNEL_ELF): $(KERNEL_OBJS) kernel/link.ld 73 $(LD) $(LDFLAGS) -o $@ $(KERNEL_OBJS) 74 75 $(KERNEL_BIN): $(KERNEL_ELF) 76 $(OBJCOPY) -O binary $< $@ 77 @size=$$(stat -c%s $@); budget=$$(( $(KERNEL_MAX_SECTORS) * 512 )); \ 78 if [ "$$size" -gt "$$budget" ]; then \ 79 echo "error: $@ is $$size bytes, exceeds the $$budget-byte ($(KERNEL_MAX_SECTORS)-sector) boot budget" >&2; \ 80 echo " bump KERNEL_SECTORS in boot/stage2.S, KERNEL_MAX_SECTORS here, and TINYFS_FS_START_LBA in kernel/fs/tinyfs.h together" >&2; \ 81 exit 1; \ 82 fi 83 84 # --- host-side mkfs tool (build-time infrastructure, not kernel code) --- 85 # -m32: matches CC's x86-32-only codegen, so tools/host_syscalls_shim.c's 86 # int 0x80 convention is correct and this build stays consistent with 87 # what Phase T7 will produce. 88 89 $(BUILD)/host_tinyfs.o: kernel/fs/tinyfs.c | $(BUILD) 90 $(HOSTCC) $(HOSTCFLAGS) -c -o $@ $< 91 92 $(BUILD)/host_string.o: kernel/string.c | $(BUILD) 93 $(HOSTCC) $(HOSTCFLAGS) -c -o $@ $< 94 95 $(BUILD)/host_syscalls_shim.o: tools/host_syscalls_shim.c | $(BUILD) 96 $(HOSTCC) $(HOSTCFLAGS) -c -o $@ $< 97 98 $(BUILD)/host_mkfs_tinyfs.o: tools/mkfs_tinyfs.c | $(BUILD) 99 $(HOSTCC) $(HOSTCFLAGS) -c -o $@ $< 100 101 $(MKFS_BIN): $(MKFS_OBJS) 102 $(HOSTCC) -m32 -o $@ $(MKFS_OBJS) 103 104 $(DISK_IMG): $(BOOT_BIN) $(STAGE2_BIN) $(KERNEL_BIN) $(MKFS_BIN) $(MKFS_SEED_FILES) 105 dd if=/dev/zero of=$@ bs=512 count=$(DISK_SECTORS) status=none 106 dd if=$(BOOT_BIN) of=$@ conv=notrunc status=none 107 dd if=$(STAGE2_BIN) of=$@ bs=512 seek=1 conv=notrunc status=none 108 dd if=$(KERNEL_BIN) of=$@ bs=512 seek=$(KERNEL_START_SECTOR) conv=notrunc status=none 109 $(MKFS_BIN) $@ $(MKFS_SEED_SPECS) 110 111 # Headless: no window at all, so there's no input surface for the emulated 112 # PS/2 keyboard - useful for a quick boot sanity check or scripting, but 113 # you cannot type into the shell this way. Use `make run-gui` to interact. 114 run: $(DISK_IMG) 115 $(QEMU) -drive file=$(DISK_IMG),format=raw -display none -serial stdio -no-reboot 116 117 # Opens a real window - click into it, then type. Serial output is also 118 # echoed to this terminal, but keystrokes only reach the guest via the 119 # window. 120 run-gui: $(DISK_IMG) 121 $(QEMU) -drive file=$(DISK_IMG),format=raw -serial stdio -no-reboot 122 123 clean: 124 rm -rf $(BUILD) 125
# BOOTSTRAP_CC: the *host's* real C compiler, needed exactly once to
# build our own AS/CC/LD from source on a fresh checkout (see
# docs/toolchain.md's Bootstrap section). Everything below that point -
# every kernel/host-tool source file - is built exclusively by our own
# tools; BOOTSTRAP_CC is never invoked on OS code.
BOOTSTRAP_CC := gcc
AS := $(BUILD)/toolchain/as
CC := $(BUILD)/toolchain/cc
LD := $(BUILD)/toolchain/ld
HOSTCC := $(BUILD)/toolchain/cc --target=host
QEMU := qemu-system-i386
BUILD := build
# -Itoolchain/include: CC has no built-in header search path at all (see
# docs/toolchain.md) - <stdint.h>/<stddef.h> only resolve because of this.
CFLAGS := -Ikernel -Itoolchain/include
HOSTCFLAGS := -Ikernel -Itools -Itoolchain/include
# Must match TINYFS_DISK_SECTORS / TINYFS_FS_START_LBA in kernel/fs/tinyfs.h
# and KERNEL_START_LBA / KERNEL_SECTORS in boot/stage2.S
DISK_SECTORS := 4096
KERNEL_START_SECTOR := 5
KERNEL_MAX_SECTORS := 512
BOOT_BIN := $(BUILD)/boot.bin
STAGE2_BIN := $(BUILD)/stage2.bin
DISK_IMG := $(BUILD)/disk.img
# kernel/builtins_shim.c is gone: CC intercepts every __builtin_* call
# site directly in codegen (see docs/toolchain.md) and never needs a
# linkable definition - the shim only existed for the old gcc build.
KERNEL_C_SRCS := kernel/kmain.c kernel/console.c kernel/serial.c kernel/gdt.c \
kernel/idt.c kernel/string.c kernel/memory.c kernel/selftest.c \
kernel/ata.c kernel/fs/tinyfs.c kernel/fs/tinyfs_disk.c \
kernel/keyboard.c kernel/shell/shell.c \
kernel/lang/lexer.c kernel/lang/script/ast.c \
kernel/lang/script/parser.c kernel/lang/script/eval.c \
kernel/lang/compiler/parser.c kernel/lang/compiler/codegen.c \
kernel/lang/vm.c
KERNEL_ASM_SRCS := kernel/entry.S kernel/idt_stubs.S
KERNEL_OBJS := $(patsubst kernel/%.S,$(BUILD)/%.o,$(KERNEL_ASM_SRCS)) \
$(patsubst kernel/%.c,$(BUILD)/%.o,$(KERNEL_C_SRCS))
KERNEL_BIN := $(BUILD)/kernel.bin
MKFS_BIN := $(BUILD)/mkfs_tinyfs
# host_string.o/host_tinyfs.o: kernel/string.c and kernel/fs/tinyfs.c
# compiled a *second* time, natively - host tools are libc-free (see
# docs/toolchain.md), same as the kernel itself, and this build has its
# own build/fs/tinyfs.o etc for the freestanding kernel target, so these
# need the host_ prefix to not collide with those.
# tools/host_syscalls_shim.c is gone the same way builtins_shim.c is:
# CC intercepts __sys_* calls directly.
MKFS_OBJS := $(BUILD)/host_tinyfs.o $(BUILD)/host_string.o $(BUILD)/host_mkfs_tinyfs.o
# host-path:fs-name pairs seeded into every disk image; the host paths
# alone are listed separately so they can be used as make prerequisites
MKFS_SEED_FILES := tests/hello.tsh tests/hello.tc
MKFS_SEED_SPECS := tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc
.PHONY: all run run-gui clean
all: $(DISK_IMG)
BUILD_SUBDIRS := $(BUILD) $(BUILD)/toolchain $(BUILD)/fs $(BUILD)/shell \
$(BUILD)/lang $(BUILD)/lang/script $(BUILD)/lang/compiler
$(BUILD_SUBDIRS):
mkdir -p $@
# --- bootstrap: build our own AS/CC/LD from source, once, with the host's gcc ---
TOOLCHAIN_AS_SRCS := $(wildcard toolchain/as/*.c)
TOOLCHAIN_CC_SRCS := $(wildcard toolchain/cc/*.c)
TOOLCHAIN_LD_SRCS := $(wildcard toolchain/ld/*.c)
$(BUILD)/toolchain/as: $(TOOLCHAIN_AS_SRCS) | $(BUILD)/toolchain
$(BOOTSTRAP_CC) -Wall -Wextra -O2 -o $@ $(TOOLCHAIN_AS_SRCS)
$(BUILD)/toolchain/cc: $(TOOLCHAIN_CC_SRCS) | $(BUILD)/toolchain
$(BOOTSTRAP_CC) -Wall -Wextra -O2 -o $@ $(TOOLCHAIN_CC_SRCS)
$(BUILD)/toolchain/ld: $(TOOLCHAIN_LD_SRCS) | $(BUILD)/toolchain
$(BOOTSTRAP_CC) -Wall -Wextra -O2 -o $@ $(TOOLCHAIN_LD_SRCS)
# --- boot loader (hand-written assembly, flat binary) ---
$(BOOT_BIN): boot/boot.S | $(BUILD) $(AS)
$(AS) -f bin -o $@ $<
$(STAGE2_BIN): boot/stage2.S | $(BUILD) $(AS)
$(AS) -f bin -o $@ $<
# --- kernel ---
# Hand-written assembly (entry.S/idt_stubs.S) assembles directly.
# C sources go through CC first (.c -> .S, AS-syntax text) and then AS
# (.S -> .o, ELF32 relocatable) - CC has no built-in assembler, it always
# emits assembly text (see docs/toolchain.md), so this is always two
# steps where gcc's `-c` used to be one.
$(BUILD)/%.o: kernel/%.S | $(BUILD_SUBDIRS) $(AS)
$(AS) -f elf32 -o $@ $<
$(BUILD)/%.S: kernel/%.c | $(BUILD_SUBDIRS) $(CC)
$(CC) $(CFLAGS) -o $@ $<
$(BUILD)/%.o: $(BUILD)/%.S | $(BUILD_SUBDIRS) $(AS)
$(AS) -f elf32 -o $@ $<
# LD's link_flat mode writes the kernel image straight to a flat binary
# (--format bin) - no intermediate ELF, no OBJCOPY step (see
# docs/toolchain.md: LD already has the loadable bytes laid out in
# memory before an ELF wrapper would even be needed).
$(KERNEL_BIN): $(KERNEL_OBJS) kernel/link.ld | $(LD)
$(LD) -T kernel/link.ld --format bin -o $@ $(KERNEL_OBJS)
@size=$$(stat -c%s $@); budget=$$(( $(KERNEL_MAX_SECTORS) * 512 )); \
if [ "$$size" -gt "$$budget" ]; then \
echo "error: $@ is $$size bytes, exceeds the $$budget-byte ($(KERNEL_MAX_SECTORS)-sector) boot budget" >&2; \
echo " bump KERNEL_SECTORS in boot/stage2.S, KERNEL_MAX_SECTORS here, and TINYFS_FS_START_LBA in kernel/fs/tinyfs.h together" >&2; \
exit 1; \
fi
# --- host-side mkfs tool (build-time infrastructure, not kernel code) ---
# Same two-step (.c -> .S -> .o) pipeline as the kernel, via HOSTCC
# (CC --target=host, which adds a _start that calls main - see
# docs/toolchain.md) instead of CC.
$(BUILD)/host_tinyfs.S: kernel/fs/tinyfs.c | $(BUILD) $(CC)
$(HOSTCC) $(HOSTCFLAGS) -o $@ $<
$(BUILD)/host_tinyfs.o: $(BUILD)/host_tinyfs.S | $(BUILD) $(AS)
$(AS) -f elf32 -o $@ $<
$(BUILD)/host_string.S: kernel/string.c | $(BUILD) $(CC)
$(HOSTCC) $(HOSTCFLAGS) -o $@ $<
$(BUILD)/host_string.o: $(BUILD)/host_string.S | $(BUILD) $(AS)
$(AS) -f elf32 -o $@ $<
$(BUILD)/host_mkfs_tinyfs.S: tools/mkfs_tinyfs.c | $(BUILD) $(CC)
$(HOSTCC) $(HOSTCFLAGS) -o $@ $<
$(BUILD)/host_mkfs_tinyfs.o: $(BUILD)/host_mkfs_tinyfs.S | $(BUILD) $(AS)
$(AS) -f elf32 -o $@ $<
# LD's default (link_static_exec) mode: a minimal static ELF32 executable,
# entry point taken from the linked _start symbol (see docs/toolchain.md).
$(MKFS_BIN): $(MKFS_OBJS) | $(LD)
$(LD) -o $@ $(MKFS_OBJS)
$(DISK_IMG): $(BOOT_BIN) $(STAGE2_BIN) $(KERNEL_BIN) $(MKFS_BIN) $(MKFS_SEED_FILES)
dd if=/dev/zero of=$@ bs=512 count=$(DISK_SECTORS) status=none
dd if=$(BOOT_BIN) of=$@ conv=notrunc status=none
dd if=$(STAGE2_BIN) of=$@ bs=512 seek=1 conv=notrunc status=none
dd if=$(KERNEL_BIN) of=$@ bs=512 seek=$(KERNEL_START_SECTOR) conv=notrunc status=none
$(MKFS_BIN) $@ $(MKFS_SEED_SPECS)
# Headless: no window at all, so there's no input surface for the emulated
# PS/2 keyboard - useful for a quick boot sanity check or scripting, but
# you cannot type into the shell this way. Use `make run-gui` to interact.
run: $(DISK_IMG)
$(QEMU) -drive file=$(DISK_IMG),format=raw -display none -serial stdio -no-reboot
# Opens a real window - click into it, then type. Serial output is also
# echoed to this terminal, but keystrokes only reach the guest via the
# window.
run-gui: $(DISK_IMG)
$(QEMU) -drive file=$(DISK_IMG),format=raw -serial stdio -no-reboot
clean:
rm -rf $(BUILD)
The file /home/martin/workspace/prototypes/boot-to-claude/Makefile has been updated successfully. (file state is current in your context — no need to Read it back)
1 # BOOTSTRAP_CC: the *host's* real C compiler, needed exactly once to 2 # build our own AS/CC/LD from source on a fresh checkout (see 3 # docs/toolchain.md's Bootstrap section). Everything below that point - 4 # every kernel/host-tool source file - is built exclusively by our own 5 # tools; BOOTSTRAP_CC is never invoked on OS code. 6 BOOTSTRAP_CC := gcc 7 8 AS := $(BUILD)/toolchain/as 9 CC := $(BUILD)/toolchain/cc 10 LD := $(BUILD)/toolchain/ld 11 HOSTCC := $(BUILD)/toolchain/cc --target=host 12 QEMU := qemu-system-i386 13 BUILD := build 14 15 # -Itoolchain/include: CC has no built-in header search path at all (see 16 # docs/toolchain.md) - <stdint.h>/<stddef.h> only resolve because of this. 17 CFLAGS := -Ikernel -Itoolchain/include 18 HOSTCFLAGS := -Ikernel -Itools -Itoolchain/include 19 20 # Must match TINYFS_DISK_SECTORS / TINYFS_FS_START_LBA in kernel/fs/tinyfs.h
- # BOOTSTRAP_CC: the *host's* real C compiler, needed exactly once to - # build our own AS/CC/LD from source on a fresh checkout (see - # docs/toolchain.md's Bootstrap section). Everything below that point - - # every kernel/host-tool source file - is built exclusively by our own - # tools; BOOTSTRAP_CC is never invoked on OS code. - BOOTSTRAP_CC := gcc - - AS := $(BUILD)/toolchain/as - CC := $(BUILD)/toolchain/cc - LD := $(BUILD)/toolchain/ld - HOSTCC := $(BUILD)/toolchain/cc --target=host - QEMU := qemu-system-i386 - BUILD := build + BUILD := build + + # BOOTSTRAP_CC: the *host's* real C compiler, needed exactly once to + # build our own AS/CC/LD from source on a fresh checkout (see + # docs/toolchain.md's Bootstrap section). Everything below that point - + # every kernel/host-tool source file - is built exclusively by our own + # tools; BOOTSTRAP_CC is never invoked on OS code. + BOOTSTRAP_CC := gcc + + AS := $(BUILD)/toolchain/as + CC := $(BUILD)/toolchain/cc + LD := $(BUILD)/toolchain/ld + HOSTCC := $(BUILD)/toolchain/cc --target=host + QEMU := qemu-system-i386
The file /home/martin/workspace/prototypes/boot-to-claude/Makefile has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude make clean make 2>&1 | tail -80
rm -rf build mkdir -p build mkdir -p build/toolchain gcc -Wall -Wextra -O2 -o build/toolchain/as toolchain/as/bin_writer.c toolchain/as/elf_writer.c toolchain/as/encode.c toolchain/as/lexer.c toolchain/as/main.c toolchain/as/parser.c build/toolchain/as -f bin -o build/boot.bin boot/boot.S build/toolchain/as -f bin -o build/stage2.bin boot/stage2.S mkdir -p build/fs mkdir -p build/shell mkdir -p build/lang mkdir -p build/lang/script mkdir -p build/lang/compiler build/toolchain/as -f elf32 -o build/entry.o kernel/entry.S build/toolchain/as -f elf32 -o build/idt_stubs.o kernel/idt_stubs.S gcc -Wall -Wextra -O2 -o build/toolchain/cc toolchain/cc/builtins.c toolchain/cc/codegen.c toolchain/cc/lexer.c toolchain/cc/main.c toolchain/cc/parser.c toolchain/cc/preprocessor.c toolchain/cc/types.c build/toolchain/cc -Ikernel -Itoolchain/include -o build/kmain.S kernel/kmain.c build/toolchain/as -f elf32 -o build/kmain.o build/kmain.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/console.S kernel/console.c build/toolchain/as -f elf32 -o build/console.o build/console.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/serial.S kernel/serial.c build/toolchain/as -f elf32 -o build/serial.o build/serial.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/gdt.S kernel/gdt.c build/toolchain/as -f elf32 -o build/gdt.o build/gdt.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/idt.S kernel/idt.c build/toolchain/as -f elf32 -o build/idt.o build/idt.S as: line 148: unsupported instruction form: lidt make: *** [Makefile:108: build/idt.o] Error 1 rm build/console.S build/idt.S build/kmain.S build/gdt.S build/serial.S
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "lidt" toolchain/cc/builtins.c toolchain/as/encode.c echo "---" sed -n '140,155p' build/idt.S 2>/dev/null || grep -n "lidt" -B3 -A2 build/idt.S
Exit code 2
toolchain/cc/builtins.c:29: "__builtin_lidt", "__builtin_gdt_flush", "__builtin_hlt",
toolchain/cc/builtins.c:70: if (strcmp(name, "__builtin_lidt") == 0) {
toolchain/cc/builtins.c:72: emitf(" mov ecx, eax\n lidt [ecx]\n");
---
ugrep: warning: build/idt.S: No such file or directory$ cd /home/martin/workspace/prototypes/boot-to-claude build/toolchain/cc -Ikernel -Itoolchain/include -o /tmp/idt.S kernel/idt.c grep -n "lidt" -B3 -A2 /tmp/idt.S
145-.L1: 146- lea eax, [idtp] 147- mov ecx, eax 148: lidt [ecx] 149- mov eax, 0 150- mov esp, ebp
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n '"lidt"' toolchain/as/encode.c
(Bash completed with no output)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n '"lgdt"\|"hlt"\|"outb"\|"inb"\|"outw"\|"inw"' toolchain/as/encode.c
649: if (strcmp(m, "lgdt") == 0) {
659: if (strcmp(m, "hlt") == 0) { e8(&e, 0xF4); return e.len; }$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "__builtin_outb\|__builtin_inb\|__builtin_outw\|__builtin_inw" -A3 toolchain/cc/builtins.c | head -30 echo "---AS support for in/out---" grep -n '"in"\|"out"\|strcmp(m, "in\|strcmp(m, "out' toolchain/as/encode.c
28: "__builtin_inb", "__builtin_outb", "__builtin_inw", "__builtin_outw",
29- "__builtin_lidt", "__builtin_gdt_flush", "__builtin_hlt",
30- "__sys_open", "__sys_read", "__sys_write", "__sys_lseek", "__sys_close", "__sys_exit",
31- NULL,
--
44: if (strcmp(name, "__builtin_outb") == 0) {
45- emit_arg(nth(args, 0)); emitf(" push eax\n"); /* port */
46- emit_arg(nth(args, 1)); emitf(" mov ecx, eax\n"); /* value */
47- emitf(" pop eax\n"); /* eax = port */
--
53: if (strcmp(name, "__builtin_inb") == 0) {
54- emit_arg(nth(args, 0)); /* port */
55- emitf(" mov edx, eax\n in al, dx\n movzx eax, al\n");
56- return 1;
--
58: if (strcmp(name, "__builtin_outw") == 0) {
59- emit_arg(nth(args, 0)); emitf(" push eax\n");
60- emit_arg(nth(args, 1)); emitf(" mov ecx, eax\n");
61- emitf(" pop eax\n mov edx, eax\n mov eax, ecx\n out dx, ax\n");
--
64: if (strcmp(name, "__builtin_inw") == 0) {
65- emit_arg(nth(args, 0));
66- emitf(" mov edx, eax\n in ax, dx\n movzx eax, ax\n");
67- return 1;
---AS support for in/out---
637: if (strcmp(m, "in") == 0) {
642: if (strcmp(m, "out") == 0) {
648: if (strcmp(m, "int") == 0) { e8(&e, 0xCD); e8(&e, (uint8_t)a->disp); return e.len; }645 if (a->reg == R_DX && b->reg == R_AX) { e8(&e, 0xEF); return e.len; }
646 }
647
648 if (strcmp(m, "int") == 0) { e8(&e, 0xCD); e8(&e, (uint8_t)a->disp); return e.len; }
649 if (strcmp(m, "lgdt") == 0) {
650 e8(&e, 0x0F); e8(&e, 0x01); e8(&e, 0x16);
651 e16(&e, (uint16_t)resolve_abs(ctx, is_bin, a->sym, a->disp, st->section, st->offset + (uint32_t)e.len, strict));
652 return e.len;
653 }
654
655 if (strcmp(m, "pusha") == 0) { e8(&e, 0x60); return e.len; }
656 if (strcmp(m, "popa") == 0) { e8(&e, 0x61); return e.len; }
657 if (strcmp(m, "lodsb") == 0) { e8(&e, 0xAC); return e.len; }
658 if (strcmp(m, "ret") == 0) { e8(&e, 0xC3); return e.len; }
659 if (strcmp(m, "hlt") == 0) { e8(&e, 0xF4); return e.len; }$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "__builtin_gdt_flush" -A10 toolchain/cc/builtins.c
29: "__builtin_lidt", "__builtin_gdt_flush", "__builtin_hlt",
30- "__sys_open", "__sys_read", "__sys_write", "__sys_lseek", "__sys_close", "__sys_exit",
31- NULL,
32- };
33- for (int i = 0; names[i]; i++) if (strcmp(name, names[i]) == 0) return 1;
34- return 0;
35-}
36-
37-/* Returns 1 if `name` was a builtin (and emitted code for it), 0
38- * otherwise (ordinary call - the caller falls back to a normal `call`). */
39-int builtin_emit_call(const char *name, node_t *args, cc_target_t target,
--
75: if (strcmp(name, "__builtin_gdt_flush") == 0) {
76- /* Loads the GDT, reloads every data/stack segment to the flat
77- * data selector (0x10, matching gdt.c's layout), and far-jumps
78- * to the flat code selector (0x08) to reload CS - the one
79- * sequence in the kernel that inherently can't be a single
80- * instruction. A unique label per call site (own counter,
81- * ".Lbi" prefix so it can never collide with codegen.c's own
82- * ".L" control-flow labels) since this can be called more than
83- * once in a translation unit. */
84- char lbl[MAX_NAME];
85- snprintf(lbl, sizeof(lbl), ".Lbi%d", g_builtin_label_id++);$ cd /home/martin/workspace/prototypes/boot-to-claude
grep -n "static void encode_rm32\|static.*encode_rm32(" toolchain/as/encode.c147:static void encode_rm32(emitter_t *e, asm_ctx_t *ctx, int is_bin, operand_t *rm, int reg_field,
140 * both x86-32). Handles register-direct, [esp]/[esp+disp8] (SIB - ESP
141 * can't be a plain ModRM base), [ebp+disp32] (always the disp32 form,
142 * mod=10 - mod=00 with rm=101 means "disp32 direct address" instead of
143 * "ebp with no displacement", so EBP-relative never uses mod=00), and
144 * [label(+disp)] (mod=00, rm=101, disp32 direct address - 32-bit's other
145 * ModRM special case). `reg_field` is either another register or a group
146 * opcode extension number, per the caller. */
147 static void encode_rm32(emitter_t *e, asm_ctx_t *ctx, int is_bin, operand_t *rm, int reg_field,
148 section_kind_t patch_sec, uint32_t stmt_offset, int strict) {
149 if (rm->kind == OPND_REG) {
150 e8(e, (uint8_t)(0xC0 | (reg_field << 3) | regnum(rm->reg)));
151 return;
152 }
153 if (rm->reg == R_ESP) {
154 if (rm->disp == 0) {
155 e8(e, (uint8_t)(0x04 | (reg_field << 3)));
156 e8(e, 0x24);
157 } else {
158 e8(e, (uint8_t)(0x44 | (reg_field << 3)));
159 e8(e, 0x24);
160 e8(e, (uint8_t)rm->disp);
161 }
162 return;
163 }
164 if (rm->reg == R_EBP) {
165 e8(e, (uint8_t)(0x85 | (reg_field << 3)));
166 e32(e, (uint32_t)rm->disp);
167 return;
168 }
169 if (rm->reg != R_NONE) {
170 /* [reg] / [reg+disp] for any other GP register (EAX/ECX/EDX/EBX/
171 * ESI/EDI) - no SIB needed, and mod=00 with no displacement is
172 * safe here (rm=101/EBP is the only base register where mod=00
173 * means something else - see the EBP case above). Always uses
174 * the disp32 form when non-zero, same "no size-optimization"
175 * philosophy as everywhere else in this encoder. */
176 if (rm->disp == 0) {
177 e8(e, (uint8_t)(0x00 | (reg_field << 3) | regnum(rm->reg)));
178 } else {
179 e8(e, (uint8_t)(0x80 | (reg_field << 3) | regnum(rm->reg)));
180 e32(e, (uint32_t)rm->disp);
181 }
182 return;
183 }
184 /* bare label, no base register: 32-bit direct address */
185 e8(e, (uint8_t)(0x05 | (reg_field << 3)));
186 uint32_t patch_off = stmt_offset + (uint32_t)e->len;
187 int64_t addr = resolve_abs(ctx, is_bin, rm->sym, rm->disp, patch_sec, patch_off, strict);
188 e32(e, (uint32_t)addr);
189 }
190
191 /* ADD/OR/AND/SUB/XOR/CMP share one opcode block layout:
192 * base+0 = r/m8,r8 ; base+1 = r/m32,r32 ; base+2 = r8,r/m8 ; base+3 = r32,r/m32
193 * base+4 = AL,imm8 ; base+5 = eAX,imm32 ; group1 (0x81 /ext) = r/m,imm32 */
194 static int alu_lookup(const char *m, int *base, int *ext) {- if (strcmp(m, "int") == 0) { e8(&e, 0xCD); e8(&e, (uint8_t)a->disp); return e.len; }
- if (strcmp(m, "lgdt") == 0) {
- e8(&e, 0x0F); e8(&e, 0x01); e8(&e, 0x16);
- e16(&e, (uint16_t)resolve_abs(ctx, is_bin, a->sym, a->disp, st->section, st->offset + (uint32_t)e.len, strict));
- return e.len;
- }
+ if (strcmp(m, "int") == 0) { e8(&e, 0xCD); e8(&e, (uint8_t)a->disp); return e.len; }
+ if (strcmp(m, "lgdt") == 0 && st->bits == 16) {
+ /* boot/stage2.S's real-mode form: a direct 16-bit absolute
+ * address, hardcoded to that one ModRM byte (mod=00, reg=2,
+ * rm=110 "disp16 direct address") - distinct from the 32-bit
+ * register-indirect form below, which CC's __builtin_gdt_flush
+ * uses instead (`lgdt [ecx]`, loading from a C variable's
+ * address held in a register). */
+ e8(&e, 0x0F); e8(&e, 0x01); e8(&e, 0x16);
+ e16(&e, (uint16_t)resolve_abs(ctx, is_bin, a->sym, a->disp, st->section, st->offset + (uint32_t)e.len, strict));
+ return e.len;
+ }
+ if ((strcmp(m, "lgdt") == 0 || strcmp(m, "lidt") == 0) && st->bits == 32 &&
+ (a->kind == OPND_MEM || a->kind == OPND_REG)) {
+ /* 0F 01 /2 (lgdt) or /3 (lidt), r/m32 - general memory/register
+ * operand via the same encode_rm32 helper every other
+ * instruction here uses, e.g. `lgdt [ecx]` / `lidt [ecx]`. */
+ e8(&e, 0x0F); e8(&e, 0x01);
+ encode_rm32(&e, ctx, is_bin, a, strcmp(m, "lgdt") == 0 ? 2 : 3, st->section, st->offset, strict);
+ return e.len;
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/encode.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -Wall -Wextra -O2 -o build/toolchain/as toolchain/as/*.c 2>&1 | tail -40 && ./tools/run_toolchain_tests.sh 2>&1 | tail -3 make 2>&1 | tail -80
PASS t07_comma (53) PASS t08_forward_struct (6) run_toolchain_tests: PASS build/toolchain/cc -Ikernel -Itoolchain/include -o build/idt.S kernel/idt.c build/toolchain/as -f elf32 -o build/idt.o build/idt.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/string.S kernel/string.c build/toolchain/as -f elf32 -o build/string.o build/string.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/memory.S kernel/memory.c build/toolchain/as -f elf32 -o build/memory.o build/memory.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/selftest.S kernel/selftest.c build/toolchain/as -f elf32 -o build/selftest.o build/selftest.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/ata.S kernel/ata.c build/toolchain/as -f elf32 -o build/ata.o build/ata.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/fs/tinyfs.S kernel/fs/tinyfs.c build/toolchain/as -f elf32 -o build/fs/tinyfs.o build/fs/tinyfs.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/fs/tinyfs_disk.S kernel/fs/tinyfs_disk.c build/toolchain/as -f elf32 -o build/fs/tinyfs_disk.o build/fs/tinyfs_disk.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/keyboard.S kernel/keyboard.c build/toolchain/as -f elf32 -o build/keyboard.o build/keyboard.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/shell/shell.S kernel/shell/shell.c build/toolchain/as -f elf32 -o build/shell/shell.o build/shell/shell.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/lexer.S kernel/lang/lexer.c build/toolchain/as -f elf32 -o build/lang/lexer.o build/lang/lexer.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/script/ast.S kernel/lang/script/ast.c build/toolchain/as -f elf32 -o build/lang/script/ast.o build/lang/script/ast.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/script/parser.S kernel/lang/script/parser.c build/toolchain/as -f elf32 -o build/lang/script/parser.o build/lang/script/parser.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/script/eval.S kernel/lang/script/eval.c cc: internal error: unsupported expression kind 35 make: *** [Makefile:105: build/lang/script/eval.S] Error 1 rm build/shell/shell.S build/idt.S build/lang/script/parser.S build/lang/script/ast.S build/lang/script/eval.S build/string.S build/fs/tinyfs.S build/selftest.S build/lang/lexer.S build/fs/tinyfs_disk.S build/ata.S build/keyboard.S build/memory.S
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "static void emit_data_bytes" toolchain/cc/codegen.c
485:static void emit_data_bytes(node_t *init, type_t *t);
492:static void emit_data_bytes(node_t *init, type_t *t) {465 safe_strcpy(lc.continue_label, g_loop ? g_loop->continue_label : "", MAX_NAME);
466 g_loop = &lc;
467 for (node_t *s = n->b->list; s; s = s->next) {
468 if (s->kind == ND_CASE || s->kind == ND_DEFAULT) out("%s:\n", s->asm_label);
469 else codegen_stmt(s);
470 }
471 g_loop = lc.parent;
472 out("%s:\n", lend);
473 return;
474 }
475 case ND_NOP:
476 return;
477 default:
478 fprintf(stderr, "cc: internal error: unsupported statement kind %d\n", n->kind);
479 exit(1);
480 }
481 }
482
483 /* ---------------- top level ---------------- */
484
485 static void emit_data_bytes(node_t *init, type_t *t);
486
487 static void emit_scalar_bytes(node_t *v, type_t *t) {
488 if (t->kind == TY_CHAR) out(" db %lld\n", (long long)v->num);
489 else out(" dd %lld\n", (long long)v->num);
490 }
491
492 static void emit_data_bytes(node_t *init, type_t *t) {
493 if (!init) {
494 out(" resb %d\n", type_size(t));
495 return;
496 }
497 if (t->kind == TY_ARRAY && init->kind == ND_BLOCK) {
498 int count = 0;
499 for (node_t *v = init->list; v; v = v->next) { emit_scalar_bytes(v, t->base); count++; }
500 int pad = t->array_len - count;
501 for (int i = 0; i < pad; i++) out(" resb %d\n", type_size(t->base));
502 return;
503 }
504 emit_scalar_bytes(init, t);
505 }
506
507 static void codegen_func(node_t *fn) {
508 safe_strcpy(g_cur_func, fn->name, MAX_NAME);
509 if (fn->is_global) out("global %s\n", fn->name); /* static functions keep internal linkage - no `global` */
510 out("%s:\n", fn->name);
511 out(" push ebp\n mov ebp, esp\n");
512 if (fn->num) out(" sub esp, %d\n", (int)fn->num);
513 codegen_stmt(fn->b);
514 out(" mov eax, 0\n mov esp, ebp\n pop ebp\n ret\n");
515 }
516
517 extern int is_builtin_name(const char *name);
518
519 #define MAX_EXTERNS 64$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "typedef struct member\|} member_t" toolchain/cc/cc.h
90:typedef struct member member_t;
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "^struct member" -A8 toolchain/cc/cc.h
92:struct member {
93- char name[MAX_NAME];
94- type_t *type;
95- int offset;
96- member_t *next;
97-};
98-
99-struct type {
100- type_kind_t kind;- static void emit_data_bytes(node_t *init, type_t *t) {
- if (!init) {
- out(" resb %d\n", type_size(t));
- return;
- }
- if (t->kind == TY_ARRAY && init->kind == ND_BLOCK) {
- int count = 0;
- for (node_t *v = init->list; v; v = v->next) { emit_scalar_bytes(v, t->base); count++; }
- int pad = t->array_len - count;
- for (int i = 0; i < pad; i++) out(" resb %d\n", type_size(t->base));
- return;
- }
- emit_scalar_bytes(init, t);
- }
+ static void emit_data_bytes(node_t *init, type_t *t) {
+ if (!init) {
+ out(" resb %d\n", type_size(t));
+ return;
+ }
+ if (t->kind == TY_ARRAY && init->kind == ND_BLOCK) {
+ int count = 0;
+ for (node_t *v = init->list; v; v = v->next) { emit_data_bytes(v, t->base); count++; }
+ int pad = t->array_len - count;
+ for (int i = 0; i < pad; i++) out(" resb %d\n", type_size(t->base));
+ return;
+ }
+ if ((t->kind == TY_STRUCT || t->kind == TY_UNION) && init->kind == ND_BLOCK) {
+ /* Positional: walk declared members alongside the initializer
+ * list (no designated-initializer support - see
+ * docs/toolchain.md). A union brace-init like value_t's
+ * `{ VAL_INT, { 0 } }` naturally works here too: its one element
+ * targets the first member at offset 0, and the trailing-padding
+ * check below fills out the rest of the union's size. */
+ member_t *m = t->members;
+ node_t *v = init->list;
+ int cur_off = 0;
+ for (; m && v; m = m->next, v = v->next) {
+ if (m->offset > cur_off) out(" resb %d\n", m->offset - cur_off);
+ emit_data_bytes(v, m->type);
+ cur_off = m->offset + type_size(m->type);
+ }
+ int total = type_size(t);
+ if (total > cur_off) out(" resb %d\n", total - cur_off);
+ return;
+ }
+ emit_scalar_bytes(init, t);
+ }
+
+ /* Runtime equivalent of emit_data_bytes, for a local (stack) variable's
+ * brace-init - e.g. `exec_result_t r = { EXEC_NORMAL, VAL_ZERO };` (used
+ * in kernel/lang/script/eval.c). Recurses the same way, but instead of
+ * writing bytes it emits store instructions relative to a base
+ * ebp-offset; a plain scalar just evaluates and stores at that offset,
+ * which also serves as the non-aggregate base case (a bare `int x = 1;`
+ * behaves like a one-element "aggregate" of itself). */
+ static void codegen_init_local(int base_offset, type_t *t, node_t *init) {
+ if (t->kind == TY_ARRAY && init->kind == ND_BLOCK) {
+ int off = 0;
+ for (node_t *v = init->list; v; v = v->next) {
+ codegen_init_local(base_offset + off, t->base, v);
+ off += type_size(t->base);
+ }
+ return;
+ }
+ if ((t->kind == TY_STRUCT || t->kind == TY_UNION) && init->kind == ND_BLOCK) {
+ member_t *m = t->members;
+ node_t *v = init->list;
+ for (; m && v; m = m->next, v = v->next) {
+ codegen_init_local(base_offset + m->offset, m->type, v);
+ }
+ return;
+ }
+ out(" lea eax, [ebp%+d]\n", base_offset);
+ out(" push eax\n");
+ codegen_expr(init);
+ out(" pop ecx\n");
+ store_value(t);
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "^static.*codegen_expr(node_t\|^static.*codegen_stmt(node_t\|^static.*codegen_addr(node_t" toolchain/cc/codegen.c
38:static void codegen_expr(node_t *n);
39:static void codegen_addr(node_t *n);
40:static void codegen_stmt(node_t *n);
79:static void codegen_addr(node_t *n) {
151:static void codegen_expr(node_t *n) {
337:static void codegen_stmt(node_t *n) {35 snprintf(buf, MAX_NAME, ".L%d", g_label_id++); 36 } 37 38 static void codegen_expr(node_t *n); 39 static void codegen_addr(node_t *n); 40 static void codegen_stmt(node_t *n);
- static void codegen_expr(node_t *n); - static void codegen_addr(node_t *n); - static void codegen_stmt(node_t *n); + static void codegen_expr(node_t *n); + static void codegen_addr(node_t *n); + static void codegen_stmt(node_t *n); + static void codegen_init_local(int base_offset, type_t *t, node_t *init);
The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "ND_VAR_DECL:" toolchain/cc/codegen.c
346: case ND_VAR_DECL:
346 case ND_VAR_DECL:
347 if (n->is_static_local) return; /* emitted into .data/.bss separately */
348 /* Brace-init lists (`{1,2,3}`) are only supported for
349 * globals/statics (compile-time .data, see emit_data_bytes)
350 * - this codebase never brace-initializes a plain (stack)
351 * local, only scalars. */
352 if (n->init) {
353 out(" lea eax, [ebp%+d]\n", n->frame_offset);
354 out(" push eax\n");
355 codegen_expr(n->init);
356 out(" pop ecx\n");
357 store_value(n->type);
358 }
359 return;
360 case ND_IF: {
361 char lelse[MAX_NAME], lend[MAX_NAME];
362 new_label(lelse); new_label(lend);
363 codegen_expr(n->a);- case ND_VAR_DECL:
- if (n->is_static_local) return; /* emitted into .data/.bss separately */
- /* Brace-init lists (`{1,2,3}`) are only supported for
- * globals/statics (compile-time .data, see emit_data_bytes)
- * - this codebase never brace-initializes a plain (stack)
- * local, only scalars. */
- if (n->init) {
- out(" lea eax, [ebp%+d]\n", n->frame_offset);
- out(" push eax\n");
- codegen_expr(n->init);
- out(" pop ecx\n");
- store_value(n->type);
- }
- return;
+ case ND_VAR_DECL:
+ if (n->is_static_local) return; /* emitted into .data/.bss separately */
+ /* codegen_init_local handles both brace-init lists (e.g.
+ * eval.c's `exec_result_t r = { EXEC_NORMAL, VAL_ZERO };`)
+ * and plain scalar initializers - a bare `int x = 1;` is
+ * just its one-element base case. */
+ if (n->init) codegen_init_local(n->frame_offset, n->type, n->init);
+ return;The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -o build/toolchain/cc toolchain/cc/*.c -Wall 2>&1 | tail -40 && ./tools/run_toolchain_tests.sh 2>&1 | tail -12
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) run_toolchain_tests: PASS
typedef struct { int a; int b; } pair_t;
typedef struct { int tag; pair_t p; } tagged_t;
int main(void) {
int arr[3] = { 10, 20, 30 };
pair_t pr = { 1, 2 };
tagged_t t = { 5, { 6, 7 } };
return arr[0] + arr[1] + arr[2] + pr.a + pr.b + t.tag + t.p.a + t.p.b;
/* 10+20+30 + 1+2 + 5+6+7 = 81 */
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/tests/toolchain/t09_local_init.c (file state is current in your context — no need to Read it back)
- [t08_forward_struct]=6 - ) + [t08_forward_struct]=6 + [t09_local_init]=81 + )
The file /home/martin/workspace/prototypes/boot-to-claude/tools/run_toolchain_tests.sh has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude ./tools/run_toolchain_tests.sh 2>&1 | tail -12
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) run_toolchain_tests: PASS
$ cd /home/martin/workspace/prototypes/boot-to-claude make 2>&1 | tail -100
build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/script/eval.S kernel/lang/script/eval.c build/toolchain/as -f elf32 -o build/lang/script/eval.o build/lang/script/eval.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/compiler/parser.S kernel/lang/compiler/parser.c build/toolchain/as -f elf32 -o build/lang/compiler/parser.o build/lang/compiler/parser.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/compiler/codegen.S kernel/lang/compiler/codegen.c build/toolchain/as -f elf32 -o build/lang/compiler/codegen.o build/lang/compiler/codegen.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/vm.S kernel/lang/vm.c build/toolchain/as -f elf32 -o build/lang/vm.o build/lang/vm.S build/toolchain/ld -T kernel/link.ld --format bin -o build/kernel.bin build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o ld: duplicate symbol '__str_0' (from build/idt.o) make: *** [Makefile:115: build/kernel.bin] Error 1 rm build/lang/script/eval.S build/lang/vm.S build/lang/compiler/codegen.S build/lang/compiler/parser.S
$ cd /home/martin/workspace/prototypes/boot-to-claude
grep -n 'out("global' toolchain/cc/codegen.c554: if (fn->is_global) out("global %s\n", fn->name); /* static functions keep internal linkage - no `global` */
662: out("global _start\n_start:\n");
682: out("global %s\n%s:\n", s->asm_label, s->asm_label);
704: out("global %s\n%s:\n", n->asm_label, n->asm_label);
711: out("global %s\n%s:\n", n->asm_label, n->asm_label);
720: out("global %s\n%s:\n resb %d\n", n->asm_label, n->asm_label, type_size(n->type));
726: out("global %s\n%s:\n resb %d\n", n->asm_label, n->asm_label, type_size(n->type));675 out(" mov ebx, eax\n mov eax, 1\n int 0x80\n");
676 }
677
678 if (g_n_strings) {
679 out("section .rodata\n");
680 for (int i = 0; i < g_n_strings; i++) {
681 node_t *s = g_strings[i];
682 out("global %s\n%s:\n", s->asm_label, s->asm_label);
683 for (const char *p = s->str; ; p++) {
684 out(" db %d\n", (int)(unsigned char)*p);
685 if (!*p) break;
686 }
687 }
688 }
689
690 int any_data = 0, any_bss = 0;
691 for (node_t *n = prog; n; n = n->next) {
692 if (n->kind == ND_VAR_DECL && n->is_global) {
693 if (n->init) any_data = 1; else any_bss = 1;
694 }
695 }
696 for (int i = 0; i < g_n_statics; i++) {
697 if (g_statics[i]->init) any_data = 1; else any_bss = 1;
698 }
699
700 if (any_data) {
701 out("section .data\n");
702 for (node_t *n = prog; n; n = n->next) {
703 if (n->kind == ND_VAR_DECL && n->is_global && n->init) {
704 out("global %s\n%s:\n", n->asm_label, n->asm_label);
705 emit_data_bytes(n->init, n->type);
706 }
707 }
708 for (int i = 0; i < g_n_statics; i++) {
709 node_t *n = g_statics[i];
710 if (n->init) {
711 out("global %s\n%s:\n", n->asm_label, n->asm_label);
712 emit_data_bytes(n->init, n->type);
713 }
714 }
715 }
716 if (any_bss) {
717 out("section .bss\n");
718 for (node_t *n = prog; n; n = n->next) {
719 if (n->kind == ND_VAR_DECL && n->is_global && !n->init) {
720 out("global %s\n%s:\n resb %d\n", n->asm_label, n->asm_label, type_size(n->type));
721 }
722 }
723 for (int i = 0; i < g_n_statics; i++) {
724 node_t *n = g_statics[i];
725 if (!n->init) {
726 out("global %s\n%s:\n resb %d\n", n->asm_label, n->asm_label, type_size(n->type));
727 }
728 }
729 }- if (g_n_strings) {
- out("section .rodata\n");
- for (int i = 0; i < g_n_strings; i++) {
- node_t *s = g_strings[i];
- out("global %s\n%s:\n", s->asm_label, s->asm_label);
- for (const char *p = s->str; ; p++) {
- out(" db %d\n", (int)(unsigned char)*p);
- if (!*p) break;
- }
- }
- }
+ if (g_n_strings) {
+ out("section .rodata\n");
+ for (int i = 0; i < g_n_strings; i++) {
+ /* Not `global`: __str_N is numbered per translation unit
+ * (parser_t's next_str_id resets for every file CC runs on),
+ * only ever referenced from within the same file, and
+ * marking it global made every file's __str_0 collide with
+ * every other file's at link time. */
+ node_t *s = g_strings[i];
+ out("%s:\n", s->asm_label);
+ for (const char *p = s->str; ; p++) {
+ out(" db %d\n", (int)(unsigned char)*p);
+ if (!*p) break;
+ }
+ }
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
- for (int i = 0; i < g_n_statics; i++) {
- node_t *n = g_statics[i];
- if (n->init) {
- out("global %s\n%s:\n", n->asm_label, n->asm_label);
- emit_data_bytes(n->init, n->type);
- }
- }
- }
- if (any_bss) {
- out("section .bss\n");
- for (node_t *n = prog; n; n = n->next) {
- if (n->kind == ND_VAR_DECL && n->is_global && !n->init) {
- out("global %s\n%s:\n resb %d\n", n->asm_label, n->asm_label, type_size(n->type));
- }
- }
- for (int i = 0; i < g_n_statics; i++) {
- node_t *n = g_statics[i];
- if (!n->init) {
- out("global %s\n%s:\n resb %d\n", n->asm_label, n->asm_label, type_size(n->type));
- }
- }
- }
+ for (int i = 0; i < g_n_statics; i++) {
+ /* Not `global`, same reasoning as __str_N above -
+ * __static_N is only numbered uniquely within one file. */
+ node_t *n = g_statics[i];
+ if (n->init) {
+ out("%s:\n", n->asm_label);
+ emit_data_bytes(n->init, n->type);
+ }
+ }
+ }
+ if (any_bss) {
+ out("section .bss\n");
+ for (node_t *n = prog; n; n = n->next) {
+ if (n->kind == ND_VAR_DECL && n->is_global && !n->init) {
+ out("global %s\n%s:\n resb %d\n", n->asm_label, n->asm_label, type_size(n->type));
+ }
+ }
+ for (int i = 0; i < g_n_statics; i++) {
+ node_t *n = g_statics[i];
+ if (!n->init) {
+ out("%s:\n resb %d\n", n->asm_label, type_size(n->type));
+ }
+ }
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -o build/toolchain/cc toolchain/cc/*.c -Wall 2>&1 | tail -40 && ./tools/run_toolchain_tests.sh 2>&1 | tail -12
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) run_toolchain_tests: PASS
$ cd /home/martin/workspace/prototypes/boot-to-claude make 2>&1 | tail -100
build/toolchain/ld -T kernel/link.ld --format bin -o build/kernel.bin build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o ld: duplicate symbol '__str_0' (from build/idt.o) make: *** [Makefile:115: build/kernel.bin] Error 1
$ cd /home/martin/workspace/prototypes/boot-to-claude make clean make 2>&1 | tail -100
rm -rf build mkdir -p build mkdir -p build/toolchain gcc -Wall -Wextra -O2 -o build/toolchain/as toolchain/as/bin_writer.c toolchain/as/elf_writer.c toolchain/as/encode.c toolchain/as/lexer.c toolchain/as/main.c toolchain/as/parser.c build/toolchain/as -f bin -o build/boot.bin boot/boot.S build/toolchain/as -f bin -o build/stage2.bin boot/stage2.S mkdir -p build/fs mkdir -p build/shell mkdir -p build/lang mkdir -p build/lang/script mkdir -p build/lang/compiler build/toolchain/as -f elf32 -o build/entry.o kernel/entry.S build/toolchain/as -f elf32 -o build/idt_stubs.o kernel/idt_stubs.S gcc -Wall -Wextra -O2 -o build/toolchain/cc toolchain/cc/builtins.c toolchain/cc/codegen.c toolchain/cc/lexer.c toolchain/cc/main.c toolchain/cc/parser.c toolchain/cc/preprocessor.c toolchain/cc/types.c build/toolchain/cc -Ikernel -Itoolchain/include -o build/kmain.S kernel/kmain.c build/toolchain/as -f elf32 -o build/kmain.o build/kmain.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/console.S kernel/console.c build/toolchain/as -f elf32 -o build/console.o build/console.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/serial.S kernel/serial.c build/toolchain/as -f elf32 -o build/serial.o build/serial.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/gdt.S kernel/gdt.c build/toolchain/as -f elf32 -o build/gdt.o build/gdt.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/idt.S kernel/idt.c build/toolchain/as -f elf32 -o build/idt.o build/idt.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/string.S kernel/string.c build/toolchain/as -f elf32 -o build/string.o build/string.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/memory.S kernel/memory.c build/toolchain/as -f elf32 -o build/memory.o build/memory.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/selftest.S kernel/selftest.c build/toolchain/as -f elf32 -o build/selftest.o build/selftest.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/ata.S kernel/ata.c build/toolchain/as -f elf32 -o build/ata.o build/ata.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/fs/tinyfs.S kernel/fs/tinyfs.c build/toolchain/as -f elf32 -o build/fs/tinyfs.o build/fs/tinyfs.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/fs/tinyfs_disk.S kernel/fs/tinyfs_disk.c build/toolchain/as -f elf32 -o build/fs/tinyfs_disk.o build/fs/tinyfs_disk.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/keyboard.S kernel/keyboard.c build/toolchain/as -f elf32 -o build/keyboard.o build/keyboard.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/shell/shell.S kernel/shell/shell.c build/toolchain/as -f elf32 -o build/shell/shell.o build/shell/shell.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/lexer.S kernel/lang/lexer.c build/toolchain/as -f elf32 -o build/lang/lexer.o build/lang/lexer.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/script/ast.S kernel/lang/script/ast.c build/toolchain/as -f elf32 -o build/lang/script/ast.o build/lang/script/ast.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/script/parser.S kernel/lang/script/parser.c build/toolchain/as -f elf32 -o build/lang/script/parser.o build/lang/script/parser.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/script/eval.S kernel/lang/script/eval.c build/toolchain/as -f elf32 -o build/lang/script/eval.o build/lang/script/eval.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/compiler/parser.S kernel/lang/compiler/parser.c build/toolchain/as -f elf32 -o build/lang/compiler/parser.o build/lang/compiler/parser.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/compiler/codegen.S kernel/lang/compiler/codegen.c build/toolchain/as -f elf32 -o build/lang/compiler/codegen.o build/lang/compiler/codegen.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/vm.S kernel/lang/vm.c build/toolchain/as -f elf32 -o build/lang/vm.o build/lang/vm.S gcc -Wall -Wextra -O2 -o build/toolchain/ld toolchain/ld/elf_reader.c toolchain/ld/link_flat.c toolchain/ld/link_static_exec.c toolchain/ld/main.c build/toolchain/ld -T kernel/link.ld --format bin -o build/kernel.bin build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o ld: duplicate symbol 'isr_stub_table' (from build/idt.o) make: *** [Makefile:115: build/kernel.bin] Error 1 rm build/console.S build/shell/shell.S build/idt.S build/lang/script/parser.S build/lang/script/ast.S build/lang/script/eval.S build/lang/vm.S build/lang/compiler/codegen.S build/lang/compiler/parser.S build/kmain.S build/gdt.S build/string.S build/fs/tinyfs.S build/selftest.S build/serial.S build/lang/lexer.S build/fs/tinyfs_disk.S build/ata.S build/keyboard.S build/memory.S
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "isr_stub_table" kernel/idt_stubs.S kernel/idt.c kernel/idt.h
kernel/idt_stubs.S:217:global isr_stub_table kernel/idt_stubs.S:218:isr_stub_table: kernel/idt.c:23:extern uint32_t isr_stub_table[32]; kernel/idt.c:41: idt_set_gate(i, isr_stub_table[i]);
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "is_extern_decl" toolchain/cc/*.c
toolchain/cc/parser.c:938: n->is_extern_decl = is_extern;
690 if (!*p) break;
691 }
692 }
693 }
694
695 int any_data = 0, any_bss = 0;
696 for (node_t *n = prog; n; n = n->next) {
697 if (n->kind == ND_VAR_DECL && n->is_global) {
698 if (n->init) any_data = 1; else any_bss = 1;
699 }
700 }
701 for (int i = 0; i < g_n_statics; i++) {
702 if (g_statics[i]->init) any_data = 1; else any_bss = 1;
703 }
704
705 if (any_data) {
706 out("section .data\n");
707 for (node_t *n = prog; n; n = n->next) {
708 if (n->kind == ND_VAR_DECL && n->is_global && n->init) {
709 out("global %s\n%s:\n", n->asm_label, n->asm_label);
710 emit_data_bytes(n->init, n->type);
711 }
712 }
713 for (int i = 0; i < g_n_statics; i++) {
714 /* Not `global`, same reasoning as __str_N above -
715 * __static_N is only numbered uniquely within one file. */
716 node_t *n = g_statics[i];
717 if (n->init) {
718 out("%s:\n", n->asm_label);
719 emit_data_bytes(n->init, n->type);
720 }
721 }
722 }
723 if (any_bss) {
724 out("section .bss\n");
725 for (node_t *n = prog; n; n = n->next) {
726 if (n->kind == ND_VAR_DECL && n->is_global && !n->init) {
727 out("global %s\n%s:\n resb %d\n", n->asm_label, n->asm_label, type_size(n->type));
728 }
729 }
730 for (int i = 0; i < g_n_statics; i++) {
731 node_t *n = g_statics[i];
732 if (!n->init) {
733 out("%s:\n resb %d\n", n->asm_label, type_size(n->type));
734 }$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n '"extern"\|is_extern\|SHN_UNDEF' toolchain/as/parser.c toolchain/as/elf_writer.c | head -30
toolchain/as/parser.c:429: if (ieq(head.text, "extern")) {
toolchain/as/parser.c:431: sym_get_or_create(ps->ctx, name.text)->is_extern = 1;
toolchain/as/elf_writer.c:88: if (!s->defined && !s->is_extern) continue;
toolchain/as/elf_writer.c:89: int is_glob = s->is_global || s->is_extern;
toolchain/as/elf_writer.c:99: sym.st_shndx = SHN_UNDEF;70 int shidx_strtab = nsh++;
71 uint32_t name_strtab = strtab_add(&shstrtab, ".strtab");
72 int shidx_shstrtab = nsh++;
73 uint32_t name_shstrtab = strtab_add(&shstrtab, ".shstrtab");
74
75 /* --- symbol table: locals first (ELF requirement), then globals --- */
76 static Elf32_Sym symtab[MAX_SYMS + 1];
77 int nsym = 0;
78 static strtab_builder_t strtab;
79 memset(&strtab, 0, sizeof(strtab));
80 static int sym_elf_index[MAX_SYMS];
81
82 symtab[nsym++] = (Elf32_Sym){0}; /* index 0: null symbol */
83
84 for (int pass = 0; pass < 2; pass++) { /* pass 0 = locals, pass 1 = globals */
85 for (int i = 0; i < ctx->nsyms; i++) {
86 symbol_t *s = &ctx->syms[i];
87 if (s->is_equ) continue;
88 if (!s->defined && !s->is_extern) continue;
89 int is_glob = s->is_global || s->is_extern;
90 if ((pass == 0 && is_glob) || (pass == 1 && !is_glob)) continue;
91
92 Elf32_Sym sym = {0};
93 sym.st_name = strtab_add(&strtab, s->name);
94 sym.st_info = (unsigned char)ELF32_ST_INFO(is_glob ? STB_GLOBAL : STB_LOCAL, STT_NOTYPE);
95 if (s->defined) {
96 sym.st_shndx = (Elf32_Half)shidx_content[s->section];
97 sym.st_value = s->offset;
98 } else {
99 sym.st_shndx = SHN_UNDEF;
100 }
101 sym_elf_index[i] = nsym;
102 symtab[nsym++] = sym;
103 }
104 }
105 int first_global = 1;
106 for (int i = 1; i < nsym; i++) {
107 if ((symtab[i].st_info >> 4) == STB_LOCAL) first_global = i + 1;
108 }
109 618 g_out = (FILE *)out_file;
619 g_target = target;
620 g_label_id = 0;
621 g_loop = NULL;
622 g_n_externs = 0;
623 g_n_strings = 0;
624 g_n_statics = 0;
625
626 out("BITS 32\n");
627
628 for (node_t *n = prog; n; n = n->next) {
629 /* walk_collect recurses into ->next (needed for sibling statement
630 * chains reached via ->list/->init) - passing a top-level node
631 * straight through would also walk the rest of the top-level
632 * chain, which this loop is already advancing over itself,
633 * causing every node after the first to be collected once per
634 * remaining prefix (quadratic duplication of externs/strings).
635 * Cut the top-level ->next link before recursing into this one
636 * node's own subtree, then restore it. */
637 node_t *saved_next = n->next;
638 n->next = NULL;
639 if (n->kind != ND_FUNC_DEF) walk_collect(n, prog); /* globals: just for string literals in initializers */
640 else walk_collect(n->b, prog); /* function bodies */
641 n->next = saved_next;
642 }
643 for (int i = 0; i < g_n_externs; i++) out("extern %s\n", g_externs[i]);
644
645 out("section .text\n");
646 for (node_t *n = prog; n; n = n->next) {
647 if (n->kind == ND_FUNC_DEF) codegen_func(n);
648 }
649 int has_main = 0;
650 for (node_t *n = prog; n; n = n->next) {
651 if (n->kind == ND_FUNC_DEF && strcmp(n->name, "main") == 0) { has_main = 1; break; }
652 }
653 if (target == TARGET_HOST && has_main) {
654 /* Only the one compilation unit that actually defines main should
655 * provide _start - host programs are typically built from several
656 * --target=host files (e.g. mkfs_tinyfs.c + tinyfs.c + string.c),
657 * and emitting a `call main` in every one of them would leave an- n->next = saved_next;
- }
- for (int i = 0; i < g_n_externs; i++) out("extern %s\n", g_externs[i]);
+ n->next = saved_next;
+ }
+ /* extern variable declarations (e.g. idt.c's `extern uint32_t
+ * isr_stub_table[32];`, defined in idt_stubs.S) need the same
+ * `extern NAME` treatment as called-but-not-locally-defined
+ * functions above - walk_collect only looks at ND_CALL, never plain
+ * variable references, so this is a separate pass. Without it, AS
+ * drops any relocation against a name with no local definition and
+ * no explicit `extern` (see docs/toolchain.md) - the reference
+ * would silently resolve to nothing instead of erroring. */
+ for (node_t *n = prog; n; n = n->next) {
+ if (n->kind == ND_VAR_DECL && n->is_extern_decl && !already_extern(n->name) && g_n_externs < MAX_EXTERNS) {
+ safe_strcpy(g_externs[g_n_externs++], n->name, MAX_NAME);
+ }
+ }
+ for (int i = 0; i < g_n_externs; i++) out("extern %s\n", g_externs[i]);The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
- int any_data = 0, any_bss = 0;
- for (node_t *n = prog; n; n = n->next) {
- if (n->kind == ND_VAR_DECL && n->is_global) {
- if (n->init) any_data = 1; else any_bss = 1;
- }
- }
- for (int i = 0; i < g_n_statics; i++) {
- if (g_statics[i]->init) any_data = 1; else any_bss = 1;
- }
-
- if (any_data) {
- out("section .data\n");
- for (node_t *n = prog; n; n = n->next) {
- if (n->kind == ND_VAR_DECL && n->is_global && n->init) {
- out("global %s\n%s:\n", n->asm_label, n->asm_label);
- emit_data_bytes(n->init, n->type);
- }
- }
+ int any_data = 0, any_bss = 0;
+ for (node_t *n = prog; n; n = n->next) {
+ /* is_extern_decl: a declaration only (`extern T name;`), not a
+ * definition - it must never get its own storage, real or
+ * otherwise it silently duplicates whatever file actually
+ * defines the symbol (see the extern-collection pass above). */
+ if (n->kind == ND_VAR_DECL && n->is_global && !n->is_extern_decl) {
+ if (n->init) any_data = 1; else any_bss = 1;
+ }
+ }
+ for (int i = 0; i < g_n_statics; i++) {
+ if (g_statics[i]->init) any_data = 1; else any_bss = 1;
+ }
+
+ if (any_data) {
+ out("section .data\n");
+ for (node_t *n = prog; n; n = n->next) {
+ if (n->kind == ND_VAR_DECL && n->is_global && !n->is_extern_decl && n->init) {
+ out("global %s\n%s:\n", n->asm_label, n->asm_label);
+ emit_data_bytes(n->init, n->type);
+ }
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'n->kind == ND_VAR_DECL && n->is_global && !n->init' toolchain/cc/codegen.c
743: if (n->kind == ND_VAR_DECL && n->is_global && !n->init) {738 }
739 }
740 if (any_bss) {
741 out("section .bss\n");
742 for (node_t *n = prog; n; n = n->next) {
743 if (n->kind == ND_VAR_DECL && n->is_global && !n->init) {
744 out("global %s\n%s:\n resb %d\n", n->asm_label, n->asm_label, type_size(n->type));
745 }
746 }
747 for (int i = 0; i < g_n_statics; i++) {- if (n->kind == ND_VAR_DECL && n->is_global && !n->init) {
- out("global %s\n%s:\n resb %d\n", n->asm_label, n->asm_label, type_size(n->type));
- }
+ if (n->kind == ND_VAR_DECL && n->is_global && !n->is_extern_decl && !n->init) {
+ out("global %s\n%s:\n resb %d\n", n->asm_label, n->asm_label, type_size(n->type));
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -o build/toolchain/cc toolchain/cc/*.c -Wall 2>&1 | tail -40 && ./tools/run_toolchain_tests.sh 2>&1 | tail -12
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) run_toolchain_tests: PASS
$ cd /home/martin/workspace/prototypes/boot-to-claude make clean make 2>&1 | tail -60
rm -rf build mkdir -p build mkdir -p build/toolchain gcc -Wall -Wextra -O2 -o build/toolchain/as toolchain/as/bin_writer.c toolchain/as/elf_writer.c toolchain/as/encode.c toolchain/as/lexer.c toolchain/as/main.c toolchain/as/parser.c build/toolchain/as -f bin -o build/boot.bin boot/boot.S build/toolchain/as -f bin -o build/stage2.bin boot/stage2.S mkdir -p build/fs mkdir -p build/shell mkdir -p build/lang mkdir -p build/lang/script mkdir -p build/lang/compiler build/toolchain/as -f elf32 -o build/entry.o kernel/entry.S build/toolchain/as -f elf32 -o build/idt_stubs.o kernel/idt_stubs.S gcc -Wall -Wextra -O2 -o build/toolchain/cc toolchain/cc/builtins.c toolchain/cc/codegen.c toolchain/cc/lexer.c toolchain/cc/main.c toolchain/cc/parser.c toolchain/cc/preprocessor.c toolchain/cc/types.c build/toolchain/cc -Ikernel -Itoolchain/include -o build/kmain.S kernel/kmain.c build/toolchain/as -f elf32 -o build/kmain.o build/kmain.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/console.S kernel/console.c build/toolchain/as -f elf32 -o build/console.o build/console.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/serial.S kernel/serial.c build/toolchain/as -f elf32 -o build/serial.o build/serial.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/gdt.S kernel/gdt.c build/toolchain/as -f elf32 -o build/gdt.o build/gdt.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/idt.S kernel/idt.c build/toolchain/as -f elf32 -o build/idt.o build/idt.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/string.S kernel/string.c build/toolchain/as -f elf32 -o build/string.o build/string.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/memory.S kernel/memory.c build/toolchain/as -f elf32 -o build/memory.o build/memory.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/selftest.S kernel/selftest.c build/toolchain/as -f elf32 -o build/selftest.o build/selftest.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/ata.S kernel/ata.c build/toolchain/as -f elf32 -o build/ata.o build/ata.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/fs/tinyfs.S kernel/fs/tinyfs.c build/toolchain/as -f elf32 -o build/fs/tinyfs.o build/fs/tinyfs.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/fs/tinyfs_disk.S kernel/fs/tinyfs_disk.c build/toolchain/as -f elf32 -o build/fs/tinyfs_disk.o build/fs/tinyfs_disk.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/keyboard.S kernel/keyboard.c build/toolchain/as -f elf32 -o build/keyboard.o build/keyboard.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/shell/shell.S kernel/shell/shell.c build/toolchain/as -f elf32 -o build/shell/shell.o build/shell/shell.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/lexer.S kernel/lang/lexer.c build/toolchain/as -f elf32 -o build/lang/lexer.o build/lang/lexer.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/script/ast.S kernel/lang/script/ast.c build/toolchain/as -f elf32 -o build/lang/script/ast.o build/lang/script/ast.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/script/parser.S kernel/lang/script/parser.c build/toolchain/as -f elf32 -o build/lang/script/parser.o build/lang/script/parser.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/script/eval.S kernel/lang/script/eval.c build/toolchain/as -f elf32 -o build/lang/script/eval.o build/lang/script/eval.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/compiler/parser.S kernel/lang/compiler/parser.c build/toolchain/as -f elf32 -o build/lang/compiler/parser.o build/lang/compiler/parser.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/compiler/codegen.S kernel/lang/compiler/codegen.c build/toolchain/as -f elf32 -o build/lang/compiler/codegen.o build/lang/compiler/codegen.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/vm.S kernel/lang/vm.c build/toolchain/as -f elf32 -o build/lang/vm.o build/lang/vm.S gcc -Wall -Wextra -O2 -o build/toolchain/ld toolchain/ld/elf_reader.c toolchain/ld/link_flat.c toolchain/ld/link_static_exec.c toolchain/ld/main.c build/toolchain/ld -T kernel/link.ld --format bin -o build/kernel.bin build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o ld: duplicate symbol 'parse_statement' (from build/lang/compiler/parser.o) make: *** [Makefile:115: build/kernel.bin] Error 1 rm build/console.S build/shell/shell.S build/idt.S build/lang/script/parser.S build/lang/script/ast.S build/lang/script/eval.S build/lang/vm.S build/lang/compiler/codegen.S build/lang/compiler/parser.S build/kmain.S build/gdt.S build/string.S build/fs/tinyfs.S build/selftest.S build/serial.S build/lang/lexer.S build/fs/tinyfs_disk.S build/ata.S build/keyboard.S build/memory.S
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "parse_statement" kernel/lang/script/parser.c kernel/lang/compiler/parser.c | grep -v "^\s*//" | head -10
kernel/lang/compiler/parser.c:55:static ast_node_t *parse_statement(tc_parser_t *p);
kernel/lang/compiler/parser.c:62: ast_node_t *s = parse_statement(p);
kernel/lang/compiler/parser.c:208:static ast_node_t *parse_statement(tc_parser_t *p) {
kernel/lang/compiler/parser.c:222: else_blk = is_kw(p, "if") ? parse_statement(p) : parse_block(p);
kernel/lang/compiler/parser.c:303: ast_node_t *s = parse_statement(p);
kernel/lang/script/parser.c:55:static ast_node_t *parse_statement(parser_t *p);
kernel/lang/script/parser.c:62: ast_node_t *s = parse_statement(p);
kernel/lang/script/parser.c:209:static ast_node_t *parse_statement(parser_t *p) {
kernel/lang/script/parser.c:223: else_blk = is_kw(p, "if") ? parse_statement(p) : parse_block(p);
kernel/lang/script/parser.c:304: ast_node_t *s = parse_statement(p);$ cd /home/martin/workspace/prototypes/boot-to-claude
cat > /tmp/static_test.c << 'EOF'
static int helper(int x) { return x + 1; }
int main(void) { return helper(4); }
EOF
build/toolchain/cc -Itoolchain/include --target=host -o /tmp/static_test.S /tmp/static_test.c
cat /tmp/static_test.SBITS 32
section .text
helper:
push ebp
mov ebp, esp
mov eax, 1
push eax
lea eax, [ebp+8]
mov eax, [eax]
pop ecx
add eax, ecx
mov esp, ebp
pop ebp
ret
mov eax, 0
mov esp, ebp
pop ebp
ret
global main
main:
push ebp
mov ebp, esp
mov eax, 4
push eax
call helper
add esp, 4
mov esp, ebp
pop ebp
ret
mov eax, 0
mov esp, ebp
pop ebp
ret
global _start
_start:
mov eax, [esp]
lea ecx, [esp+4]
push ecx
push eax
call main
mov ebx, eax
mov eax, 1
int 0x80$ cd /home/martin/workspace/prototypes/boot-to-claude
cat > /tmp/static_test2.c << 'EOF'
static int helper(int x);
int main(void) { return helper(4); }
static int helper(int x) { return x + 1; }
EOF
build/toolchain/cc -Itoolchain/include --target=host -o /tmp/static_test2.S /tmp/static_test2.c
grep -n "global\|^helper:" /tmp/static_test2.S4:global main 19:helper: 35:global _start
$ cd /home/martin/workspace/prototypes/boot-to-claude build/toolchain/cc -Ikernel -Itoolchain/include -o /tmp/compiler_parser.S kernel/lang/compiler/parser.c grep -n "global parse_statement\|^parse_statement:" /tmp/compiler_parser.S echo "---script parser---" build/toolchain/cc -Ikernel -Itoolchain/include -o /tmp/script_parser.S kernel/lang/script/parser.c grep -n "global parse_statement\|^parse_statement:" /tmp/script_parser.S
1250:parse_statement: ---script parser--- 1260:parse_statement:
$ cd /home/martin/workspace/prototypes/boot-to-claude ls build/lang/script/parser.o build/lang/compiler/parser.o readelf -s build/lang/script/parser.o 2>/dev/null | grep -i "parse_statement\|GLOBAL\|LOCAL" | grep parse_statement echo "---" readelf -s build/lang/compiler/parser.o 2>/dev/null | grep parse_statement
build/lang/compiler/parser.o
build/lang/script/parser.o
76: 00000ff1 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L57
77: 00000ff1 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L58
78: 0000112d 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L63
79: 00001141 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L64
80: 00001149 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L61
81: 00001149 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L62
82: 000011c3 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L59
83: 000011c3 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L60
84: 000012ce 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L65
85: 000012ce 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L66
86: 0000136f 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L69
87: 000013a2 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L70
88: 0000140c 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L73
89: 000014c7 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L78
90: 000014e1 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L79
91: 000014f8 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L76
92: 00001518 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L77
93: 00001554 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L80
94: 00001554 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L81
95: 00001554 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L75
96: 00001559 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L74
97: 0000155e 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L71
98: 0000155e 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L72
99: 00001617 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L67
100: 00001617 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L68
101: 000016ca 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L84
102: 000016ca 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L85
103: 000016f5 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L82
104: 000016f5 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L83
131: 00000fa5 0 NOTYPE GLOBAL DEFAULT 1 parse_statement
---
76: 00000fd2 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L57
77: 00000fd2 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L58
78: 0000110e 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L63
79: 00001122 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L64
80: 0000112a 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L61
81: 0000112a 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L62
82: 000011a4 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L59
83: 000011a4 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L60
84: 000012af 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L65
85: 000012af 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L66
86: 00001350 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L69
87: 00001383 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L70
88: 000013ed 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L73
89: 000014a8 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L78
90: 000014c2 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L79
91: 000014d9 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L76
92: 000014f9 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L77
93: 00001535 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L80
94: 00001535 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L81
95: 00001535 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L75
96: 0000153a 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L74
97: 0000153f 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L71
98: 0000153f 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L72
99: 000015f8 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L67
100: 000015f8 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L68
101: 000016ab 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L84
102: 000016ab 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L85
103: 000016d6 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L82
104: 000016d6 0 NOTYPE LOCAL DEFAULT 1 parse_statement.L83
132: 00000f86 0 NOTYPE GLOBAL DEFAULT 1 parse_statement$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "parse_statement" /tmp/compiler_parser.S | grep -v "^\d*:.*\.L"
6:extern parse_statement 388: call parse_statement 1250:parse_statement: 1353: call parse_statement 1880: call parse_statement
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "static void walk_collect" toolchain/cc/codegen.c
587:static void walk_collect(node_t *n, node_t *prog) {587 static void walk_collect(node_t *n, node_t *prog) {
588 if (!n) return;
589 if (n->kind == ND_CALL) {
590 int is_local = 0;
591 for (node_t *f = prog; f; f = f->next) {
592 if (f->kind == ND_FUNC_DEF && strcmp(f->name, n->name) == 0) { is_local = 1; break; }
593 }
594 if (!is_local && !is_builtin_name(n->name) && !already_extern(n->name) && g_n_externs < MAX_EXTERNS) {
595 safe_strcpy(g_externs[g_n_externs++], n->name, MAX_NAME);
596 }
597 }
598 if (n->kind == ND_STR && g_n_strings < MAX_STRINGS) {
599 g_strings[g_n_strings++] = n;
600 }
601 /* static locals live inside a function body's statement list, not585 * locally-defined, non-builtin functions (need `extern`), and string
586 * literal nodes (need their bytes emitted into a data section later). */
587 static void walk_collect(node_t *n, node_t *prog) {
588 if (!n) return;
589 if (n->kind == ND_CALL) {
590 int is_local = 0;
591 for (node_t *f = prog; f; f = f->next) {
592 if (f->kind == ND_FUNC_DEF && strcmp(f->name, n->name) == 0) { is_local = 1; break; }
593 }
594 if (!is_local && !is_builtin_name(n->name) && !already_extern(n->name) && g_n_externs < MAX_EXTERNS) {
595 safe_strcpy(g_externs[g_n_externs++], n->name, MAX_NAME);
596 }
597 }
598 if (n->kind == ND_STR && g_n_strings < MAX_STRINGS) {
599 g_strings[g_n_strings++] = n;
600 }
601 /* static locals live inside a function body's statement list, not
602 * prog's top-level chain - the .data/.bss emission below only scans
603 * top-level decls, so without this they'd be referenced (lea eax,
604 * [__static_N]) but never actually allocated storage. */
605 if (n->kind == ND_VAR_DECL && n->is_static_local && g_n_statics < MAX_STATICS) {
606 g_statics[g_n_statics++] = n;
607 }
608 walk_collect(n->a, prog);
609 walk_collect(n->b, prog);
610 walk_collect(n->c, prog);
611 walk_collect(n->d, prog);
612 walk_collect(n->list, prog);
613 walk_collect(n->init, prog);
614 walk_collect(n->next, prog);- static void walk_collect(node_t *n, node_t *prog) {
- if (!n) return;
- if (n->kind == ND_CALL) {
- int is_local = 0;
- for (node_t *f = prog; f; f = f->next) {
- if (f->kind == ND_FUNC_DEF && strcmp(f->name, n->name) == 0) { is_local = 1; break; }
- }
- if (!is_local && !is_builtin_name(n->name) && !already_extern(n->name) && g_n_externs < MAX_EXTERNS) {
- safe_strcpy(g_externs[g_n_externs++], n->name, MAX_NAME);
- }
- }
- if (n->kind == ND_STR && g_n_strings < MAX_STRINGS) {
- g_strings[g_n_strings++] = n;
- }
- /* static locals live inside a function body's statement list, not
- * prog's top-level chain - the .data/.bss emission below only scans
- * top-level decls, so without this they'd be referenced (lea eax,
- * [__static_N]) but never actually allocated storage. */
- if (n->kind == ND_VAR_DECL && n->is_static_local && g_n_statics < MAX_STATICS) {
- g_statics[g_n_statics++] = n;
- }
- walk_collect(n->a, prog);
- walk_collect(n->b, prog);
- walk_collect(n->c, prog);
- walk_collect(n->d, prog);
- walk_collect(n->list, prog);
- walk_collect(n->init, prog);
- walk_collect(n->next, prog);
+ /* skip_next=1 for the one call the top-level driver makes per top-level
+ * node (see codegen_emit) - that loop already advances over prog's own
+ * ->next chain itself, and recursing into ->next there too would
+ * re-walk (and re-collect) the whole remaining top-level chain on every
+ * iteration. An earlier version of this fix instead temporarily NULLed
+ * n->next around that call, which corrupted prog's chain *while it was
+ * still being scanned* by the is_local check below whenever that scan
+ * reached the currently-truncated node - any forward-referenced same-
+ * file function (called from a function defined earlier in the file)
+ * would then look "not local" and get a spurious `extern`, colliding at
+ * link time with the real definition once another file defined a same-
+ * named local of its own. skip_next avoids the mutation entirely: below
+ * ->next is walked normally for every *nested* recursion (statement
+ * sibling chains reached via ->list/->init still need it), just not for
+ * this one top-level entry point. */
+ static void walk_collect_ex(node_t *n, node_t *prog, int skip_next) {
+ if (!n) return;
+ if (n->kind == ND_CALL) {
+ int is_local = 0;
+ for (node_t *f = prog; f; f = f->next) {
+ if (f->kind == ND_FUNC_DEF && strcmp(f->name, n->name) == 0) { is_local = 1; break; }
+ }
+ if (!is_local && !is_builtin_name(n->name) && !already_extern(n->name) && g_n_externs < MAX_EXTERNS) {
+ safe_strcpy(g_externs[g_n_externs++], n->name, MAX_NAME);
+ }
+ }
+ if (n->kind == ND_STR && g_n_strings < MAX_STRINGS) {
+ g_strings[g_n_strings++] = n;
+ }
+ /* static locals live inside a function body's statement list, not
+ * prog's top-level chain - the .data/.bss emission below only scans
+ * top-level decls, so without this they'd be referenced (lea eax,
+ * [__static_N]) but never actually allocated storage. */
+ if (n->kind == ND_VAR_DECL && n->is_static_local && g_n_statics < MAX_STATICS) {
+ g_statics[g_n_statics++] = n;
+ }
+ walk_collect_ex(n->a, prog, 0);
+ walk_collect_ex(n->b, prog, 0);
+ walk_collect_ex(n->c, prog, 0);
+ walk_collect_ex(n->d, prog, 0);
+ walk_collect_ex(n->list, prog, 0);
+ walk_collect_ex(n->init, prog, 0);
+ if (!skip_next) walk_collect_ex(n->next, prog, 0);The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
624 walk_collect_ex(n->b, prog, 0);
625 walk_collect_ex(n->c, prog, 0);
626 walk_collect_ex(n->d, prog, 0);
627 walk_collect_ex(n->list, prog, 0);
628 walk_collect_ex(n->init, prog, 0);
629 if (!skip_next) walk_collect_ex(n->next, prog, 0);
630 }
631
632 void codegen_emit(node_t *prog, void *out_file, cc_target_t target) {
633 g_out = (FILE *)out_file;632 void codegen_emit(node_t *prog, void *out_file, cc_target_t target) {
633 g_out = (FILE *)out_file;
634 g_target = target;
635 g_label_id = 0;
636 g_loop = NULL;
637 g_n_externs = 0;
638 g_n_strings = 0;
639 g_n_statics = 0;
640
641 out("BITS 32\n");
642
643 for (node_t *n = prog; n; n = n->next) {
644 /* walk_collect recurses into ->next (needed for sibling statement
645 * chains reached via ->list/->init) - passing a top-level node
646 * straight through would also walk the rest of the top-level
647 * chain, which this loop is already advancing over itself,
648 * causing every node after the first to be collected once per
649 * remaining prefix (quadratic duplication of externs/strings).
650 * Cut the top-level ->next link before recursing into this one
651 * node's own subtree, then restore it. */
652 node_t *saved_next = n->next;
653 n->next = NULL;
654 if (n->kind != ND_FUNC_DEF) walk_collect(n, prog); /* globals: just for string literals in initializers */
655 else walk_collect(n->b, prog); /* function bodies */
656 n->next = saved_next;
657 }
658 /* extern variable declarations (e.g. idt.c's `extern uint32_t
659 * isr_stub_table[32];`, defined in idt_stubs.S) need the same
660 * `extern NAME` treatment as called-but-not-locally-defined
661 * functions above - walk_collect only looks at ND_CALL, never plain- for (node_t *n = prog; n; n = n->next) {
- /* walk_collect recurses into ->next (needed for sibling statement
- * chains reached via ->list/->init) - passing a top-level node
- * straight through would also walk the rest of the top-level
- * chain, which this loop is already advancing over itself,
- * causing every node after the first to be collected once per
- * remaining prefix (quadratic duplication of externs/strings).
- * Cut the top-level ->next link before recursing into this one
- * node's own subtree, then restore it. */
- node_t *saved_next = n->next;
- n->next = NULL;
- if (n->kind != ND_FUNC_DEF) walk_collect(n, prog); /* globals: just for string literals in initializers */
- else walk_collect(n->b, prog); /* function bodies */
- n->next = saved_next;
- }
+ for (node_t *n = prog; n; n = n->next) {
+ /* skip_next=1: this loop already advances over prog's top-level
+ * chain itself (see walk_collect_ex's comment). */
+ if (n->kind != ND_FUNC_DEF) walk_collect_ex(n, prog, 1); /* globals: just for string literals in initializers */
+ else walk_collect_ex(n->b, prog, 1); /* function bodies */
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude
grep -n "walk_collect(" toolchain/cc/codegen.c(Bash completed with no output)
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -o build/toolchain/cc toolchain/cc/*.c -Wall 2>&1 | tail -40 && ./tools/run_toolchain_tests.sh 2>&1 | tail -12
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) run_toolchain_tests: PASS
$ cd /home/martin/workspace/prototypes/boot-to-claude build/toolchain/cc -Ikernel -Itoolchain/include -o /tmp/compiler_parser2.S kernel/lang/compiler/parser.c grep -n "extern parse_statement\|^global parse_statement" /tmp/compiler_parser2.S echo "exit=$?"
exit=1
$ cd /home/martin/workspace/prototypes/boot-to-claude make clean make 2>&1 | tail -100
rm -rf build mkdir -p build mkdir -p build/toolchain gcc -Wall -Wextra -O2 -o build/toolchain/as toolchain/as/bin_writer.c toolchain/as/elf_writer.c toolchain/as/encode.c toolchain/as/lexer.c toolchain/as/main.c toolchain/as/parser.c build/toolchain/as -f bin -o build/boot.bin boot/boot.S build/toolchain/as -f bin -o build/stage2.bin boot/stage2.S mkdir -p build/fs mkdir -p build/shell mkdir -p build/lang mkdir -p build/lang/script mkdir -p build/lang/compiler build/toolchain/as -f elf32 -o build/entry.o kernel/entry.S build/toolchain/as -f elf32 -o build/idt_stubs.o kernel/idt_stubs.S gcc -Wall -Wextra -O2 -o build/toolchain/cc toolchain/cc/builtins.c toolchain/cc/codegen.c toolchain/cc/lexer.c toolchain/cc/main.c toolchain/cc/parser.c toolchain/cc/preprocessor.c toolchain/cc/types.c build/toolchain/cc -Ikernel -Itoolchain/include -o build/kmain.S kernel/kmain.c build/toolchain/as -f elf32 -o build/kmain.o build/kmain.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/console.S kernel/console.c build/toolchain/as -f elf32 -o build/console.o build/console.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/serial.S kernel/serial.c build/toolchain/as -f elf32 -o build/serial.o build/serial.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/gdt.S kernel/gdt.c build/toolchain/as -f elf32 -o build/gdt.o build/gdt.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/idt.S kernel/idt.c build/toolchain/as -f elf32 -o build/idt.o build/idt.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/string.S kernel/string.c build/toolchain/as -f elf32 -o build/string.o build/string.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/memory.S kernel/memory.c build/toolchain/as -f elf32 -o build/memory.o build/memory.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/selftest.S kernel/selftest.c build/toolchain/as -f elf32 -o build/selftest.o build/selftest.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/ata.S kernel/ata.c build/toolchain/as -f elf32 -o build/ata.o build/ata.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/fs/tinyfs.S kernel/fs/tinyfs.c build/toolchain/as -f elf32 -o build/fs/tinyfs.o build/fs/tinyfs.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/fs/tinyfs_disk.S kernel/fs/tinyfs_disk.c build/toolchain/as -f elf32 -o build/fs/tinyfs_disk.o build/fs/tinyfs_disk.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/keyboard.S kernel/keyboard.c build/toolchain/as -f elf32 -o build/keyboard.o build/keyboard.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/shell/shell.S kernel/shell/shell.c build/toolchain/as -f elf32 -o build/shell/shell.o build/shell/shell.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/lexer.S kernel/lang/lexer.c build/toolchain/as -f elf32 -o build/lang/lexer.o build/lang/lexer.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/script/ast.S kernel/lang/script/ast.c build/toolchain/as -f elf32 -o build/lang/script/ast.o build/lang/script/ast.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/script/parser.S kernel/lang/script/parser.c build/toolchain/as -f elf32 -o build/lang/script/parser.o build/lang/script/parser.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/script/eval.S kernel/lang/script/eval.c build/toolchain/as -f elf32 -o build/lang/script/eval.o build/lang/script/eval.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/compiler/parser.S kernel/lang/compiler/parser.c build/toolchain/as -f elf32 -o build/lang/compiler/parser.o build/lang/compiler/parser.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/compiler/codegen.S kernel/lang/compiler/codegen.c build/toolchain/as -f elf32 -o build/lang/compiler/codegen.o build/lang/compiler/codegen.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/vm.S kernel/lang/vm.c build/toolchain/as -f elf32 -o build/lang/vm.o build/lang/vm.S gcc -Wall -Wextra -O2 -o build/toolchain/ld toolchain/ld/elf_reader.c toolchain/ld/link_flat.c toolchain/ld/link_static_exec.c toolchain/ld/main.c build/toolchain/ld -T kernel/link.ld --format bin -o build/kernel.bin build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_tinyfs.S kernel/fs/tinyfs.c build/toolchain/as -f elf32 -o build/host_tinyfs.o build/host_tinyfs.S build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_string.S kernel/string.c build/toolchain/as -f elf32 -o build/host_string.o build/host_string.S build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_mkfs_tinyfs.S tools/mkfs_tinyfs.c build/toolchain/as -f elf32 -o build/host_mkfs_tinyfs.o build/host_mkfs_tinyfs.S build/toolchain/ld -o build/mkfs_tinyfs build/host_tinyfs.o build/host_string.o build/host_mkfs_tinyfs.o dd if=/dev/zero of=build/disk.img bs=512 count=4096 status=none dd if=build/boot.bin of=build/disk.img conv=notrunc status=none dd if=build/stage2.bin of=build/disk.img bs=512 seek=1 conv=notrunc status=none dd if=build/kernel.bin of=build/disk.img bs=512 seek=5 conv=notrunc status=none build/mkfs_tinyfs build/disk.img tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc mkfs_tinyfs: done rm build/console.S build/shell/shell.S build/idt.S build/lang/script/parser.S build/lang/script/ast.S build/lang/script/eval.S build/lang/vm.S build/lang/compiler/codegen.S build/lang/compiler/parser.S build/kmain.S build/gdt.S build/string.S build/fs/tinyfs.S build/selftest.S build/serial.S build/lang/lexer.S build/fs/tinyfs_disk.S build/ata.S build/keyboard.S build/memory.S
$ cd /home/martin/workspace/prototypes/boot-to-claude ./tools/run_tests.sh 2>&1 | tail -80
run_tests: FAIL - qemu monitor socket never appeared --- serial log ---
$ cd /home/martin/workspace/prototypes/boot-to-claude rm -f /tmp/serial_debug.log timeout 8 qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial file:/tmp/serial_debug.log -no-reboot 2>&1 echo "===serial===" cat /tmp/serial_debug.log
===serial===
$ cd /home/martin/workspace/prototypes/boot-to-claude
ls -la build/boot.bin build/stage2.bin build/kernel.bin build/disk.img
python3 -c "
import struct
with open('build/kernel.bin','rb') as f:
d = f.read()
print('kernel.bin size:', len(d))
print('first 32 bytes:', d[:32].hex())
"-rw-r----- 1 martin martin 512 Jul 18 17:41 build/boot.bin -rw-r----- 1 martin martin 2097152 Jul 18 17:41 build/disk.img -rw-r----- 1 martin martin 63748 Jul 18 17:41 build/kernel.bin -rw-r----- 1 martin martin 2048 Jul 18 17:41 build/stage2.bin kernel.bin size: 63748 first 32 bytes: bc04390200bf04f90100b9d8d002002bcf33c0fcf3aae8d7010000f4e9faffff
$ cd /home/martin/workspace/prototypes/boot-to-claude timeout 5 qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial stdio -no-reboot -d int,cpu_reset -D /tmp/qemu_debug.log 2>&1 | head -5 echo "===qemu debug log (first 100 lines)===" head -100 /tmp/qemu_debug.log
===qemu debug log (first 100 lines)=== CPU Reset (CPU 0) EAX=00000000 EBX=00000000 ECX=00000000 EDX=00000000 ESI=00000000 EDI=00000000 EBP=00000000 ESP=00000000 EIP=00000000 EFL=00000000 [-------] CPL=0 II=0 A20=0 SMM=0 HLT=0 ES =0000 00000000 00000000 00000000 CS =0000 00000000 00000000 00000000 SS =0000 00000000 00000000 00000000 DS =0000 00000000 00000000 00000000 FS =0000 00000000 00000000 00000000 GS =0000 00000000 00000000 00000000 LDT=0000 00000000 00000000 00000000 TR =0000 00000000 00000000 00000000 GDT= 00000000 00000000 IDT= 00000000 00000000 CR0=00000000 CR2=00000000 CR3=00000000 CR4=00000000 DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 DR6=00000000 DR7=00000000 CCS=00000000 CCD=00000000 CCO=EFLAGS EFER=0000000000000000 FCW=0000 FSW=0000 [ST=0] FTW=ff MXCSR=00000000 FPR0=0000000000000000 0000 FPR1=0000000000000000 0000 FPR2=0000000000000000 0000 FPR3=0000000000000000 0000 FPR4=0000000000000000 0000 FPR5=0000000000000000 0000 FPR6=0000000000000000 0000 FPR7=0000000000000000 0000 XMM00=0000000000000000 0000000000000000 XMM01=0000000000000000 0000000000000000 XMM02=0000000000000000 0000000000000000 XMM03=0000000000000000 0000000000000000 XMM04=0000000000000000 0000000000000000 XMM05=0000000000000000 0000000000000000 XMM06=0000000000000000 0000000000000000 XMM07=0000000000000000 0000000000000000 CPU Reset (CPU 0) EAX=00000000 EBX=00000000 ECX=00000000 EDX=00000663 ESI=00000000 EDI=00000000 EBP=00000000 ESP=00000000 EIP=0000fff0 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0 ES =0000 00000000 0000ffff 00009300 CS =f000 ffff0000 0000ffff 00009b00 SS =0000 00000000 0000ffff 00009300 DS =0000 00000000 0000ffff 00009300 FS =0000 00000000 0000ffff 00009300 GS =0000 00000000 0000ffff 00009300 LDT=0000 00000000 0000ffff 00008200 TR =0000 00000000 0000ffff 00008b00 GDT= 00000000 0000ffff IDT= 00000000 0000ffff CR0=60000010 CR2=00000000 CR3=00000000 CR4=00000000 DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 DR6=ffff0ff0 DR7=00000400 CCS=00000000 CCD=00000000 CCO=EFLAGS EFER=0000000000000000 FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80 FPR0=0000000000000000 0000 FPR1=0000000000000000 0000 FPR2=0000000000000000 0000 FPR3=0000000000000000 0000 FPR4=0000000000000000 0000 FPR5=0000000000000000 0000 FPR6=0000000000000000 0000 FPR7=0000000000000000 0000 XMM00=0000000000000000 0000000000000000 XMM01=0000000000000000 0000000000000000 XMM02=0000000000000000 0000000000000000 XMM03=0000000000000000 0000000000000000 XMM04=0000000000000000 0000000000000000 XMM05=0000000000000000 0000000000000000 XMM06=0000000000000000 0000000000000000 XMM07=0000000000000000 0000000000000000 SMM: enter EAX=00000000 EBX=00000000 ECX=02000000 EDX=02000628 ESI=0000000b EDI=02000000 EBP=00014e40 ESP=00006c5c EIP=000ea61d EFL=00000046 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0 ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA] CS =0008 00000000 ffffffff 00cf9b00 DPL=0 CS32 [-RA] SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA] DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA] FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA] GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA] LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy GDT= 000f61e0 00000037 IDT= 000f621e 00000000 CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000 DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 DR6=ffff0ff0 DR7=00000400 CCS=00000044 CCD=ffffffff CCO=EFLAGS EFER=0000000000000000 SMM: after RSM EAX=00000000 EBX=00000000 ECX=02000000 EDX=02000628 ESI=0000000b EDI=02000000 EBP=00014e40 ESP=00006c5c EIP=000ea61d EFL=00000046 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0 ES =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] CS =0008 00000000 ffffffff 00c09b00 DPL=0 CS32 [-RA] SS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] DS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] FS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] GS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy GDT= 000f61e0 00000037 IDT= 000f621e 00000000 CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000 DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 DR6=ffff0ff0 DR7=00000400 CCS=00000044 CCD=00000000 CCO=EFLAGS EFER=0000000000000000 SMM: enter EAX=000000b5 EBX=000f7e0e ECX=00001234 EDX=00006cff ESI=00006cb4 EDI=06ffec55 EBP=00006c74 ESP=00006c74 EIP=00007e0b EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0 ES =d900 000d9000 ffffffff 008f9300 CS =f000 000f0000 ffffffff 008f9b00
$ wc -l /tmp/qemu_debug.log grep -n "Triple fault\|check_exception\|v=0e\|v=0d\|v=08" /tmp/qemu_debug.log | tail -20
518 /tmp/qemu_debug.log 477:check_exception old: 0xffffffff new 0xd 478: 0: v=0d e=00b8 i=0 cpl=0 IP=0008:00010b77 pc=00010b77 SP=0010:000238f4 env->regs[R_EAX]=00020010 497:check_exception old: 0xd new 0xd 498: 1: v=08 e=0000 i=0 cpl=0 IP=0008:00010b77 pc=00010b77 SP=0010:000238f4 env->regs[R_EAX]=00020010 517:check_exception old: 0x8 new 0xd 518:Triple fault
$ cd /home/martin/workspace/prototypes/boot-to-claude
KOBJS="build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o"
build/toolchain/ld -T kernel/link.ld -o /tmp/kernel_debug.elf $KOBJS
nm /tmp/kernel_debug.elf | sort | awk '{print $1, $3}' > /tmp/kernel_syms.txt
python3 -c "
target = 0x10b77
best = None
with open('/tmp/kernel_syms.txt') as f:
for line in f:
parts = line.split()
if len(parts) < 2: continue
try:
addr = int(parts[0], 16)
except ValueError:
continue
if addr <= target:
if best is None or addr > best[0]:
best = (addr, parts[1])
print(best, hex(target - best[0]) if best else None)
"(68274, 'gdt_init') 0xc5
$ cd /home/martin/workspace/prototypes/boot-to-claude cat kernel/gdt.c
#include "gdt.h"
#include <stdint.h>
#include "builtins.h"
/* The kernel owns its GDT rather than relying on the one stage2 built on
* the real-mode stack - stage2's copy happens to still be valid at this
* point, but the kernel should not depend on bootloader memory. */
struct gdt_entry {
uint16_t limit_low;
uint16_t base_low;
uint8_t base_mid;
uint8_t access;
uint8_t granularity;
uint8_t base_high;
} __attribute__((packed));
struct gdt_ptr {
uint16_t limit;
uint32_t base;
} __attribute__((packed));
static struct gdt_entry gdt[3];
static struct gdt_ptr gdtp;
static void gdt_set_gate(int n, uint32_t base, uint32_t limit, uint8_t access, uint8_t gran) {
gdt[n].base_low = base & 0xFFFF;
gdt[n].base_mid = (base >> 16) & 0xFF;
gdt[n].base_high = (base >> 24) & 0xFF;
gdt[n].limit_low = limit & 0xFFFF;
gdt[n].granularity = (uint8_t)(((limit >> 16) & 0x0F) | (gran & 0xF0));
gdt[n].access = access;
}
void gdt_init(void) {
gdtp.limit = sizeof(gdt) - 1;
gdtp.base = (uint32_t)&gdt;
gdt_set_gate(0, 0, 0, 0, 0); /* null descriptor */
gdt_set_gate(1, 0, 0xFFFFF, 0x9A, 0xC0); /* 0x08: code, ring0, 4GB flat */
gdt_set_gate(2, 0, 0xFFFFF, 0x92, 0xC0); /* 0x10: data, ring0, 4GB flat */
__builtin_gdt_flush(&gdtp);
}$ cd /home/martin/workspace/prototypes/boot-to-claude objdump -d -M intel /tmp/kernel_debug.elf 2>&1 | grep -A60 "<gdt_init>:"
00010ab2 <gdt_init>: 10ab2: 55 push ebp 10ab3: 8b ec mov ebp,esp 10ab5: 8d 05 1c 39 02 00 lea eax,ds:0x2391c 10abb: 50 push eax 10abc: b8 01 00 00 00 mov eax,0x1 10ac1: 50 push eax 10ac2: b8 18 00 00 00 mov eax,0x18 10ac7: 59 pop ecx 10ac8: 2b c1 sub eax,ecx 10aca: 59 pop ecx 10acb: 89 01 mov DWORD PTR [ecx],eax 10acd: 8d 05 1c 39 02 00 lea eax,ds:0x2391c 10ad3: 81 c0 02 00 00 00 add eax,0x2 10ad9: 50 push eax 10ada: 8d 05 04 39 02 00 lea eax,ds:0x23904 10ae0: 59 pop ecx 10ae1: 89 01 mov DWORD PTR [ecx],eax 10ae3: b8 00 00 00 00 mov eax,0x0 10ae8: 50 push eax 10ae9: b8 00 00 00 00 mov eax,0x0 10aee: 50 push eax 10aef: b8 00 00 00 00 mov eax,0x0 10af4: 50 push eax 10af5: b8 00 00 00 00 mov eax,0x0 10afa: 50 push eax 10afb: b8 00 00 00 00 mov eax,0x0 10b00: 50 push eax 10b01: e8 48 fe ff ff call 1094e <serial_write+0x4d> 10b06: 81 c4 14 00 00 00 add esp,0x14 10b0c: b8 c0 00 00 00 mov eax,0xc0 10b11: 50 push eax 10b12: b8 9a 00 00 00 mov eax,0x9a 10b17: 50 push eax 10b18: b8 ff ff 0f 00 mov eax,0xfffff 10b1d: 50 push eax 10b1e: b8 00 00 00 00 mov eax,0x0 10b23: 50 push eax 10b24: b8 01 00 00 00 mov eax,0x1 10b29: 50 push eax 10b2a: e8 1f fe ff ff call 1094e <serial_write+0x4d> 10b2f: 81 c4 14 00 00 00 add esp,0x14 10b35: b8 c0 00 00 00 mov eax,0xc0 10b3a: 50 push eax 10b3b: b8 92 00 00 00 mov eax,0x92 10b40: 50 push eax 10b41: b8 ff ff 0f 00 mov eax,0xfffff 10b46: 50 push eax 10b47: b8 00 00 00 00 mov eax,0x0 10b4c: 50 push eax 10b4d: b8 02 00 00 00 mov eax,0x2 10b52: 50 push eax 10b53: e8 f6 fd ff ff call 1094e <serial_write+0x4d> 10b58: 81 c4 14 00 00 00 add esp,0x14 10b5e: 8d 05 1c 39 02 00 lea eax,ds:0x2391c 10b64: 8b c8 mov ecx,eax 10b66: 0f 01 11 lgdtd [ecx] 10b69: 66 b8 10 00 mov ax,0x10 10b6d: 8e d8 mov ds,eax 10b6f: 8e c0 mov es,eax 10b71: 8e e0 mov fs,eax
$ cd /home/martin/workspace/prototypes/boot-to-claude objdump -d -M intel /tmp/kernel_debug.elf 2>&1 | grep -A75 "<gdt_init>:" | tail -15
10b73: 8e e8 mov gs,eax 10b75: 8e d0 mov ss,eax 10b77: ea 7c 0b 09 00 b8 00 jmp 0xb8:0x90b7c 10b7e: 00 00 add BYTE PTR [eax],al 10b80: 00 8b e5 5d c3 55 add BYTE PTR [ebx+0x55c35de5],cl 10b86: 8b ec mov ebp,esp 10b88: 8d 85 08 00 00 00 lea eax,[ebp+0x8] 10b8e: 8b 00 mov eax,DWORD PTR [eax] 10b90: 50 push eax 10b91: 8d 05 22 39 02 00 lea eax,ds:0x23922 10b97: 59 pop ecx 10b98: 69 c9 08 00 00 00 imul ecx,ecx,0x8 10b9e: 03 c1 add eax,ecx 10ba0: 50 push eax 10ba1: b8 ff ff 00 00 mov eax,0xffff
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "OPND_FAR" toolchain/as/encode.c
576: if (a->kind == OPND_FAR) {560 (int64_t)st->offset + 6, st->section, st->offset + 2, strict, 4);
561 e32(&e, (uint32_t)rel);
562 }
563 return e.len;
564 }
565 if (strcmp(m, "jne") == 0 || strcmp(m, "jl") == 0 || strcmp(m, "jle") == 0 || strcmp(m, "jg") == 0 || strcmp(m, "jge") == 0) {
566 uint8_t cc = strcmp(m, "jne") == 0 ? 0x85 : strcmp(m, "jl") == 0 ? 0x8C : strcmp(m, "jle") == 0 ? 0x8E
567 : strcmp(m, "jg") == 0 ? 0x8F : 0x8D; /* jge */
568 e8(&e, 0x0F); e8(&e, cc);
569 int64_t rel = resolve_pcrel(ctx, is_bin, a->sym, a->disp, st->section,
570 (int64_t)st->offset + 6, st->section, st->offset + 2, strict, 4);
571 e32(&e, (uint32_t)rel);
572 return e.len;
573 }
574
575 if (strcmp(m, "jmp") == 0) {
576 if (a->kind == OPND_FAR) {
577 e8(&e, 0xEA);
578 int64_t off = resolve_abs(ctx, is_bin, a->sym, a->disp, st->section, st->offset + 1, strict);
579 e16(&e, (uint16_t)off);
580 int64_t seg = a->far_seg_has_sym
581 ? resolve_abs(ctx, is_bin, a->far_seg_sym, a->far_seg, st->section, st->offset + 3, strict)
582 : a->far_seg;
583 e16(&e, (uint16_t)seg);
584 return e.len;
585 }
586 if (st->bits == 16) {
587 e8(&e, 0xE9);
588 int64_t rel = resolve_pcrel(ctx, is_bin, a->sym, a->disp, st->section,
589 (int64_t)st->offset + 3, st->section, st->offset + 1, strict, 2);
590 e16(&e, (uint16_t)rel);
591 } else {
592 e8(&e, 0xE9);
593 int64_t rel = resolve_pcrel(ctx, is_bin, a->sym, a->disp, st->section,
594 (int64_t)st->offset + 5, st->section, st->offset + 1, strict, 4);
595 e32(&e, (uint32_t)rel);
596 }
597 return e.len;
598 }
599 - if (a->kind == OPND_FAR) {
- e8(&e, 0xEA);
- int64_t off = resolve_abs(ctx, is_bin, a->sym, a->disp, st->section, st->offset + 1, strict);
- e16(&e, (uint16_t)off);
- int64_t seg = a->far_seg_has_sym
- ? resolve_abs(ctx, is_bin, a->far_seg_sym, a->far_seg, st->section, st->offset + 3, strict)
- : a->far_seg;
- e16(&e, (uint16_t)seg);
- return e.len;
- }
+ if (a->kind == OPND_FAR) {
+ /* EA ptr16:16 (16-bit operand size) or EA ptr16:32 (32-bit) -
+ * the segment selector is always 16 bits, but the offset
+ * width follows the current bit mode. Using the 16-bit form
+ * unconditionally left the far jump 2 bytes short in 32-bit
+ * code (e.g. __builtin_gdt_flush's `jmp 0x08:label`, used to
+ * reload CS after loading the GDT) - the "segment" half
+ * ended up reading the offset's missing high 16 bits
+ * instead, and the real segment bytes spilled into the next
+ * instruction, corrupting everything after it. */
+ e8(&e, 0xEA);
+ uint32_t off_patch_off = st->offset + 1;
+ int width = st->bits == 16 ? 2 : 4;
+ int64_t off = resolve_abs(ctx, is_bin, a->sym, a->disp, st->section, off_patch_off, strict);
+ if (width == 2) e16(&e, (uint16_t)off); else e32(&e, (uint32_t)off);
+ int64_t seg = a->far_seg_has_sym
+ ? resolve_abs(ctx, is_bin, a->far_seg_sym, a->far_seg, st->section, off_patch_off + (uint32_t)width, strict)
+ : a->far_seg;
+ e16(&e, (uint16_t)seg);
+ return e.len;
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/encode.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "jmp.*:" boot/boot.S boot/stage2.S kernel/entry.S 2>/dev/null
boot/boot.S:27: jmp 0x0000:0x7e00 boot/stage2.S:26: jmp CODE_SEG:protected_mode_entry
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -Wall -Wextra -O2 -o build/toolchain/as toolchain/as/*.c 2>&1 | tail -30 build/toolchain/as -f bin -o /tmp/stage2_new.bin boot/stage2.S nasm -f bin -o /tmp/stage2_nasm.bin boot/stage2.S 2>&1 cmp /tmp/stage2_new.bin /tmp/stage2_nasm.bin && echo "IDENTICAL" || echo "DIFFERS" cmp build/boot.bin <(nasm -f bin -o /dev/stdout boot/boot.S 2>/dev/null) 2>&1 || true
/tmp/stage2_new.bin /tmp/stage2_nasm.bin differ: byte 2, line 1 DIFFERS build/boot.bin /dev/fd/63 differ: byte 13, line 1
$ cd /home/martin/workspace/prototypes/boot-to-claude xxd build/boot.bin | head -2 echo "---nasm---" nasm -f bin -o /tmp/boot_nasm.bin boot/boot.S xxd /tmp/boot_nasm.bin | head -2
00000000: 31c0 8ed8 8ec0 8ed0 bc00 7cbe 507c e812 1.........|.P|.. 00000010: 00be 407c b442 b280 cd13 0f82 1800 ea00 ..@|.B.......... ---nasm--- 00000000: 31c0 8ed8 8ec0 8ed0 bc00 7cbe 4c7c e810 1.........|.L|.. 00000010: 00be 3c7c b442 b280 cd13 7215 ea00 7e00 ..<|.B....r...~.
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "jmp CODE_SEG" boot/stage2.S grep -n "^BITS" boot/stage2.S
26: jmp CODE_SEG:protected_mode_entry 5:BITS 16 143:BITS 32
$ cd /home/martin/workspace/prototypes/boot-to-claude
python3 -c "
with open('/tmp/stage2_new.bin','rb') as f: a = f.read()
with open('/tmp/stage2_nasm.bin','rb') as f: b = f.read()
print('len a:', len(a), 'len b:', len(b))
for i in range(min(len(a),len(b))):
if a[i]!=b[i]:
print('first diff at byte', i, hex(a[i]), hex(b[i]))
break
"len a: 2048 len b: 2048 first diff at byte 1 0xdc 0xc8
$ cd /home/martin/workspace/prototypes/boot-to-claude xxd /tmp/stage2_new.bin | head -2 echo "---nasm---" xxd /tmp/stage2_nasm.bin | head -2 echo "---source start---" sed -n '1,15p' boot/stage2.S
00000000: bedc 7ee8 1d00 e82d 00e8 3100 fa0f 0116 ..~....-..1.....
00000010: 107f 0f20 c066 0d01 0000 000f 22c0 ea16 ... .f......"...
---nasm---
00000000: bec8 7ee8 1b00 e828 00e8 2c00 fa0f 0116 ..~....(..,.....
00000010: f87e 0f20 c066 83c8 010f 22c0 eafe 7e08 .~. .f...."...~.
---source start---
; Stage 2 bootloader. Loaded by stage1 at 0x0000:0x7e00, still 16-bit real mode.
; Job: enable A20, load the kernel from disk into memory below 1MB, build a
; flat GDT, switch to 32-bit protected mode, and jump into the kernel entry.
BITS 16
ORG 0x7e00
KERNEL_LOAD_ADDR equ 0x10000 ; 64KB - plenty of conventional memory headroom
KERNEL_START_LBA equ 5 ; right after stage1 (LBA 0) + stage2 (LBA 1-4)
KERNEL_SECTORS equ 512 ; 256KB kernel image budget (must match Makefile/tinyfs.h)
stage2_start:
mov si, msg_stage2
call print_str16$ cd /home/martin/workspace/prototypes/boot-to-claude ./tools/run_toolchain_tests.sh 2>&1 | tail -12 make clean make 2>&1 | tail -20
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) run_toolchain_tests: PASS rm -rf build build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/compiler/codegen.S kernel/lang/compiler/codegen.c build/toolchain/as -f elf32 -o build/lang/compiler/codegen.o build/lang/compiler/codegen.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/vm.S kernel/lang/vm.c build/toolchain/as -f elf32 -o build/lang/vm.o build/lang/vm.S gcc -Wall -Wextra -O2 -o build/toolchain/ld toolchain/ld/elf_reader.c toolchain/ld/link_flat.c toolchain/ld/link_static_exec.c toolchain/ld/main.c build/toolchain/ld -T kernel/link.ld --format bin -o build/kernel.bin build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_tinyfs.S kernel/fs/tinyfs.c build/toolchain/as -f elf32 -o build/host_tinyfs.o build/host_tinyfs.S build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_string.S kernel/string.c build/toolchain/as -f elf32 -o build/host_string.o build/host_string.S build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_mkfs_tinyfs.S tools/mkfs_tinyfs.c build/toolchain/as -f elf32 -o build/host_mkfs_tinyfs.o build/host_mkfs_tinyfs.S build/toolchain/ld -o build/mkfs_tinyfs build/host_tinyfs.o build/host_string.o build/host_mkfs_tinyfs.o dd if=/dev/zero of=build/disk.img bs=512 count=4096 status=none dd if=build/boot.bin of=build/disk.img conv=notrunc status=none dd if=build/stage2.bin of=build/disk.img bs=512 seek=1 conv=notrunc status=none dd if=build/kernel.bin of=build/disk.img bs=512 seek=5 conv=notrunc status=none build/mkfs_tinyfs build/disk.img tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc mkfs_tinyfs: done rm build/console.S build/shell/shell.S build/idt.S build/lang/script/parser.S build/lang/script/ast.S build/lang/script/eval.S build/lang/vm.S build/lang/compiler/codegen.S build/lang/compiler/parser.S build/kmain.S build/gdt.S build/string.S build/fs/tinyfs.S build/selftest.S build/serial.S build/lang/lexer.S build/fs/tinyfs_disk.S build/ata.S build/keyboard.S build/memory.S
$ cd /home/martin/workspace/prototypes/boot-to-claude ./tools/run_tests.sh 2>&1 | tail -80
run_tests: FAIL - timed out waiting for 'SELFTEST PASS' --- serial log --- tiny-os: kernel entered EXCEPTION vector=6 error_code=0 - halted qemu-system-i386: terminating on signal 15 from pid 3330290 (bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "EXCEPTION\|isr_handler" kernel/idt.c | head -10
48:void isr_handler(uint32_t vector, uint32_t error_code) {
51: console_write("\nEXCEPTION vector=");
57: serial_write("\nEXCEPTION vector=");$ cd /home/martin/workspace/prototypes/boot-to-claude rm -f /tmp/qemu_debug2.log timeout 5 qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -d int -D /tmp/qemu_debug2.log 2>&1 grep -n "v=06" /tmp/qemu_debug2.log | tail -5
qemu-system-i386: terminating on signal 15 from pid 3330706 (timeout) 422: 0: v=06 e=0000 i=0 cpl=0 IP=0008:00000003 pc=00000003 SP=0010:00020004 env->regs[R_EAX]=ffffffff
$ cd /home/martin/workspace/prototypes/boot-to-claude cat kernel/kmain.c
#include "console.h"
#include "fs/tinyfs_disk.h"
#include "gdt.h"
#include "idt.h"
#include "memory.h"
#include "selftest.h"
#include "serial.h"
#include "shell/shell.h"
void kmain(void) {
serial_init();
console_clear();
gdt_init();
idt_init();
memory_init();
console_write("tiny-os: kernel entered\n");
serial_write("tiny-os: kernel entered\n");
if (tinyfs_disk_init() != 0) {
console_write("tinyfs: mount FAILED\n");
serial_write("tinyfs: mount FAILED\n");
}
selftest_run();
shell_run();
}$ cd /home/martin/workspace/prototypes/boot-to-claude cat kernel/fs/tinyfs_disk.c
#include "tinyfs_disk.h"
#include "../ata.h"
#include "tinyfs.h"
static void disk_block_read(uint32_t block, void *buf) {
ata_read_sector(TINYFS_FS_START_LBA + block, buf);
}
static void disk_block_write(uint32_t block, const void *buf) {
ata_write_sector(TINYFS_FS_START_LBA + block, buf);
}
int tinyfs_disk_init(void) {
tinyfs_init(disk_block_read, disk_block_write);
return tinyfs_mount();
}$ cd /home/martin/workspace/prototypes/boot-to-claude build/toolchain/cc -Ikernel -Itoolchain/include -o /tmp/tinyfs_disk.S kernel/fs/tinyfs_disk.c cat /tmp/tinyfs_disk.S
BITS 32
extern ata_read_sector
extern ata_write_sector
extern tinyfs_init
extern tinyfs_mount
section .text
disk_block_read:
push ebp
mov ebp, esp
lea eax, [ebp+12]
mov eax, [eax]
push eax
lea eax, [ebp+8]
mov eax, [eax]
push eax
mov eax, 640
pop ecx
add eax, ecx
push eax
call ata_read_sector
add esp, 8
mov eax, 0
mov esp, ebp
pop ebp
ret
disk_block_write:
push ebp
mov ebp, esp
lea eax, [ebp+12]
mov eax, [eax]
push eax
lea eax, [ebp+8]
mov eax, [eax]
push eax
mov eax, 640
pop ecx
add eax, ecx
push eax
call ata_write_sector
add esp, 8
mov eax, 0
mov esp, ebp
pop ebp
ret
global tinyfs_disk_init
tinyfs_disk_init:
push ebp
mov ebp, esp
lea eax, [disk_block_write]
push eax
lea eax, [disk_block_read]
push eax
call tinyfs_init
add esp, 8
call tinyfs_mount
mov esp, ebp
pop ebp
ret
mov eax, 0
mov esp, ebp
pop ebp
ret$ cd /home/martin/workspace/prototypes/boot-to-claude build/toolchain/cc -Ikernel -Itoolchain/include -o /tmp/tinyfs_kernel.S kernel/fs/tinyfs.c grep -n "^tinyfs_init:\|call eax\|block_read\|block_write" /tmp/tinyfs_kernel.S | head -30
2:extern block_read 3:extern block_write 9:tinyfs_init: 12: lea eax, [block_read] 18: lea eax, [block_write] 66: lea eax, [block_read] 68: call eax 122: lea eax, [block_write] 124: call eax 188: lea eax, [block_read] 190: call eax 254: lea eax, [block_write] 256: call eax 752: lea eax, [block_write] 754: call eax 775: lea eax, [block_read] 777: call eax 1279: lea eax, [block_write] 1281: call eax 1440: lea eax, [block_read] 1442: call eax 1660:global block_read 1661:block_read: 1663:global block_write 1664:block_write:
614 g_strings[g_n_strings++] = n;
615 }
616 /* static locals live inside a function body's statement list, not
617 * prog's top-level chain - the .data/.bss emission below only scans
618 * top-level decls, so without this they'd be referenced (lea eax,
619 * [__static_N]) but never actually allocated storage. */
620 if (n->kind == ND_VAR_DECL && n->is_static_local && g_n_statics < MAX_STATICS) {
621 g_statics[g_n_statics++] = n;
622 }
623 walk_collect_ex(n->a, prog, 0);602 static void walk_collect_ex(node_t *n, node_t *prog, int skip_next) {
603 if (!n) return;
604 if (n->kind == ND_CALL) {
605 int is_local = 0;
606 for (node_t *f = prog; f; f = f->next) {
607 if (f->kind == ND_FUNC_DEF && strcmp(f->name, n->name) == 0) { is_local = 1; break; }
608 }
609 if (!is_local && !is_builtin_name(n->name) && !already_extern(n->name) && g_n_externs < MAX_EXTERNS) {
610 safe_strcpy(g_externs[g_n_externs++], n->name, MAX_NAME);
611 }
612 }
613 if (n->kind == ND_STR && g_n_strings < MAX_STRINGS) {
614 g_strings[g_n_strings++] = n;
615 }- if (n->kind == ND_CALL) {
- int is_local = 0;
- for (node_t *f = prog; f; f = f->next) {
- if (f->kind == ND_FUNC_DEF && strcmp(f->name, n->name) == 0) { is_local = 1; break; }
- }
- if (!is_local && !is_builtin_name(n->name) && !already_extern(n->name) && g_n_externs < MAX_EXTERNS) {
- safe_strcpy(g_externs[g_n_externs++], n->name, MAX_NAME);
- }
- }
+ /* is_indirect_call: the callee is a variable (see cc.h/parser.c),
+ * not a function symbol at all - e.g. tinyfs.c's `block_read(...)`
+ * calls through a function-pointer global. Its linkage is handled
+ * entirely by the ordinary global/extern-variable machinery
+ * (is_extern_decl below); treating n->name as a possibly-external
+ * *function* name here as well double-declared it (`extern
+ * block_read` *and* a real local `global block_read` definition in
+ * the same file), which left AS unable to tell which one a
+ * `lea eax, [block_read]` reference should resolve to. */
+ if (n->kind == ND_CALL && !n->is_indirect_call) {
+ int is_local = 0;
+ for (node_t *f = prog; f; f = f->next) {
+ if (f->kind == ND_FUNC_DEF && strcmp(f->name, n->name) == 0) { is_local = 1; break; }
+ }
+ if (!is_local && !is_builtin_name(n->name) && !already_extern(n->name) && g_n_externs < MAX_EXTERNS) {
+ safe_strcpy(g_externs[g_n_externs++], n->name, MAX_NAME);
+ }
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -o build/toolchain/cc toolchain/cc/*.c -Wall 2>&1 | tail -40 && ./tools/run_toolchain_tests.sh 2>&1 | tail -12 build/toolchain/cc -Ikernel -Itoolchain/include -o /tmp/tinyfs_kernel2.S kernel/fs/tinyfs.c grep -n "extern block_read\|extern block_write" /tmp/tinyfs_kernel2.S echo "exit=$?"
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) run_toolchain_tests: PASS exit=1
$ cd /home/martin/workspace/prototypes/boot-to-claude make clean make 2>&1 | tail -20
rm -rf build build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/compiler/codegen.S kernel/lang/compiler/codegen.c build/toolchain/as -f elf32 -o build/lang/compiler/codegen.o build/lang/compiler/codegen.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/vm.S kernel/lang/vm.c build/toolchain/as -f elf32 -o build/lang/vm.o build/lang/vm.S gcc -Wall -Wextra -O2 -o build/toolchain/ld toolchain/ld/elf_reader.c toolchain/ld/link_flat.c toolchain/ld/link_static_exec.c toolchain/ld/main.c build/toolchain/ld -T kernel/link.ld --format bin -o build/kernel.bin build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_tinyfs.S kernel/fs/tinyfs.c build/toolchain/as -f elf32 -o build/host_tinyfs.o build/host_tinyfs.S build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_string.S kernel/string.c build/toolchain/as -f elf32 -o build/host_string.o build/host_string.S build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_mkfs_tinyfs.S tools/mkfs_tinyfs.c build/toolchain/as -f elf32 -o build/host_mkfs_tinyfs.o build/host_mkfs_tinyfs.S build/toolchain/ld -o build/mkfs_tinyfs build/host_tinyfs.o build/host_string.o build/host_mkfs_tinyfs.o dd if=/dev/zero of=build/disk.img bs=512 count=4096 status=none dd if=build/boot.bin of=build/disk.img conv=notrunc status=none dd if=build/stage2.bin of=build/disk.img bs=512 seek=1 conv=notrunc status=none dd if=build/kernel.bin of=build/disk.img bs=512 seek=5 conv=notrunc status=none build/mkfs_tinyfs build/disk.img tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc mkfs_tinyfs: done rm build/console.S build/shell/shell.S build/idt.S build/lang/script/parser.S build/lang/script/ast.S build/lang/script/eval.S build/lang/vm.S build/lang/compiler/codegen.S build/lang/compiler/parser.S build/kmain.S build/gdt.S build/string.S build/fs/tinyfs.S build/selftest.S build/serial.S build/lang/lexer.S build/fs/tinyfs_disk.S build/ata.S build/keyboard.S build/memory.S
$ cd /home/martin/workspace/prototypes/boot-to-claude ./tools/run_tests.sh 2>&1 | tail -80
run_tests: FAIL - timed out waiting for 'SELFTEST PASS' --- serial log --- tiny-os: kernel entered EXCEPTION vector=6 error_code=0 - halted qemu-system-i386: terminating on signal 15 from pid 3336832 (bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude rm -f /tmp/qemu_debug3.log timeout 5 qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -d int -D /tmp/qemu_debug3.log 2>&1 grep -n "v=06" /tmp/qemu_debug3.log | tail -3
qemu-system-i386: terminating on signal 15 from pid 3337184 (timeout) 422: 0: v=06 e=0000 i=0 cpl=0 IP=0008:00000003 pc=00000003 SP=0010:00020004 env->regs[R_EAX]=ffffffff
$ cd /home/martin/workspace/prototypes/boot-to-claude readelf -s build/fs/tinyfs.o 2>/dev/null | grep -i "block_read\|block_write"
102: 00000000 0 NOTYPE GLOBAL DEFAULT 2 block_read 103: 00000004 0 NOTYPE GLOBAL DEFAULT 2 block_write
$ cd /home/martin/workspace/prototypes/boot-to-claude sed -n '1,30p' /tmp/tinyfs_kernel2.S
BITS 32
extern strcmp
extern memset
extern memcpy
section .text
global tinyfs_init
tinyfs_init:
push ebp
mov ebp, esp
lea eax, [block_read]
push eax
lea eax, [ebp+8]
mov eax, [eax]
pop ecx
mov [ecx], eax
lea eax, [block_write]
push eax
lea eax, [ebp+12]
mov eax, [eax]
pop ecx
mov [ecx], eax
mov eax, 0
mov esp, ebp
pop ebp
ret
load_bitmap:
push ebp
mov ebp, esp
sub esp, 4
lea eax, [ebp-4]$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "static node_t \*parse_global_var" toolchain/cc/parser.c
932:static node_t *parse_global_var(parser_t *p, const char *name, type_t *t, int is_extern) {932 static node_t *parse_global_var(parser_t *p, const char *name, type_t *t, int is_extern) {
933 add_global(p, name, t);
934 node_t *n = node_new(ND_VAR_DECL);
935 safe_strcpy(n->name, name, MAX_NAME);
936 n->type = t;
937 n->is_global = 1;
938 n->is_extern_decl = is_extern;
939 safe_strcpy(n->asm_label, name, MAX_NAME);
940 if (match(p, TK_ASSIGN)) n->init = parse_initializer(p);
941 return n;
942 }
943
944 static node_t *parse_func(parser_t *p, const char *name, type_t *functy,
945 char param_names[][MAX_NAME], int n_params, int is_static) {
946 sym_t *fs = add_global(p, name, functy);- int is_global; - int is_static_local; /* function-local `static` - lives in .data/.bss, own label */ + int is_global; + int is_static_local; /* function-local `static` - lives in .data/.bss, own label */ + int is_file_static; /* file-scope `static T name;` - still needs storage (is_global + * stays 1 for that purpose) but must not get a `global` directive: + * two files each with their own file-private `static int foo;` + * would otherwise collide as duplicate global symbols at link time. */
The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/cc.h has been updated successfully. (file state is current in your context — no need to Read it back)
- static node_t *parse_global_var(parser_t *p, const char *name, type_t *t, int is_extern) {
- add_global(p, name, t);
- node_t *n = node_new(ND_VAR_DECL);
- safe_strcpy(n->name, name, MAX_NAME);
- n->type = t;
- n->is_global = 1;
- n->is_extern_decl = is_extern;
- safe_strcpy(n->asm_label, name, MAX_NAME);
- if (match(p, TK_ASSIGN)) n->init = parse_initializer(p);
- return n;
- }
+ static node_t *parse_global_var(parser_t *p, const char *name, type_t *t, int is_extern, int is_static) {
+ add_global(p, name, t);
+ node_t *n = node_new(ND_VAR_DECL);
+ safe_strcpy(n->name, name, MAX_NAME);
+ n->type = t;
+ n->is_global = 1;
+ n->is_extern_decl = is_extern;
+ n->is_file_static = is_static;
+ safe_strcpy(n->asm_label, name, MAX_NAME);
+ if (match(p, TK_ASSIGN)) n->init = parse_initializer(p);
+ return n;
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/parser.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude
grep -n "parse_global_var(" toolchain/cc/parser.c932:static node_t *parse_global_var(parser_t *p, const char *name, type_t *t, int is_extern, int is_static) {
1029: decl = parse_global_var(&p, name, t, is_extern);
1033: node_t *more = parse_global_var(&p, name2, t2, is_extern);1015 decl = parse_typedef(&p);
1016 } else {
1017 int is_static = 0, is_extern = 0;
1018 type_t *base = parse_type_spec(&p, &is_static, &is_extern);
1019 if (match(&p, TK_SEMI)) {
1020 decl = node_new(ND_NOP); /* lone `struct Foo { ... };` with no variable */
1021 } else {
1022 char param_names[16][MAX_NAME];
1023 int n_params = 0;
1024 char name[MAX_NAME];
1025 type_t *t = parse_declarator(&p, base, name, param_names, &n_params);
1026 if (t->kind == TY_FUNC) {
1027 decl = parse_func(&p, name, t, param_names, n_params, is_static);
1028 } else {
1029 decl = parse_global_var(&p, name, t, is_extern);
1030 while (match(&p, TK_COMMA)) {
1031 char name2[MAX_NAME];
1032 type_t *t2 = parse_declarator(&p, base, name2, NULL, NULL);
1033 node_t *more = parse_global_var(&p, name2, t2, is_extern);
1034 decl->next = more;
1035 }
1036 expect(&p, TK_SEMI);
1037 }
1038 }
1039 }- decl = parse_global_var(&p, name, t, is_extern);
- while (match(&p, TK_COMMA)) {
- char name2[MAX_NAME];
- type_t *t2 = parse_declarator(&p, base, name2, NULL, NULL);
- node_t *more = parse_global_var(&p, name2, t2, is_extern);
- decl->next = more;
- }
+ decl = parse_global_var(&p, name, t, is_extern, is_static);
+ while (match(&p, TK_COMMA)) {
+ char name2[MAX_NAME];
+ type_t *t2 = parse_declarator(&p, base, name2, NULL, NULL);
+ node_t *more = parse_global_var(&p, name2, t2, is_extern, is_static);
+ decl->next = more;
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/parser.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "n->is_global && !n->is_extern_decl" toolchain/cc/codegen.c
729: if (n->kind == ND_VAR_DECL && n->is_global && !n->is_extern_decl) {
740: if (n->kind == ND_VAR_DECL && n->is_global && !n->is_extern_decl && n->init) {
758: if (n->kind == ND_VAR_DECL && n->is_global && !n->is_extern_decl && !n->init) {736
737 if (any_data) {
738 out("section .data\n");
739 for (node_t *n = prog; n; n = n->next) {
740 if (n->kind == ND_VAR_DECL && n->is_global && !n->is_extern_decl && n->init) {
741 out("global %s\n%s:\n", n->asm_label, n->asm_label);
742 emit_data_bytes(n->init, n->type);
743 }
744 }
745 for (int i = 0; i < g_n_statics; i++) {
746 /* Not `global`, same reasoning as __str_N above -
747 * __static_N is only numbered uniquely within one file. */
748 node_t *n = g_statics[i];
749 if (n->init) {
750 out("%s:\n", n->asm_label);
751 emit_data_bytes(n->init, n->type);
752 }
753 }
754 }
755 if (any_bss) {
756 out("section .bss\n");
757 for (node_t *n = prog; n; n = n->next) {
758 if (n->kind == ND_VAR_DECL && n->is_global && !n->is_extern_decl && !n->init) {
759 out("global %s\n%s:\n resb %d\n", n->asm_label, n->asm_label, type_size(n->type));
760 }
761 }
762 for (int i = 0; i < g_n_statics; i++) {
763 node_t *n = g_statics[i];- if (n->kind == ND_VAR_DECL && n->is_global && !n->is_extern_decl && n->init) {
- out("global %s\n%s:\n", n->asm_label, n->asm_label);
- emit_data_bytes(n->init, n->type);
- }
+ if (n->kind == ND_VAR_DECL && n->is_global && !n->is_extern_decl && n->init) {
+ /* file-scope `static` keeps internal linkage - no `global`,
+ * same reasoning as function-local statics below (two
+ * files' own file-private `static T name;` must not
+ * collide as duplicate global symbols). */
+ if (!n->is_file_static) out("global %s\n", n->asm_label);
+ out("%s:\n", n->asm_label);
+ emit_data_bytes(n->init, n->type);
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
- if (n->kind == ND_VAR_DECL && n->is_global && !n->is_extern_decl && !n->init) {
- out("global %s\n%s:\n resb %d\n", n->asm_label, n->asm_label, type_size(n->type));
- }
+ if (n->kind == ND_VAR_DECL && n->is_global && !n->is_extern_decl && !n->init) {
+ if (!n->is_file_static) out("global %s\n", n->asm_label);
+ out("%s:\n resb %d\n", n->asm_label, type_size(n->type));
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -o build/toolchain/cc toolchain/cc/*.c -Wall 2>&1 | tail -40 && ./tools/run_toolchain_tests.sh 2>&1 | tail -12
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) run_toolchain_tests: PASS
$ cd /home/martin/workspace/prototypes/boot-to-claude build/toolchain/cc -Ikernel -Itoolchain/include -o /tmp/tinyfs_kernel3.S kernel/fs/tinyfs.c grep -n "global block_read\|global block_write\|^block_read:\|^block_write:" /tmp/tinyfs_kernel3.S
1658:block_read: 1660:block_write:
$ cd /home/martin/workspace/prototypes/boot-to-claude make clean make 2>&1 | tail -20
rm -rf build build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/compiler/codegen.S kernel/lang/compiler/codegen.c build/toolchain/as -f elf32 -o build/lang/compiler/codegen.o build/lang/compiler/codegen.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/vm.S kernel/lang/vm.c build/toolchain/as -f elf32 -o build/lang/vm.o build/lang/vm.S gcc -Wall -Wextra -O2 -o build/toolchain/ld toolchain/ld/elf_reader.c toolchain/ld/link_flat.c toolchain/ld/link_static_exec.c toolchain/ld/main.c build/toolchain/ld -T kernel/link.ld --format bin -o build/kernel.bin build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_tinyfs.S kernel/fs/tinyfs.c build/toolchain/as -f elf32 -o build/host_tinyfs.o build/host_tinyfs.S build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_string.S kernel/string.c build/toolchain/as -f elf32 -o build/host_string.o build/host_string.S build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_mkfs_tinyfs.S tools/mkfs_tinyfs.c build/toolchain/as -f elf32 -o build/host_mkfs_tinyfs.o build/host_mkfs_tinyfs.S build/toolchain/ld -o build/mkfs_tinyfs build/host_tinyfs.o build/host_string.o build/host_mkfs_tinyfs.o dd if=/dev/zero of=build/disk.img bs=512 count=4096 status=none dd if=build/boot.bin of=build/disk.img conv=notrunc status=none dd if=build/stage2.bin of=build/disk.img bs=512 seek=1 conv=notrunc status=none dd if=build/kernel.bin of=build/disk.img bs=512 seek=5 conv=notrunc status=none build/mkfs_tinyfs build/disk.img tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc mkfs_tinyfs: done rm build/console.S build/shell/shell.S build/idt.S build/lang/script/parser.S build/lang/script/ast.S build/lang/script/eval.S build/lang/vm.S build/lang/compiler/codegen.S build/lang/compiler/parser.S build/kmain.S build/gdt.S build/string.S build/fs/tinyfs.S build/selftest.S build/serial.S build/lang/lexer.S build/fs/tinyfs_disk.S build/ata.S build/keyboard.S build/memory.S
$ cd /home/martin/workspace/prototypes/boot-to-claude ./tools/run_tests.sh 2>&1 | tail -80
run_tests: FAIL - timed out waiting for 'SELFTEST PASS' --- serial log --- tiny-os: kernel entered EXCEPTION vector=6 error_code=0 - halted qemu-system-i386: terminating on signal 15 from pid 3340174 (bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude nm build/mkfs_tinyfs 2>/dev/null | head -1 # get kernel symbols via a debug ELF link KOBJS="build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o" build/toolchain/ld -T kernel/link.ld -o /tmp/kernel_dbg.elf $KOBJS nm /tmp/kernel_dbg.elf | grep -i "tinyfs_disk_init\|tinyfs_mount\|tinyfs_init\b"
00013989 T tinyfs_disk_init 00012614 T tinyfs_init 00012e63 T tinyfs_mount
$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_kernel.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x12e63 continue stepi 30 info registers eax ecx edx quit EOF timeout 30 gdb -q -batch -x /tmp/gdb_kernel.txt 2>&1 | tail -60 kill $QPID 2>/dev/null
The target architecture is set to "i386". warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000fff0 in ?? () Breakpoint 1 at 0x12e63 Breakpoint 1, 0x00012e63 in ?? () 0x00012288 in ?? () eax 0x80 128 ecx 0x0 0 edx 0x3f8 1016 A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached] qemu-system-i386: terminating on signal 15 from pid 3340959 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude nm /tmp/kernel_dbg.elf | grep -i "ata_read_sector\|disk_block_read\|block_read\b"
00012418 T ata_read_sector
$ cd /home/martin/workspace/prototypes/boot-to-claude nm /tmp/kernel_dbg.elf | grep -i "block_read\|block_write"
(Bash completed with no output)
$ cd /home/martin/workspace/prototypes/boot-to-claude readelf -s build/fs/tinyfs.o 2>/dev/null | grep -E "tinyfs_init\b|block_read|block_write|tinyfs_mount\b"
42: 000008c7 0 NOTYPE LOCAL DEFAULT 1 tinyfs_mount.L31
43: 000008c7 0 NOTYPE LOCAL DEFAULT 1 tinyfs_mount.L32
91: 00000000 0 NOTYPE LOCAL DEFAULT 2 block_read
92: 00000004 0 NOTYPE LOCAL DEFAULT 2 block_write
99: 00000000 0 NOTYPE GLOBAL DEFAULT 1 tinyfs_init
101: 0000084f 0 NOTYPE GLOBAL DEFAULT 1 tinyfs_mount$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_kernel2.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x12614 continue stepi 3 info registers eax x/1xw $eax stepi 4 info registers eax ecx stepi 4 info registers eax ecx quit EOF timeout 30 gdb -q -batch -x /tmp/gdb_kernel2.txt 2>&1 | tail -50 kill $QPID 2>/dev/null
The target architecture is set to "i386". warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000fff0 in ?? () Breakpoint 1 at 0x12614 Breakpoint 1, 0x00012614 in ?? () 0x0001261d in ?? () eax 0x2412c 147756 0x2412c: 0x00000000 0x00012627 in ?? () eax 0x13925 80165 ecx 0x2412c 147756 0x00012636 in ?? () eax 0x238ec 145644 ecx 0x2412c 147756 A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached] qemu-system-i386: terminating on signal 15 from pid 3343784 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_kernel3.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x12e63 continue x/2xw 0x2412c quit EOF timeout 30 gdb -q -batch -x /tmp/gdb_kernel3.txt 2>&1 | tail -20 kill $QPID 2>/dev/null
The target architecture is set to "i386". warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000fff0 in ?? () Breakpoint 1 at 0x12e63 Breakpoint 1, 0x00012e63 in ?? () 0x2412c: 0x00013925 0x00013957 A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached] qemu-system-i386: terminating on signal 15 from pid 3344010 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_kernel4.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x12e63 continue x/8i 0x13925 x/8i 0x13957 quit EOF timeout 30 gdb -q -batch -x /tmp/gdb_kernel4.txt 2>&1 | tail -25 kill $QPID 2>/dev/null
Breakpoint 1 at 0x12e63 Breakpoint 1, 0x00012e63 in ?? () 0x13925: push %ebp 0x13926: mov %esp,%ebp 0x13928: lea 0xc(%ebp),%eax 0x1392e: mov (%eax),%eax 0x13930: push %eax 0x13931: lea 0x8(%ebp),%eax 0x13937: mov (%eax),%eax 0x13939: push %eax 0x13957: push %ebp 0x13958: mov %esp,%ebp 0x1395a: lea 0xc(%ebp),%eax 0x13960: mov (%eax),%eax 0x13962: push %eax 0x13963: lea 0x8(%ebp),%eax 0x13969: mov (%eax),%eax 0x1396b: push %eax A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached] qemu-system-i386: terminating on signal 15 from pid 3344134 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_kernel5.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x12e63 continue break *0x13925 continue info registers eip quit EOF timeout 30 gdb -q -batch -x /tmp/gdb_kernel5.txt 2>&1 | tail -30 kill $QPID 2>/dev/null
The target architecture is set to "i386". warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000fff0 in ?? () Breakpoint 1 at 0x12e63 Breakpoint 1, 0x00012e63 in ?? () Breakpoint 2 at 0x13925 Breakpoint 2, 0x00013925 in ?? () eip 0x13925 0x13925 A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached] qemu-system-i386: terminating on signal 15 from pid 3344435 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_kernel6.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x12418 continue info registers eip continue info registers eip continue info registers eip continue info registers eip continue info registers eip continue info registers eip quit EOF timeout 30 gdb -q -batch -x /tmp/gdb_kernel6.txt 2>&1 | tail -60 kill $QPID 2>/dev/null
Terminated qemu-system-i386: terminating on signal 15 from pid 3344610 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_kernel7.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x12418 continue info registers eip finish info registers eip continue info registers eip quit EOF timeout 15 gdb -q -batch -x /tmp/gdb_kernel7.txt 2>&1 | tail -60 kill $QPID 2>/dev/null
Terminated qemu-system-i386: terminating on signal 15 from pid 3345496 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude nm /tmp/kernel_dbg.elf | grep -i "isr_handler\|isr_stub_table"
00010d38 T isr_handler 0001f790 T isr_stub_table
$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_kernel8.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x10d38 continue info registers x/8xw $ebp bt quit EOF timeout 20 gdb -q -batch -x /tmp/gdb_kernel8.txt 2>&1 | tail -60 kill $QPID 2>/dev/null
The target architecture is set to "i386".
warning: No executable has been specified and target does not support
determining executable automatically. Try using the "file" command.
0x0000fff0 in ?? ()
Breakpoint 1 at 0x10d38
Breakpoint 1, 0x00010d38 in ?? ()
eax 0x6 6
ecx 0x54465331 1413894961
edx 0xf 15
ebx 0x0 0
esp 0x1ffe4 0x1ffe4
ebp 0x0 0x0
esi 0x7ecc 32460
edi 0x2d0d8 184536
eip 0x10d38 0x10d38
eflags 0x3 [ IOPL=0 CF ]
cs 0x8 8
ss 0x10 16
ds 0x10 16
es 0x10 16
fs 0x10 16
gs 0x10 16
fs_base 0x0 0
gs_base 0x0 0
k_gs_base 0x0 0
cr0 0x11 [ ET PE ]
cr2 0x0 0
cr3 0x0 [ PDBR=0 PCID=0 ]
cr4 0x0 [ ]
cr8 0x0 0
efer 0x0 [ ]
xmm0 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x0}
xmm1 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x0}
xmm2 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x0}
xmm3 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x0}
xmm4 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x0}
xmm5 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x0}
xmm6 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x0}
xmm7 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x0}
mxcsr 0x1f80 [ IM DM ZM OM UM PM ]
0x0: 0xf000ff53 0xf000ff53 0xf000e2c3 0xf000ff53
0x10: 0xf000ff53 0xf000ff54 0xf000ff53 0xf000ff53
#0 0x00010d38 in ?? ()
A debugging session is active.
Inferior 1 [process 1] will be detached.
Quit anyway? (y or n) [answered Y; input not from terminal]
[Inferior 1 (process 1) detached]
qemu-system-i386: terminating on signal 15 from pid 3346172 (/bin/bash)$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "TINYFS_MAGIC" kernel/fs/tinyfs.c
96: sb.magic = TINYFS_MAGIC;
115: if (sb.magic != TINYFS_MAGIC) {108 return 0;
109 }
110
111 int tinyfs_mount(void) {
112 uint8_t sbbuf[TINYFS_BLOCK_SIZE];
113 block_read(TINYFS_SB_BLOCK, sbbuf);
114 memcpy(&sb, sbbuf, sizeof(sb));
115 if (sb.magic != TINYFS_MAGIC) {
116 return -1;
117 }
118 load_bitmap();
119 load_inodes();
120 return 0;
121 }
122
123 int tinyfs_create(const char *name) {
124 if (find_inode(name)) {
125 return -1;
126 }
127 int idx = find_free_inode();$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "0x\|hex\|isxdigit\|TK_NUM" toolchain/cc/lexer.c | head -20
97: token_t t = mk(lx, TK_NUM);
75 int was_bol = lx->bol;
76 lx->bol = 0;
77 int c = peekc(lx);
78 if (c == -1) { token_t t = mk(lx, TK_EOF); t.bol = was_bol; return t; }
79
80 if (c == '#' && was_bol) { lx->pos++; token_t t = mk(lx, TK_HASH); t.bol = 1; return t; }
81
82 if (isalpha(c) || c == '_') {
83 size_t start = lx->pos;
84 while (isalnum(peekc(lx)) || peekc(lx) == '_') lx->pos++;
85 token_t t = mk(lx, TK_IDENT);
86 t.bol = was_bol;
87 size_t n = lx->pos - start;
88 if (n >= sizeof(t.text)) n = sizeof(t.text) - 1;
89 memcpy(t.text, lx->src + start, n);
90 t.text[n] = '\0';
91 return t;
92 }
93
94 if (isdigit(c)) {
95 size_t start = lx->pos;
96 while (isalnum(peekc(lx))) lx->pos++;
97 token_t t = mk(lx, TK_NUM);
98 t.bol = was_bol;
99 int len = (int)(lx->pos - start);
100 const char *p = lx->src + start;
101 if (len > 2 && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
102 t.num = strtoll(p + 2, NULL, 16);
103 } else {
104 t.num = strtoll(p, NULL, 10);$ cd /home/martin/workspace/prototypes/boot-to-claude objdump -d -M intel /tmp/kernel_dbg.elf 2>&1 | grep -A80 "<tinyfs_mount>:" | head -100
00012e63 <tinyfs_mount>: 12e63: 55 push ebp 12e64: 8b ec mov ebp,esp 12e66: 81 ec 00 02 00 00 sub esp,0x200 12e6c: 8d 85 00 fe ff ff lea eax,[ebp-0x200] 12e72: 50 push eax 12e73: b8 00 00 00 00 mov eax,0x0 12e78: 50 push eax 12e79: 8d 05 2c 41 02 00 lea eax,ds:0x2412c 12e7f: 8b 00 mov eax,DWORD PTR [eax] 12e81: ff d0 call eax 12e83: 81 c4 08 00 00 00 add esp,0x8 12e89: b8 10 00 00 00 mov eax,0x10 12e8e: 50 push eax 12e8f: 8d 85 00 fe ff ff lea eax,[ebp-0x200] 12e95: 50 push eax 12e96: 8d 05 34 41 02 00 lea eax,ds:0x24134 12e9c: 50 push eax 12e9d: e8 c8 df ff ff call 10e6a <memcpy> 12ea2: 81 c4 0c 00 00 00 add esp,0xc 12ea8: b8 31 53 46 54 mov eax,0x54465331 12ead: 50 push eax 12eae: 8d 05 34 41 02 00 lea eax,ds:0x24134 12eb4: 8b 00 mov eax,DWORD PTR [eax] 12eb6: 59 pop ecx 12eb7: 3b c1 cmp eax,ecx 12eb9: 0f 95 c0 setne al 12ebc: 0f b6 c0 movzx eax,al 12ebf: 81 f8 00 00 00 00 cmp eax,0x0 12ec5: 0f 84 10 00 00 00 je 12edb <tinyfs_mount+0x78> 12ecb: b8 01 00 00 00 mov eax,0x1 12ed0: f7 d8 neg eax 12ed2: 8b e5 mov esp,ebp 12ed4: 5d pop ebp 12ed5: c3 ret 12ed6: e9 00 00 00 00 jmp 12edb <tinyfs_mount+0x78> 12edb: e8 64 f7 ff ff call 12644 <tinyfs_init+0x30> 12ee0: e8 9b f8 ff ff call 12780 <tinyfs_init+0x16c> 12ee5: b8 00 00 00 00 mov eax,0x0 12eea: 8b e5 mov esp,ebp 12eec: 5d pop ebp 12eed: c3 ret 12eee: b8 00 00 00 00 mov eax,0x0 12ef3: 8b e5 mov esp,ebp 12ef5: 5d pop ebp 12ef6: c3 ret 00012ef7 <tinyfs_create>: 12ef7: 55 push ebp 12ef8: 8b ec mov ebp,esp 12efa: 81 ec 0c 00 00 00 sub esp,0xc 12f00: 8d 85 08 00 00 00 lea eax,[ebp+0x8] 12f06: 8b 00 mov eax,DWORD PTR [eax] 12f08: 50 push eax 12f09: e8 58 fc ff ff call 12b66 <tinyfs_init+0x552> 12f0e: 81 c4 04 00 00 00 add esp,0x4 12f14: 81 f8 00 00 00 00 cmp eax,0x0 12f1a: 0f 84 10 00 00 00 je 12f30 <tinyfs_create+0x39> 12f20: b8 01 00 00 00 mov eax,0x1 12f25: f7 d8 neg eax 12f27: 8b e5 mov esp,ebp 12f29: 5d pop ebp 12f2a: c3 ret 12f2b: e9 00 00 00 00 jmp 12f30 <tinyfs_create+0x39> 12f30: 8d 85 fc ff ff ff lea eax,[ebp-0x4] 12f36: 50 push eax 12f37: e8 43 fd ff ff call 12c7f <tinyfs_init+0x66b> 12f3c: 59 pop ecx 12f3d: 89 01 mov DWORD PTR [ecx],eax 12f3f: b8 00 00 00 00 mov eax,0x0 12f44: 50 push eax 12f45: 8d 85 fc ff ff ff lea eax,[ebp-0x4] 12f4b: 8b 00 mov eax,DWORD PTR [eax] 12f4d: 59 pop ecx 12f4e: 3b c1 cmp eax,ecx 12f50: 0f 9c c0 setl al 12f53: 0f b6 c0 movzx eax,al 12f56: 81 f8 00 00 00 00 cmp eax,0x0 12f5c: 0f 84 10 00 00 00 je 12f72 <tinyfs_create+0x7b> 12f62: b8 01 00 00 00 mov eax,0x1 12f67: f7 d8 neg eax
$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_kernel9.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x12e81 continue info registers eax stepi info registers eip quit EOF timeout 15 gdb -q -batch -x /tmp/gdb_kernel9.txt 2>&1 | tail -20 kill $QPID 2>/dev/null
The target architecture is set to "i386". warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000fff0 in ?? () Breakpoint 1 at 0x12e81 Breakpoint 1, 0x00012e81 in ?? () eax 0x13925 80165 0x00013925 in ?? () eip 0x13925 0x13925 A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached] qemu-system-i386: terminating on signal 15 from pid 3349468 ()
$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_kernel10.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x12ea8 continue info registers eax stepi 20 info registers eip eax ecx quit EOF timeout 15 gdb -q -batch -x /tmp/gdb_kernel10.txt 2>&1 | tail -30 kill $QPID 2>/dev/null
The target architecture is set to "i386". warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000fff0 in ?? () Breakpoint 1 at 0x12ea8 Breakpoint 1, 0x00012ea8 in ?? () eax 0x24134 147764 0x00000003 in ?? () eip 0x3 0x3 eax 0xffffffff -1 ecx 0x54465331 1413894961 A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached] qemu-system-i386: terminating on signal 15 from pid 3349573 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_kernel11.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x12ea8 continue stepi p/x $eip stepi p/x $eip stepi p/x $eip stepi p/x $eip stepi p/x $eip stepi p/x $eip stepi p/x $eip stepi p/x $eip stepi p/x $eip stepi p/x $eip quit EOF timeout 15 gdb -q -batch -x /tmp/gdb_kernel11.txt 2>&1 | tail -30 kill $QPID 2>/dev/null
0x0000fff0 in ?? () Breakpoint 1 at 0x12ea8 Breakpoint 1, 0x00012ea8 in ?? () 0x00012ead in ?? () $1 = 0x12ead 0x00012eae in ?? () $2 = 0x12eae 0x00012eb4 in ?? () $3 = 0x12eb4 0x00012eb6 in ?? () $4 = 0x12eb6 0x00012eb7 in ?? () $5 = 0x12eb7 0x00012eb9 in ?? () $6 = 0x12eb9 0x00012ebc in ?? () $7 = 0x12ebc 0x00012ebf in ?? () $8 = 0x12ebf 0x00012ec5 in ?? () $9 = 0x12ec5 0x00012ecb in ?? () $10 = 0x12ecb A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached] qemu-system-i386: terminating on signal 15 from pid 3350038 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude
python3 -c "
with open('build/disk.img','rb') as f:
f.seek(640*512)
data = f.read(32)
print(data.hex())
print(data)
"31534654800d000040000000760d000000000000000000000000000000000000 b'1SFT\x80\r\x00\x00@\x00\x00\x00v\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_kernel12.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x12eb7 continue info registers eax ecx quit EOF timeout 15 gdb -q -batch -x /tmp/gdb_kernel12.txt 2>&1 | tail -15 kill $QPID 2>/dev/null
The target architecture is set to "i386". warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000fff0 in ?? () Breakpoint 1 at 0x12eb7 Breakpoint 1, 0x00012eb7 in ?? () eax 0xd805331 226513713 ecx 0x54465331 1413894961 A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached] qemu-system-i386: terminating on signal 15 from pid 3352816 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude build/toolchain/cc -Ikernel -Itoolchain/include -o /tmp/tinyfs_check.S kernel/fs/tinyfs.c grep -n "1413894961\|54465331\|0x54465331" /tmp/tinyfs_check.S
689: mov eax, 1413894961 785: mov eax, 1413894961
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "atoi\|strtol\|isdigit" toolchain/as/lexer.c
92: if (isdigit(c)) {$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "parse_number" toolchain/as/lexer.c
33:static int64_t parse_number(const char *text, int len) {
98: t.number = parse_number(lx->src + start, n);33 static int64_t parse_number(const char *text, int len) {
34 if (len > 2 && text[0] == '0' && (text[1] == 'x' || text[1] == 'X')) {
35 int64_t v = 0;
36 for (int i = 2; i < len; i++) {
37 char c = text[i];
38 v *= 16;
39 if (c >= '0' && c <= '9') v += c - '0';
40 else if (c >= 'a' && c <= 'f') v += 10 + (c - 'a');
41 else if (c >= 'A' && c <= 'F') v += 10 + (c - 'A');
42 }
43 return v;
44 }
45 if (len > 1 && (text[len - 1] == 'b' || text[len - 1] == 'B')) {
46 int all01 = 1;
47 for (int i = 0; i < len - 1; i++) {
48 if (text[i] != '0' && text[i] != '1') { all01 = 0; break; }
49 }
50 if (all01) {
51 int64_t v = 0;
52 for (int i = 0; i < len - 1; i++) {
53 v = v * 2 + (text[i] - '0');
54 }
55 return v;
56 }
57 }
58 int64_t v = 0;
59 for (int i = 0; i < len; i++) {
60 v = v * 10 + (text[i] - '0');
61 }
62 return v;
63 }
64
65 token_t lexer_next(lexer_t *lx) {
66 for (;;) {
67 int c = peekc(lx);
68 if (c == ' ' || c == '\t' || c == '\r') { lx->pos++; continue; }
69 if (c == ';') {
70 while (peekc(lx) != -1 && peekc(lx) != '\n') lx->pos++;
71 continue;
72 }
73 break;
74 }
75
76 int c = peekc(lx);
77 if (c == -1) return make(TK_EOF);
78 if (c == '\n') { lx->pos++; return make(TK_NEWLINE); }
79
80 if (is_ident_start(c)) {
81 size_t start = lx->pos;
82 lx->pos++;$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "number" toolchain/as/as.h
127: int64_t number;
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n '"mov"' toolchain/as/encode.c | head -5
318: if (strcmp(m, "mov") == 0) {318 if (strcmp(m, "mov") == 0) {
319 if (is_sreg(a->reg) || is_sreg(b->reg)) {
320 reg_t sreg = is_sreg(a->reg) ? a->reg : b->reg;
321 e8(&e, 0x8E);
322 e8(&e, (uint8_t)(0xC0 | (regnum(sreg) << 3) | regnum(b->reg == sreg ? a->reg : b->reg)));
323 return e.len;
324 }
325 if (a->reg == R_CR0 && b->kind == OPND_REG && is_reg32(b->reg)) {
326 e8(&e, 0x0F); e8(&e, 0x22);
327 e8(&e, (uint8_t)(0xC0 | regnum(b->reg)));
328 return e.len;
329 }
330 if (b->reg == R_CR0 && a->kind == OPND_REG && is_reg32(a->reg)) {
331 /* MOV r32, CR0: modrm reg field = CR0 (0), rm field = dest reg */
332 e8(&e, 0x0F); e8(&e, 0x20);
333 e8(&e, (uint8_t)(0xC0 | regnum(a->reg)));
334 return e.len;
335 }
336 if (a->kind == OPND_REG && b->kind == OPND_IMM && is_reg8(a->reg)) {
337 e8(&e, (uint8_t)(0xB0 + regnum(a->reg)));
338 int64_t v = b->has_sym ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + 1, strict) : b->disp;
339 e8(&e, (uint8_t)v);
340 return e.len;
341 }
342 if (a->kind == OPND_REG && b->kind == OPND_IMM && is_reg16(a->reg) && st->bits == 16) {
343 e8(&e, (uint8_t)(0xB8 + regnum(a->reg)));
344 int64_t v = b->has_sym ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + 1, strict) : b->disp;
345 e16(&e, (uint16_t)v);
346 return e.len;
347 }
348 if (a->kind == OPND_REG && b->kind == OPND_IMM && is_reg16(a->reg) && st->bits == 32) {
349 e8(&e, 0x66);
350 e8(&e, (uint8_t)(0xB8 + regnum(a->reg)));
351 int64_t v = b->has_sym ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + 2, strict) : b->disp;
352 e16(&e, (uint16_t)v);
353 return e.len;
354 }
355 if (a->kind == OPND_REG && b->kind == OPND_IMM && is_reg32(a->reg)) {
356 e8(&e, (uint8_t)(0xB8 + regnum(a->reg)));
357 int64_t v = b->has_sym
358 ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + 1, strict)
359 : b->disp;
360 e32(&e, (uint32_t)v);
361 return e.len;
362 }
363 if (a->kind == OPND_REG && b->kind == OPND_REG && is_reg16(a->reg) && is_reg16(b->reg)) {
364 e8(&e, 0x8B);
365 e8(&e, (uint8_t)(0xC0 | (regnum(a->reg) << 3) | regnum(b->reg)));
366 return e.len;
367 }
368 if (st->bits == 16 && a->kind == OPND_REG && is_reg16(a->reg) && b->kind == OPND_MEM && !b->reg) {
369 e8(&e, 0x8B);
370 e8(&e, (uint8_t)(0x06 | (regnum(a->reg) << 3)));
371 e16(&e, (uint16_t)resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + (uint32_t)e.len, strict));
372 return e.len;$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "typedef struct.*val\|} value_t\|int32_t.*val\|int64_t.*val" toolchain/as/*.h toolchain/as/parser.c | head -20
toolchain/as/parser.c:106: int64_t val; /* constant, or addend when has_sym */
toolchain/as/parser.c:107:} value_t;
toolchain/as/parser.c:109:static value_t val_const(int64_t v) { value_t r = {0, {0}, v}; return r; }
toolchain/as/parser.c:415: ps->ctx->org = (uint32_t)v.val;
toolchain/as/parser.c:436: uint32_t n = (uint32_t)v.val;
toolchain/as/as.h:40: int64_t equ_value; /* valid if is_equ */
toolchain/as/as.h:42: uint32_t offset; /* byte offset within its section, valid if defined && !is_equ */
toolchain/as/as.h:69: int64_t disp; /* constant part; for OPND_IMM with no symbol, this is the whole value */$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "static value_t\|int .*=.*\.val\|(int)" toolchain/as/parser.c | head -30
109:static value_t val_const(int64_t v) { value_t r = {0, {0}, v}; return r; }
111:static value_t eval_primary(pstate_t *ps, line_t *ln);
112:static value_t eval_expr(pstate_t *ps, line_t *ln);
129:static value_t val_add(pstate_t *ps, value_t a, value_t b, int negate_b) {
160:static value_t eval_primary(pstate_t *ps, line_t *ln) {
201:static value_t eval_mul(pstate_t *ps, line_t *ln) {
221:static value_t eval_expr(pstate_t *ps, line_t *ln) {
412: if (ieq(head.text, "BITS")) { ps->bits = (int)line_next(ln).number; return; }160 static value_t eval_primary(pstate_t *ps, line_t *ln) {
161 token_t t = line_peek(ln);
162 if (t.kind == TK_NUMBER) { line_next(ln); return val_const(t.number); }
163 if (t.kind == TK_MINUS) {
164 line_next(ln);
165 value_t v = eval_primary(ps, ln);
166 if (v.has_sym) { perr(ps, "cannot negate a symbol"); return val_const(0); }
167 return val_const(-v.val);
168 }
169 if (t.kind == TK_LPAREN) {
170 line_next(ln);
171 value_t v = eval_expr(ps, ln);
172 if (line_peek(ln).kind == TK_RPAREN) line_next(ln); else perr(ps, "expected )");
173 return v;
174 }
175 if (t.kind == TK_DOLLAR) {
176 line_next(ln);
177 return val_const((int64_t)ps->offsets[ps->cur_section]);
178 }
179 if (t.kind == TK_DOLLARDOLLAR) {
180 line_next(ln);
181 return val_const(0); /* start of the current section - always offset 0 */
182 }
183 if (t.kind == TK_IDENT) {
184 line_next(ln);
185 char qname[MAX_NAME];
186 qualify_local(ps, t.text, qname, sizeof(qname));
187 symbol_t *s = sym_find(ps->ctx, qname);
188 if (s && s->is_equ) return val_const(s->equ_value);
189 /* Labels always stay symbolic, even once defined, so callers
190 * route them through resolve_abs/resolve_pcrel in encode.c,
191 * which apply ORG (-f bin) or emit relocations (-f elf32) as
192 * needed - see the val_add note above for the one exception. */
193 value_t r = {1, {0}, 0};
194 safe_strcpy(r.sym, qname, MAX_NAME);
195 return r;
196 }
197 perr(ps, "expected a value");
198 return val_const(0);
199 }$ cd /home/martin/workspace/prototypes/boot-to-claude
cat > /tmp/magic_test.S << 'EOF'
BITS 32
section .text
global _start
_start:
mov eax, 1413894961
mov ebx, eax
mov eax, 1
int 0x80
EOF
build/toolchain/as -f elf32 -o /tmp/magic_test.o /tmp/magic_test.S
objdump -d -M intel /tmp/magic_test.o 2>&1 | grep -A3 "_start"00000000 <_start>: 0: b8 31 53 46 54 mov eax,0x54465331 5: 8b d8 mov ebx,eax 7: b8 01 00 00 00 mov eax,0x1
$ cd /home/martin/workspace/prototypes/boot-to-claude objdump -d -M intel build/fs/tinyfs.o 2>&1 | grep -B3 -A5 "b8 31 53 46 54\|0x54465331"
758: 81 c4 0c 00 00 00 add esp,0xc
75e: 8d 05 00 00 00 00 lea eax,ds:0x0
764: 50 push eax
765: b8 31 53 46 54 mov eax,0x54465331
76a: 59 pop ecx
76b: 89 01 mov DWORD PTR [ecx],eax
76d: 8d 05 00 00 00 00 lea eax,ds:0x0
773: 81 c0 04 00 00 00 add eax,0x4
779: 50 push eax
--
888: 50 push eax
889: e8 fc ff ff ff call 88a <tinyfs_mount+0x3b>
88e: 81 c4 0c 00 00 00 add esp,0xc
894: b8 31 53 46 54 mov eax,0x54465331
899: 50 push eax
89a: 8d 05 00 00 00 00 lea eax,ds:0x0
8a0: 8b 00 mov eax,DWORD PTR [eax]
8a2: 59 pop ecx
8a3: 3b c1 cmp eax,ecx$ cd /home/martin/workspace/prototypes/boot-to-claude
python3 -c "
import struct
# read tinyfs.o's .text section raw bytes
with open('build/fs/tinyfs.o','rb') as f:
data = f.read()
e_shoff = struct.unpack('<I', data[32:36])[0]
e_shentsize, e_shnum, e_shstrndx = struct.unpack('<HHH', data[46:52])
shdrs = []
for i in range(e_shnum):
off = e_shoff + i*e_shentsize
sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size = struct.unpack('<IIIIII', data[off:off+24])
shdrs.append((sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size))
shstr_off = shdrs[e_shstrndx][4]
def name_of(sh_name):
end = data.index(b'\\0', shstr_off+sh_name)
return data[shstr_off+sh_name:end].decode()
text_sh = None
for sh in shdrs:
if name_of(sh[0]) == '.text':
text_sh = sh
break
print('found .text section, offset', hex(text_sh[4]), 'size', hex(text_sh[5]))
text_bytes = data[text_sh[4]:text_sh[4]+text_sh[5]]
with open('/tmp/tinyfs_o_text.bin','wb') as f:
f.write(text_bytes)
print('wrote', len(text_bytes), 'bytes')
"
ls -la /tmp/tinyfs_o_text.binfound .text section, offset 0x34 size 0x1311 wrote 4881 bytes -rw-r----- 1 martin martin 4881 Jul 18 18:00 /tmp/tinyfs_o_text.bin
$ cd /home/martin/workspace/prototypes/boot-to-claude
python3 -c "
with open('/tmp/tinyfs_o_text.bin','rb') as f:
orig = f.read()
with open('build/kernel.bin','rb') as f:
kernel = f.read()
base = 0x12614 - 0x10000 # file offset of tinyfs.o's .text within kernel.bin
region = kernel[base:base+len(orig)]
diffs = [i for i in range(len(orig)) if orig[i] != region[i]]
print('total bytes:', len(orig), 'diffs:', len(diffs))
if diffs:
print('first diff at', hex(diffs[0]))
print('last diff at', hex(diffs[-1]))
# print diff runs
runs = []
start = diffs[0]
prev = diffs[0]
for d in diffs[1:]:
if d != prev+1:
runs.append((start, prev))
start = d
prev = d
runs.append((start, prev))
print('num runs:', len(runs))
for r in runs[:20]:
print(hex(r[0]), '-', hex(r[1]), 'orig:', orig[r[0]:r[1]+1].hex(), 'new:', region[r[0]:r[1]+1].hex())
"
total bytes: 4881 diffs: 133 first diff at 0x5 last diff at 0x12b0 num runs: 48 0x5 - 0x7 orig: 000000 new: 2c4102 0x17 - 0x19 orig: 000000 new: 304102 0x80 - 0x82 orig: 000000 new: 444102 0x9c - 0x9e orig: 000000 new: 2c4102 0x11e - 0x120 orig: 000000 new: 444102 0x13a - 0x13c orig: 000000 new: 304102 0x17e - 0x180 orig: 000000 new: 444302 0x1f3 - 0x1f5 orig: 000000 new: 2c4102 0x237 - 0x239 orig: 000000 new: 444302 0x2ac - 0x2ae orig: 000000 new: 304102 0x311 - 0x313 orig: 000000 new: 444102 0x35a - 0x35c orig: 000000 new: 444102 0x395 - 0x397 orig: 000000 new: 444102 0x3c2 - 0x3c4 orig: 000000 new: 444102 0x3ff - 0x401 orig: 000000 new: 444102 0x435 - 0x437 orig: 000000 new: 344102 0x598 - 0x59a orig: 000000 new: 444302 0x5ce - 0x5d0 orig: 000000 new: 444302 0x5e3 - 0x5e4 orig: fcff new: 3ee4 0x628 - 0x62a orig: 000000 new: 444302
$ cd /home/martin/workspace/prototypes/boot-to-claude
python3 -c "
with open('/tmp/tinyfs_o_text.bin','rb') as f:
orig = f.read()
with open('build/kernel.bin','rb') as f:
kernel = f.read()
base = 0x12614 - 0x10000
region = kernel[base:base+len(orig)]
# print bytes around 0x894
for off in [0x765, 0x894]:
print(hex(off), 'orig:', orig[off-2:off+8].hex(), 'new:', region[off-2:off+8].hex())
"
0x765 orig: 0050b831534654598901 new: 0050b831534654598901 0x894 orig: 0000b831534654508d05 new: 0000b831534654508d05
$ cd /home/martin/workspace/prototypes/boot-to-claude nm /tmp/kernel_dbg.elf | grep -i "^000.*kmain\b" qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_kernel13.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x10000 continue x/10xb 0x12ea8 quit EOF timeout 15 gdb -q -batch -x /tmp/gdb_kernel13.txt 2>&1 | tail -15 kill $QPID 2>/dev/null
000101f2 T kmain The target architecture is set to "i386". warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000fff0 in ?? () Breakpoint 1 at 0x10000 Breakpoint 1, 0x00010000 in ?? () 0x12ea8: 0xb8 0x31 0x53 0x46 0x54 0x50 0x8d 0x05 0x12eb0: 0x34 0x41 A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached] qemu-system-i386: terminating on signal 15 from pid 3356040 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_kernel14.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x10000 continue watch *(unsigned short*)0x12eab continue info registers bt quit EOF timeout 30 gdb -q -batch -x /tmp/gdb_kernel14.txt 2>&1 | tail -60 kill $QPID 2>/dev/null
Terminated qemu-system-i386: terminating on signal 15 from pid 3356283 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude cat kernel/ata.c
#include "ata.h"
#include "builtins.h"
#define ATA_IO_BASE 0x1F0
#define ATA_DATA (ATA_IO_BASE + 0)
#define ATA_SECCOUNT (ATA_IO_BASE + 2)
#define ATA_LBA_LO (ATA_IO_BASE + 3)
#define ATA_LBA_MID (ATA_IO_BASE + 4)
#define ATA_LBA_HI (ATA_IO_BASE + 5)
#define ATA_DRIVE (ATA_IO_BASE + 6)
#define ATA_STATUS (ATA_IO_BASE + 7)
#define ATA_COMMAND (ATA_IO_BASE + 7)
#define ATA_CMD_READ 0x20
#define ATA_CMD_WRITE 0x30
#define ATA_CMD_CACHE_FLUSH 0xE7
#define ATA_SR_BSY 0x80
#define ATA_SR_DRQ 0x08
/* Thin names over the compiler's fixed port-I/O builtins (see
* docs/toolchain.md) - kept so the many call sites below read the same
* as before rather than spelling out __builtin_* everywhere. */
static inline void outb(uint16_t port, uint8_t val) { __builtin_outb(port, val); }
static inline uint8_t inb(uint16_t port) { return __builtin_inb(port); }
static inline void outw(uint16_t port, uint16_t val) { __builtin_outw(port, val); }
static inline uint16_t inw(uint16_t port) { return __builtin_inw(port); }
static void ata_wait_bsy(void) {
while (inb(ATA_STATUS) & ATA_SR_BSY) {}
}
static void ata_wait_drq(void) {
while (!(inb(ATA_STATUS) & ATA_SR_DRQ)) {}
}
static void ata_select(uint32_t lba) {
outb(ATA_DRIVE, (uint8_t)(0xE0 | ((lba >> 24) & 0x0F)));
outb(ATA_SECCOUNT, 1);
outb(ATA_LBA_LO, (uint8_t)(lba & 0xFF));
outb(ATA_LBA_MID, (uint8_t)((lba >> 8) & 0xFF));
outb(ATA_LBA_HI, (uint8_t)((lba >> 16) & 0xFF));
}
void ata_read_sector(uint32_t lba, void *buf) {
ata_wait_bsy();
ata_select(lba);
outb(ATA_COMMAND, ATA_CMD_READ);
ata_wait_bsy();
ata_wait_drq();
uint16_t *p = buf;
for (int i = 0; i < 256; i++) {
p[i] = inw(ATA_DATA);
}
}
void ata_write_sector(uint32_t lba, const void *buf) {
ata_wait_bsy();
ata_select(lba);
outb(ATA_COMMAND, ATA_CMD_WRITE);
ata_wait_bsy();
ata_wait_drq();
const uint16_t *p = buf;
for (int i = 0; i < 256; i++) {
outw(ATA_DATA, p[i]);
}
outb(ATA_COMMAND, ATA_CMD_CACHE_FLUSH);
ata_wait_bsy();
}$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "ND_INDEX" toolchain/cc/parser.c toolchain/cc/codegen.c
(Bash completed with no output)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "TK_LBRACKET" toolchain/cc/parser.c
334: while (match(p, TK_LBRACKET)) {
384: while (match(p, TK_LBRACKET)) {
430: if (match(p, TK_LBRACKET)) {
452: if (match(p, TK_LBRACKET)) {
567: if (match(p, TK_LBRACKET)) {560 perr(p, "expected expression");
561 return make_num(0);
562 }
563
564 static node_t *parse_postfix(parser_t *p) {
565 node_t *n = parse_primary(p);
566 for (;;) {
567 if (match(p, TK_LBRACKET)) {
568 node_t *idx = parse_expr(p);
569 expect(p, TK_RBRACKET);
570 node_t *add = node_new(ND_BINOP);
571 add->op = TK_PLUS;
572 add->a = n;
573 add->b = idx;
574 add->type = decay(n->type);
575 node_t *deref = node_new(ND_DEREF);
576 deref->a = add;
577 deref->type = add->type->base;
578 n = deref;
579 } else if (match(p, TK_DOT)) {$ cd /home/martin/workspace/prototypes/boot-to-claude build/toolchain/cc -Ikernel -Itoolchain/include -o /tmp/ata_check.S kernel/ata.c grep -n "ata_read_sector:" /tmp/ata_check.S
216:ata_read_sector:
File does not exist. Note: your current working directory is /home/martin/workspace/prototypes/boot-to-claude.
216 ata_read_sector: 217 push ebp 218 mov ebp, esp 219 sub esp, 8 220 call ata_wait_bsy 221 lea eax, [ebp+8] 222 mov eax, [eax] 223 push eax 224 call ata_select 225 add esp, 4 226 mov eax, 32 227 push eax 228 mov eax, 7 229 push eax 230 mov eax, 496 231 pop ecx 232 add eax, ecx 233 push eax 234 call outb 235 add esp, 8 236 call ata_wait_bsy 237 call ata_wait_drq 238 lea eax, [ebp-4] 239 push eax 240 lea eax, [ebp+12] 241 mov eax, [eax] 242 pop ecx 243 mov [ecx], eax 244 lea eax, [ebp-8] 245 push eax 246 mov eax, 0 247 pop ecx 248 mov [ecx], eax 249 .L4: 250 mov eax, 256 251 push eax 252 lea eax, [ebp-8] 253 mov eax, [eax] 254 pop ecx 255 cmp eax, ecx 256 setl al 257 movzx eax, al 258 cmp eax, 0 259 je .L5 260 lea eax, [ebp-8] 261 mov eax, [eax] 262 push eax 263 lea eax, [ebp-4] 264 mov eax, [eax] 265 pop ecx 266 imul ecx, ecx, 2 267 add eax, ecx 268 push eax 269 mov eax, 0 270 push eax 271 mov eax, 496 272 pop ecx 273 add eax, ecx 274 push eax 275 call inw 276 add esp, 4 277 pop ecx 278 mov [ecx], eax 279 .L6: 280 lea eax, [ebp-8] 281 mov ecx, eax 282 mov eax, [ecx] 283 mov edx, eax 284 add eax, 1 285 mov [ecx], eax 286 mov eax, edx 287 jmp .L4 288 .L5: 289 mov eax, 0 290 mov esp, ebp 291 pop ebp 292 ret 293 global ata_write_sector 294 ata_write_sector: 295 push ebp 296 mov ebp, esp 297 sub esp, 8 298 call ata_wait_bsy 299 lea eax, [ebp+8] 300 mov eax, [eax] 301 push eax 302 call ata_select 303 add esp, 4 304 mov eax, 48 305 push eax 306 mov eax, 7 307 push eax 308 mov eax, 496 309 pop ecx 310 add eax, ecx 311 push eax 312 call outb 313 add esp, 8 314 call ata_wait_bsy 315 call ata_wait_drq
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "static void store_value\|is_byte_type" toolchain/cc/codegen.c | head -10
46:static int is_byte_type(type_t *t) { return t->kind == TY_CHAR; }
51: if (is_byte_type(t)) {
59:static void store_value(type_t *t) {
60: if (is_byte_type(t)) {
272: if (is_byte_type(n->type)) out(" movzx eax, al\n");
324: * load_value/is_byte_type dance needed) and call through it.$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n '"movzx"\|is_reg16' toolchain/as/encode.c | head -20
29:static int is_reg16(reg_t r) { return r >= R_AX && r <= R_DI; }
273: if (a->kind == OPND_REG && b->kind == OPND_REG && is_reg16(a->reg) && is_reg16(b->reg)) {
301: if (st->bits == 16 && a->kind == OPND_MEM && !a->reg && b->kind == OPND_REG && is_reg16(b->reg)) {
310: if (a->kind == OPND_REG && is_reg16(a->reg) && b->kind == OPND_IMM) {
342: if (a->kind == OPND_REG && b->kind == OPND_IMM && is_reg16(a->reg) && st->bits == 16) {
348: if (a->kind == OPND_REG && b->kind == OPND_IMM && is_reg16(a->reg) && st->bits == 32) {
363: if (a->kind == OPND_REG && b->kind == OPND_REG && is_reg16(a->reg) && is_reg16(b->reg)) {
368: if (st->bits == 16 && a->kind == OPND_REG && is_reg16(a->reg) && b->kind == OPND_MEM && !b->reg) {
374: if (st->bits == 16 && a->kind == OPND_MEM && !a->reg && b->kind == OPND_REG && is_reg16(b->reg)) {
518: if (strcmp(m, "movzx") == 0 && is_reg32(a->reg) && is_reg16(b->reg)) {
523: if (strcmp(m, "movzx") == 0 && st->bits == 32 && is_reg32(a->reg) &&
530: if (strcmp(m, "shl") == 0 && is_reg16(a->reg) && b->kind == OPND_IMM) {
642: if (is_reg16(a->reg)) { e8(&e, (uint8_t)(0x50 + regnum(a->reg))); return e.len; }
645: if (is_reg32(a->reg) || is_reg16(a->reg)) { e8(&e, (uint8_t)(0x58 + regnum(a->reg))); return e.len; }515 }
516 }
517
518 if (strcmp(m, "movzx") == 0 && is_reg32(a->reg) && is_reg16(b->reg)) {
519 e8(&e, 0x66); e8(&e, 0x0F); e8(&e, 0xB7);
520 e8(&e, (uint8_t)(0xC0 | (regnum(a->reg) << 3) | regnum(b->reg)));
521 return e.len;
522 }
523 if (strcmp(m, "movzx") == 0 && st->bits == 32 && is_reg32(a->reg) &&
524 (b->kind == OPND_REG ? is_reg8(b->reg) : 1)) {
525 e8(&e, 0x0F); e8(&e, 0xB6);
526 encode_rm32(&e, ctx, is_bin, b, regnum(a->reg), st->section, st->offset, strict);
527 return e.len;
528 }
529
530 if (strcmp(m, "shl") == 0 && is_reg16(a->reg) && b->kind == OPND_IMM) {
531 e8(&e, 0xC1);
532 e8(&e, (uint8_t)(0xE0 | regnum(a->reg)));
533 e8(&e, (uint8_t)b->disp);
534 return e.len;$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "static int regnum\|R_AX\|R_EAX" toolchain/as/encode.c toolchain/as/as.h | head -20
toolchain/as/as.h:57: R_AX, R_CX, R_DX, R_BX, R_SP, R_BP, R_SI, R_DI,
toolchain/as/as.h:58: R_EAX, R_ECX, R_EDX, R_EBX, R_ESP, R_EBP, R_ESI, R_EDI,
toolchain/as/encode.c:7:static int regnum(reg_t r) {
toolchain/as/encode.c:9: case R_AL: case R_AX: case R_EAX: return 0;
toolchain/as/encode.c:28:static int is_reg32(reg_t r) { return r >= R_EAX && r <= R_EDI; }
toolchain/as/encode.c:29:static int is_reg16(reg_t r) { return r >= R_AX && r <= R_DI; }
toolchain/as/encode.c:288: if (a->kind == OPND_REG && a->reg == R_EAX && b->kind == OPND_IMM && st->bits == 16) {
toolchain/as/encode.c:651: if (b->reg == R_DX && a->reg == R_AX) { e8(&e, 0xED); return e.len; }
toolchain/as/encode.c:656: if (a->reg == R_DX && b->reg == R_AX) { e8(&e, 0xEF); return e.len; }355 if (a->kind == OPND_REG && b->kind == OPND_IMM && is_reg32(a->reg)) {
356 e8(&e, (uint8_t)(0xB8 + regnum(a->reg)));
357 int64_t v = b->has_sym
358 ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + 1, strict)
359 : b->disp;
360 e32(&e, (uint32_t)v);
361 return e.len;
362 }
363 if (a->kind == OPND_REG && b->kind == OPND_REG && is_reg16(a->reg) && is_reg16(b->reg)) {
364 e8(&e, 0x8B);
365 e8(&e, (uint8_t)(0xC0 | (regnum(a->reg) << 3) | regnum(b->reg)));
366 return e.len;
367 }
368 if (st->bits == 16 && a->kind == OPND_REG && is_reg16(a->reg) && b->kind == OPND_MEM && !b->reg) {
369 e8(&e, 0x8B);
370 e8(&e, (uint8_t)(0x06 | (regnum(a->reg) << 3)));
371 e16(&e, (uint16_t)resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + (uint32_t)e.len, strict));
372 return e.len;
373 }
374 if (st->bits == 16 && a->kind == OPND_MEM && !a->reg && b->kind == OPND_REG && is_reg16(b->reg)) {- if (a->kind == OPND_REG && b->kind == OPND_REG && is_reg16(a->reg) && is_reg16(b->reg)) {
- e8(&e, 0x8B);
- e8(&e, (uint8_t)(0xC0 | (regnum(a->reg) << 3) | regnum(b->reg)));
- return e.len;
- }
- if (st->bits == 16 && a->kind == OPND_REG && is_reg16(a->reg) && b->kind == OPND_MEM && !b->reg) {
+ if (a->kind == OPND_REG && b->kind == OPND_REG && is_reg16(a->reg) && is_reg16(b->reg)) {
+ e8(&e, 0x8B);
+ e8(&e, (uint8_t)(0xC0 | (regnum(a->reg) << 3) | regnum(b->reg)));
+ return e.len;
+ }
+ /* mov r16, r/m16 and mov r/m16, r16 in 32-bit code - e.g.
+ * `mov ax, [eax]` / `mov [ecx], ax`, the pair CC's codegen uses
+ * for 16-bit (uint16_t/short) loads/stores via the same general
+ * register-based addressing forms encode_rm32 already handles
+ * for 32-bit operands. Only the operand-size prefix (0x66)
+ * differs from the 32-bit mov r32,r/m32 case below - the
+ * addressing byte(s) are identical either way. Distinct from the
+ * st->bits==16 bare-label forms right below, which are for
+ * 16-bit *code* (real mode), not 32-bit code touching a 16-bit
+ * operand. */
+ if (st->bits == 32 && a->kind == OPND_REG && is_reg16(a->reg) && b->kind == OPND_MEM) {
+ e8(&e, 0x66); e8(&e, 0x8B);
+ encode_rm32(&e, ctx, is_bin, b, regnum(a->reg), st->section, st->offset, strict);
+ return e.len;
+ }
+ if (st->bits == 32 && a->kind == OPND_MEM && b->kind == OPND_REG && is_reg16(b->reg)) {
+ e8(&e, 0x66); e8(&e, 0x89);
+ encode_rm32(&e, ctx, is_bin, a, regnum(b->reg), st->section, st->offset, strict);
+ return e.len;
+ }
+ if (st->bits == 16 && a->kind == OPND_REG && is_reg16(a->reg) && b->kind == OPND_MEM && !b->reg) {The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/encode.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "TY_SHORT" toolchain/cc/*.h toolchain/cc/*.c
toolchain/cc/cc.h:85: TY_VOID, TY_CHAR, TY_SHORT, TY_INT, TY_PTR, TY_ARRAY, toolchain/cc/types.c:27: return t->kind == TY_CHAR || t->kind == TY_SHORT || t->kind == TY_INT || t->kind == TY_ENUM; toolchain/cc/types.c:36: case TY_SHORT: t->align = 2; break; toolchain/cc/types.c:58: case TY_SHORT: t->size = 2; break; toolchain/cc/parser.c:318: else if (saw_short) t = type_new(TY_SHORT);
<tool_use_error>InputValidationError: Read was called with input that could not be parsed as JSON.
You sent (first 117 of 117 bytes): {"file_path": "/home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c", "offset": 44/2, "limit": 18}
Common causes: unescaped backslashes in file paths (use / or \\), unescaped control characters, or truncated output. Retry with valid JSON.</tool_use_error>44 /* ---------------- loads/stores sized per C type ---------------- */
45
46 static int is_byte_type(type_t *t) { return t->kind == TY_CHAR; }
47
48 /* Address already in EAX; loads the value (sized per `t`) into EAX. */
49 static void load_value(type_t *t) {
50 if (t->kind == TY_ARRAY) return; /* arrays decay to their own address - nothing to load */
51 if (is_byte_type(t)) {
52 out(" movzx eax, byte [eax]\n");
53 } else {
54 out(" mov eax, [eax]\n");
55 }
56 }
57
58 /* Stores EAX's value into the address in ECX. */
59 static void store_value(type_t *t) {
60 if (is_byte_type(t)) {
61 out(" mov [ecx], al\n");- static int is_byte_type(type_t *t) { return t->kind == TY_CHAR; }
-
- /* Address already in EAX; loads the value (sized per `t`) into EAX. */
- static void load_value(type_t *t) {
- if (t->kind == TY_ARRAY) return; /* arrays decay to their own address - nothing to load */
- if (is_byte_type(t)) {
- out(" movzx eax, byte [eax]\n");
- } else {
- out(" mov eax, [eax]\n");
- }
- }
-
- /* Stores EAX's value into the address in ECX. */
- static void store_value(type_t *t) {
- if (is_byte_type(t)) {
- out(" mov [ecx], al\n");
+ static int is_byte_type(type_t *t) { return t->kind == TY_CHAR; }
+ static int is_short_type(type_t *t) { return t->kind == TY_SHORT; }
+
+ /* Address already in EAX; loads the value (sized per `t`) into EAX. */
+ static void load_value(type_t *t) {
+ if (t->kind == TY_ARRAY) return; /* arrays decay to their own address - nothing to load */
+ if (is_byte_type(t)) {
+ out(" movzx eax, byte [eax]\n");
+ } else if (is_short_type(t)) {
+ /* mov ax, [eax] reads through the same register that holds the
+ * address (ax is eax's low 16 bits) - fine, the CPU reads the
+ * memory operand before writing the result back. movzx then
+ * clears the high 16 bits, which the mov left as whatever
+ * garbage eax's upper half held before. */
+ out(" mov ax, [eax]\n movzx eax, ax\n");
+ } else {
+ out(" mov eax, [eax]\n");
+ }
+ }
+
+ /* Stores EAX's value into the address in ECX. */
+ static void store_value(type_t *t) {
+ if (is_byte_type(t)) {
+ out(" mov [ecx], al\n");
+ } else if (is_short_type(t)) {
+ out(" mov [ecx], ax\n");The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
63 }
64 }
65
66 /* Stores EAX's value into the address in ECX. */
67 static void store_value(type_t *t) {
68 if (is_byte_type(t)) {
69 out(" mov [ecx], al\n");
70 } else if (is_short_type(t)) {
71 out(" mov [ecx], ax\n");
72 } else {$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -Wall -Wextra -O2 -o build/toolchain/as toolchain/as/*.c 2>&1 | tail -40 gcc -o build/toolchain/cc toolchain/cc/*.c -Wall 2>&1 | tail -40 ./tools/run_toolchain_tests.sh 2>&1 | tail -12
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) run_toolchain_tests: PASS
/* uint16_t load/store, in an array - the pattern kernel/ata.c uses to
* read/write ATA sector buffers 16 bits at a time. A doubled store width
* here would corrupt every other element (or overrun the buffer). */
int main(void) {
unsigned short buf[4];
for (int i = 0; i < 4; i++) {
buf[i] = (unsigned short)(0x1000 + i);
}
unsigned short sentinel = 0xBEEF;
int total = 0;
for (int i = 0; i < 4; i++) {
total += buf[i];
}
return (total & 0xff) + (sentinel != 0xBEEF ? 100 : 0);
/* buf = 0x1000,0x1001,0x1002,0x1003 -> sum = 0x4006 = 16390, &0xff = 6 */
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/tests/toolchain/t10_short.c (file state is current in your context — no need to Read it back)
- [t09_local_init]=81 - ) + [t09_local_init]=81 + [t10_short]=6 + )
The file /home/martin/workspace/prototypes/boot-to-claude/tools/run_toolchain_tests.sh has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude ./tools/run_toolchain_tests.sh 2>&1 | tail -13
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) FAIL t10_short: expected 6, got 106 run_toolchain_tests: FAIL
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "TY_ARRAY" toolchain/cc/types.c
38: case TY_ARRAY: t->align = type_align(t->base); break; 60: case TY_ARRAY: t->size = type_size(t->base) * (t->array_len > 0 ? t->array_len : 0); break;
$ cd /home/martin/workspace/prototypes/boot-to-claude build/toolchain/cc -Itoolchain/include --target=host -o /tmp/t10.S tests/toolchain/t10_short.c cat /tmp/t10.S
BITS 32
section .text
global main
main:
push ebp
mov ebp, esp
sub esp, 24
lea eax, [ebp-12]
push eax
mov eax, 0
pop ecx
mov [ecx], eax
.L0:
mov eax, 4
push eax
lea eax, [ebp-12]
mov eax, [eax]
pop ecx
cmp eax, ecx
setl al
movzx eax, al
cmp eax, 0
je .L1
lea eax, [ebp-12]
mov eax, [eax]
push eax
lea eax, [ebp-8]
pop ecx
imul ecx, ecx, 2
add eax, ecx
push eax
lea eax, [ebp-12]
mov eax, [eax]
push eax
mov eax, 4096
pop ecx
add eax, ecx
pop ecx
mov [ecx], ax
.L2:
lea eax, [ebp-12]
mov ecx, eax
mov eax, [ecx]
mov edx, eax
add eax, 1
mov [ecx], eax
mov eax, edx
jmp .L0
.L1:
lea eax, [ebp-16]
push eax
mov eax, 48879
pop ecx
mov [ecx], ax
lea eax, [ebp-20]
push eax
mov eax, 0
pop ecx
mov [ecx], eax
lea eax, [ebp-24]
push eax
mov eax, 0
pop ecx
mov [ecx], eax
.L3:
mov eax, 4
push eax
lea eax, [ebp-24]
mov eax, [eax]
pop ecx
cmp eax, ecx
setl al
movzx eax, al
cmp eax, 0
je .L4
lea eax, [ebp-20]
push eax
lea eax, [ebp-24]
mov eax, [eax]
push eax
lea eax, [ebp-8]
pop ecx
imul ecx, ecx, 2
add eax, ecx
mov ax, [eax]
movzx eax, ax
push eax
pop ecx
mov edx, [esp]
mov eax, [edx]
add eax, ecx
pop ecx
mov [ecx], eax
.L5:
lea eax, [ebp-24]
mov ecx, eax
mov eax, [ecx]
mov edx, eax
add eax, 1
mov [ecx], eax
mov eax, edx
jmp .L3
.L4:
mov eax, 48879
push eax
lea eax, [ebp-16]
mov ax, [eax]
movzx eax, ax
pop ecx
cmp eax, ecx
setne al
movzx eax, al
cmp eax, 0
je .L6
mov eax, 100
jmp .L7
.L6:
mov eax, 0
.L7:
push eax
mov eax, 255
push eax
lea eax, [ebp-20]
mov eax, [eax]
pop ecx
and eax, ecx
pop ecx
add eax, ecx
mov esp, ebp
pop ebp
ret
mov eax, 0
mov esp, ebp
pop ebp
ret
global _start
_start:
mov eax, [esp]
lea ecx, [esp+4]
push ecx
push eax
call main
mov ebx, eax
mov eax, 1
int 0x80$ cd /home/martin/workspace/prototypes/boot-to-claude build/toolchain/as -f elf32 -o /tmp/t10.o /tmp/t10.S build/toolchain/ld -o /tmp/t10 /tmp/t10.o /tmp/t10; echo "exit=$?"
exit=106
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "^main:\|^\.L1:\|^\.L4:" /tmp/t10.S
4:main: 49:.L1: 103:.L4:
$ cd /home/martin/workspace/prototypes/boot-to-claude objdump -d -M intel /tmp/t10.o 2>&1 | grep -B2 -A3 "mov.*0xbeef\|mov.*ax,\[eax\]\|mov.*\[eax\],ax\|48879"
84: 8d 85 f0 ff ff ff lea eax,[ebp-0x10] 8a: 50 push eax 8b: b8 ef be 00 00 mov eax,0xbeef 90: 59 pop ecx 91: 66 89 01 mov WORD PTR [ecx],ax 94: 8d 85 ec ff ff ff lea eax,[ebp-0x14] -- 00000122 <main.L4>: 122: b8 ef be 00 00 mov eax,0xbeef 127: 50 push eax 128: 8d 85 f0 ff ff ff lea eax,[ebp-0x10] 12e: 66 8b 00 mov ax,WORD PTR [eax]
$ cd /home/martin/workspace/prototypes/boot-to-claude cat > /tmp/gdb_t10.txt << 'EOF' set architecture i386 break *0x122 run x/1xw $ebp-0x10 info registers eax quit EOF gdb -q -batch -x /tmp/gdb_t10.txt /tmp/t10 2>&1 | tail -20
The target architecture is set to "i386". Breakpoint 1 at 0x122 /tmp/gdb_t10.txt:3: Error in sourced command file: Warning: Cannot insert breakpoint 1. Cannot access memory at address 0x122
$ cd /home/martin/workspace/prototypes/boot-to-claude
cat > /tmp/t10_debug.c << 'EOF'
int __sys_write(int fd, const void *buf, unsigned int count);
void __sys_exit(int code);
int main(void) {
unsigned short buf[4];
for (int i = 0; i < 4; i++) {
buf[i] = (unsigned short)(0x1000 + i);
}
unsigned short sentinel = 0xBEEF;
__sys_write(1, &sentinel, 2);
__sys_exit(0);
return 0;
}
EOF
build/toolchain/cc -Itoolchain/include --target=host -o /tmp/t10_debug.S /tmp/t10_debug.c
build/toolchain/as -f elf32 -o /tmp/t10_debug.o /tmp/t10_debug.S
build/toolchain/ld -o /tmp/t10_debug /tmp/t10_debug.o
/tmp/t10_debug | xxd00000000: efbe ..
$ cd /home/martin/workspace/prototypes/boot-to-claude
cat > /tmp/t10_debug2.c << 'EOF'
int __sys_write(int fd, const void *buf, unsigned int count);
void __sys_exit(int code);
int main(void) {
unsigned short buf[4];
for (int i = 0; i < 4; i++) {
buf[i] = (unsigned short)(0x1000 + i);
}
unsigned short sentinel = 0xBEEF;
int total = 0;
for (int i = 0; i < 4; i++) {
total += buf[i];
}
__sys_write(1, &sentinel, 2);
__sys_exit(0);
return 0;
}
EOF
build/toolchain/cc -Itoolchain/include --target=host -o /tmp/t10_debug2.S /tmp/t10_debug2.c
build/toolchain/as -f elf32 -o /tmp/t10_debug2.o /tmp/t10_debug2.S
build/toolchain/ld -o /tmp/t10_debug2 /tmp/t10_debug2.o
/tmp/t10_debug2 | xxd00000000: efbe ..
$ cd /home/martin/workspace/prototypes/boot-to-claude
cat > /tmp/t10_debug3.c << 'EOF'
int __sys_write(int fd, const void *buf, unsigned int count);
void __sys_exit(int code);
int main(void) {
unsigned short sentinel = 0xBEEF;
int r = (sentinel != 0xBEEF) ? 100 : 0;
__sys_exit(r);
return 0;
}
EOF
build/toolchain/cc -Itoolchain/include --target=host -o /tmp/t10_debug3.S /tmp/t10_debug3.c
build/toolchain/as -f elf32 -o /tmp/t10_debug3.o /tmp/t10_debug3.S
build/toolchain/ld -o /tmp/t10_debug3 /tmp/t10_debug3.o
/tmp/t10_debug3; echo "exit=$?"exit=100
$ cat /tmp/t10_debug3.S
BITS 32
section .text
global main
main:
push ebp
mov ebp, esp
sub esp, 8
lea eax, [ebp-4]
push eax
mov eax, 48879
pop ecx
mov [ecx], ax
lea eax, [ebp-8]
push eax
mov eax, 48879
push eax
lea eax, [ebp-4]
mov ax, [eax]
movzx eax, ax
pop ecx
cmp eax, ecx
setne al
movzx eax, al
cmp eax, 0
je .L0
mov eax, 100
jmp .L1
.L0:
mov eax, 0
.L1:
pop ecx
mov [ecx], eax
lea eax, [ebp-8]
mov eax, [eax]
mov ebx, eax
mov eax, 1
int 0x80
mov eax, 0
mov esp, ebp
pop ebp
ret
mov eax, 0
mov esp, ebp
pop ebp
ret
global _start
_start:
mov eax, [esp]
lea ecx, [esp+4]
push ecx
push eax
call main
mov ebx, eax
mov eax, 1
int 0x80$ cd /home/martin/workspace/prototypes/boot-to-claude
cat > /tmp/t10_debug4.c << 'EOF'
void __sys_exit(int code);
int main(void) {
unsigned short sentinel = 0xBEEF;
int r = sentinel != 0xBEEF;
__sys_exit(r);
return 0;
}
EOF
build/toolchain/cc -Itoolchain/include --target=host -o /tmp/t10_debug4.S /tmp/t10_debug4.c
build/toolchain/as -f elf32 -o /tmp/t10_debug4.o /tmp/t10_debug4.S
build/toolchain/ld -o /tmp/t10_debug4 /tmp/t10_debug4.o
/tmp/t10_debug4; echo "exit=$?"exit=1
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "cmp eax, ecx" /tmp/t10_debug4.S cat /tmp/t10_debug4.S
21: cmp eax, ecx
BITS 32
section .text
global main
main:
push ebp
mov ebp, esp
sub esp, 8
lea eax, [ebp-4]
push eax
mov eax, 48879
pop ecx
mov [ecx], ax
lea eax, [ebp-8]
push eax
mov eax, 48879
push eax
lea eax, [ebp-4]
mov ax, [eax]
movzx eax, ax
pop ecx
cmp eax, ecx
setne al
movzx eax, al
pop ecx
mov [ecx], eax
lea eax, [ebp-8]
mov eax, [eax]
mov ebx, eax
mov eax, 1
int 0x80
mov eax, 0
mov esp, ebp
pop ebp
ret
mov eax, 0
mov esp, ebp
pop ebp
ret
global _start
_start:
mov eax, [esp]
lea ecx, [esp+4]
push ecx
push eax
call main
mov ebx, eax
mov eax, 1
int 0x80$ cd /home/martin/workspace/prototypes/boot-to-claude objdump -d -M intel /tmp/t10_debug4.o 2>&1 | grep -B2 "cmp.*eax,ecx"
2f: 66 0f b7 c0 movzx ax,ax 33: 59 pop ecx 34: 3b c1 cmp eax,ecx
530 if (strcmp(m, full) == 0) {
531 e8(&e, 0x0F); e8(&e, cc[i].op);
532 e8(&e, (uint8_t)(0xC0 | regnum(a->reg)));
533 return e.len;
534 }
535 }
536 }
537
538 if (strcmp(m, "movzx") == 0 && is_reg32(a->reg) && is_reg16(b->reg)) {
539 e8(&e, 0x66); e8(&e, 0x0F); e8(&e, 0xB7);- if (strcmp(m, "movzx") == 0 && is_reg32(a->reg) && is_reg16(b->reg)) {
- e8(&e, 0x66); e8(&e, 0x0F); e8(&e, 0xB7);
+ if (strcmp(m, "movzx") == 0 && is_reg32(a->reg) && is_reg16(b->reg)) {
+ /* MOVZX r32, r/m16 = 0F B7 /r, no 0x66 prefix - the opcode
+ * itself already means "32-bit destination, 16-bit source"; a
+ * leading 0x66 operand-size override reinterprets *both*
+ * operands as 16-bit, silently downgrading this to r16,r16 (a
+ * near no-op) instead of actually zero-extending into the full
+ * 32-bit register. Never previously exercised: every earlier
+ * movzx use was the byte-from-memory form (0F B6) below, not
+ * this register-to-register 16-to-32 form - only CC's new
+ * TY_SHORT load path (`mov ax,[eax]` then `movzx eax,ax`) hit
+ * it, see docs/toolchain.md. */
+ e8(&e, 0x0F); e8(&e, 0xB7);The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/encode.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -Wall -Wextra -O2 -o build/toolchain/as toolchain/as/*.c 2>&1 | tail -40 ./tools/run_toolchain_tests.sh 2>&1 | tail -13
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) PASS t10_short (6) run_toolchain_tests: PASS
$ cd /home/martin/workspace/prototypes/boot-to-claude make clean make 2>&1 | tail -20
rm -rf build build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/compiler/codegen.S kernel/lang/compiler/codegen.c build/toolchain/as -f elf32 -o build/lang/compiler/codegen.o build/lang/compiler/codegen.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/vm.S kernel/lang/vm.c build/toolchain/as -f elf32 -o build/lang/vm.o build/lang/vm.S gcc -Wall -Wextra -O2 -o build/toolchain/ld toolchain/ld/elf_reader.c toolchain/ld/link_flat.c toolchain/ld/link_static_exec.c toolchain/ld/main.c build/toolchain/ld -T kernel/link.ld --format bin -o build/kernel.bin build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_tinyfs.S kernel/fs/tinyfs.c build/toolchain/as -f elf32 -o build/host_tinyfs.o build/host_tinyfs.S build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_string.S kernel/string.c build/toolchain/as -f elf32 -o build/host_string.o build/host_string.S build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_mkfs_tinyfs.S tools/mkfs_tinyfs.c build/toolchain/as -f elf32 -o build/host_mkfs_tinyfs.o build/host_mkfs_tinyfs.S build/toolchain/ld -o build/mkfs_tinyfs build/host_tinyfs.o build/host_string.o build/host_mkfs_tinyfs.o dd if=/dev/zero of=build/disk.img bs=512 count=4096 status=none dd if=build/boot.bin of=build/disk.img conv=notrunc status=none dd if=build/stage2.bin of=build/disk.img bs=512 seek=1 conv=notrunc status=none dd if=build/kernel.bin of=build/disk.img bs=512 seek=5 conv=notrunc status=none build/mkfs_tinyfs build/disk.img tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc mkfs_tinyfs: done rm build/console.S build/shell/shell.S build/idt.S build/lang/script/parser.S build/lang/script/ast.S build/lang/script/eval.S build/lang/vm.S build/lang/compiler/codegen.S build/lang/compiler/parser.S build/kmain.S build/gdt.S build/string.S build/fs/tinyfs.S build/selftest.S build/serial.S build/lang/lexer.S build/fs/tinyfs_disk.S build/ata.S build/keyboard.S build/memory.S
$ cd /home/martin/workspace/prototypes/boot-to-claude ./tools/run_tests.sh 2>&1 | tail -80
run_tests: FAIL - timed out waiting for 'SELFTEST PASS' --- serial log --- tiny-os: kernel entered tinyfs: mount FAILED selftest: running FAIL: tinyfs_list finds seeded files FAIL: tinyfs_list contains hello.txt FAIL: tinyfs_read hello.txt succeeds FAIL: tinyfs_write new file FAIL: tinyfs_read roundtrip length FAIL: tinyfs_read roundtrip content SELFTEST FAIL tiny-os shell. type 'help' for commands. > qemu-system-i386: terminating on signal 15 from pid 3368296 (bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude KOBJS="build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o" build/toolchain/ld -T kernel/link.ld -o /tmp/kernel_dbg2.elf $KOBJS nm /tmp/kernel_dbg2.elf | grep -i "tinyfs_mount\b"
00012e92 T tinyfs_mount
$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_final.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x12e92 continue finish info registers eax quit EOF timeout 15 gdb -q -batch -x /tmp/gdb_final.txt 2>&1 | tail -20 kill $QPID 2>/dev/null
The target architecture is set to "i386". warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000fff0 in ?? () Breakpoint 1 at 0x12e92 Breakpoint 1, 0x00012e92 in ?? () 0x0001023d in ?? () eax 0xffffffff -1 A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached] qemu-system-i386: terminating on signal 15 from pid 3368740 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude objdump -d -M intel /tmp/kernel_dbg2.elf 2>&1 | grep -A20 "<tinyfs_mount>:" | head -25
00012e92 <tinyfs_mount>: 12e92: 55 push ebp 12e93: 8b ec mov ebp,esp 12e95: 81 ec 00 02 00 00 sub esp,0x200 12e9b: 8d 85 00 fe ff ff lea eax,[ebp-0x200] 12ea1: 50 push eax 12ea2: b8 00 00 00 00 mov eax,0x0 12ea7: 50 push eax 12ea8: 8d 05 b8 41 02 00 lea eax,ds:0x241b8 12eae: 8b 00 mov eax,DWORD PTR [eax] 12eb0: ff d0 call eax 12eb2: 81 c4 08 00 00 00 add esp,0x8 12eb8: b8 10 00 00 00 mov eax,0x10 12ebd: 50 push eax 12ebe: 8d 85 00 fe ff ff lea eax,[ebp-0x200] 12ec4: 50 push eax 12ec5: 8d 05 c0 41 02 00 lea eax,ds:0x241c0 12ecb: 50 push eax 12ecc: e8 b0 df ff ff call 10e81 <memcpy> 12ed1: 81 c4 0c 00 00 00 add esp,0xc 12ed7: b8 31 53 46 54 mov eax,0x54465331
$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_final2.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x12ee6 continue info registers eax ecx quit EOF timeout 15 gdb -q -batch -x /tmp/gdb_final2.txt 2>&1 | tail -15 kill $QPID 2>/dev/null
The target architecture is set to "i386". warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000fff0 in ?? () Breakpoint 1 at 0x12ee6 Breakpoint 1, 0x00012ee6 in ?? () eax 0xd805331 226513713 ecx 0x54465331 1413894961 A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached] qemu-system-i386: terminating on signal 15 from pid 3368954 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude objdump -d -M intel /tmp/kernel_dbg2.elf 2>&1 | grep -A25 "<tinyfs_mount>:" | tail -10
12ec5: 8d 05 c0 41 02 00 lea eax,ds:0x241c0 12ecb: 50 push eax 12ecc: e8 b0 df ff ff call 10e81 <memcpy> 12ed1: 81 c4 0c 00 00 00 add esp,0xc 12ed7: b8 31 53 46 54 mov eax,0x54465331 12edc: 50 push eax 12edd: 8d 05 c0 41 02 00 lea eax,ds:0x241c0 12ee3: 8b 00 mov eax,DWORD PTR [eax] 12ee5: 59 pop ecx 12ee6: 3b c1 cmp eax,ecx
$ cd /home/martin/workspace/prototypes/boot-to-claude build/toolchain/cc -Ikernel -Itoolchain/include -o /tmp/ata_recheck.S kernel/ata.c grep -n "mov \[ecx\], ax\|mov \[ecx\], eax" /tmp/ata_recheck.S | head -5
248: mov [ecx], eax 253: mov [ecx], eax 283: mov [ecx], ax 290: mov [ecx], eax 326: mov [ecx], eax
$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_final3.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x10000 continue x/10xb 0x12ed7 quit EOF timeout 15 gdb -q -batch -x /tmp/gdb_final3.txt 2>&1 | tail -15 kill $QPID 2>/dev/null
The target architecture is set to "i386". warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000fff0 in ?? () Breakpoint 1 at 0x10000 Breakpoint 1, 0x00010000 in ?? () 0x12ed7: 0xb8 0x31 0x53 0x46 0x54 0x50 0x8d 0x05 0x12edf: 0xc0 0x41 A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached] qemu-system-i386: terminating on signal 15 from pid 3369646 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_final4.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x12e92 continue x/10xb 0x12ed7 quit EOF timeout 15 gdb -q -batch -x /tmp/gdb_final4.txt 2>&1 | tail -15 kill $QPID 2>/dev/null
The target architecture is set to "i386". warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000fff0 in ?? () Breakpoint 1 at 0x12e92 Breakpoint 1, 0x00012e92 in ?? () 0x12ed7: 0xb8 0x31 0x53 0x46 0x54 0x50 0x8d 0x05 0x12edf: 0xc0 0x41 A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached] qemu-system-i386: terminating on signal 15 from pid 3371385 ()
$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_final5.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x12eb2 continue x/10xb 0x12ed7 break *0x12ed1 continue x/10xb 0x12ed7 quit EOF timeout 15 gdb -q -batch -x /tmp/gdb_final5.txt 2>&1 | tail -20 kill $QPID 2>/dev/null
The target architecture is set to "i386". warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000fff0 in ?? () Breakpoint 1 at 0x12eb2 Breakpoint 1, 0x00012eb2 in ?? () 0x12ed7: 0xb8 0x31 0x53 0x46 0x54 0x50 0x8d 0x05 0x12edf: 0xc0 0x41 Breakpoint 2 at 0x12ed1 Breakpoint 2, 0x00012ed1 in ?? () 0x12ed7: 0xb8 0x31 0x53 0x46 0x54 0x50 0x8d 0x05 0x12edf: 0xc0 0x41 A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached] qemu-system-i386: terminating on signal 15 from pid 3372104 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_final6.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x12e92 continue x/16xb 0x241c0 x/512xb 0x12e9b quit EOF timeout 15 gdb -q -batch -x /tmp/gdb_final6.txt 2>&1 | tail -10 kill $QPID 2>/dev/null
0x1307b: 0xff 0x8b 0x00 0x81 0xc0 0x01 0x00 0x00 0x13083: 0x00 0x59 0x03 0xc1 0x50 0x8d 0x85 0xf4 0x1308b: 0xff 0xff 0xff 0x8b 0x00 0x50 0x8d 0x85 0x13093: 0x08 0x00 0x00 0x00 0x8b 0x00 0x59 0x03 A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached] qemu-system-i386: terminating on signal 15 from pid 3372456 ()
$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_final7.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x12ee6 continue x/16xb 0x241c0 info registers eax ecx quit EOF timeout 15 gdb -q -batch -x /tmp/gdb_final7.txt 2>&1 kill $QPID 2>/dev/null
The target architecture is set to "i386". warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000fff0 in ?? () Breakpoint 1 at 0x12ee6 Breakpoint 1, 0x00012ee6 in ?? () 0x241c0: 0x31 0x53 0x80 0x0d 0x40 0x00 0x76 0x0d 0x241c8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 eax 0xd805331 226513713 ecx 0x54465331 1413894961 A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached] qemu-system-i386: terminating on signal 15 from pid 3372626 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_final8.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x12eb2 continue info registers ebp x/16xb $ebp-0x200 quit EOF timeout 15 gdb -q -batch -x /tmp/gdb_final8.txt 2>&1 kill $QPID 2>/dev/null
The target architecture is set to "i386". warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000fff0 in ?? () Breakpoint 1 at 0x12eb2 Breakpoint 1, 0x00012eb2 in ?? () ebp 0x23974 0x23974 0x23774: 0x31 0x53 0x80 0x0d 0x40 0x00 0x76 0x0d 0x2377c: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached] qemu-system-i386: terminating on signal 15 from pid 3372833 ()
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "call inw" /tmp/ata_recheck.S
280: call inw
$ cd /home/martin/workspace/prototypes/boot-to-claude build/toolchain/as -f elf32 -o /tmp/ata_recheck.o /tmp/ata_recheck.S objdump -d -M intel /tmp/ata_recheck.o 2>&1 | grep -B5 -A5 "imul.*ecx,ecx,0x2\b"
2c9: 8b 00 mov eax,DWORD PTR [eax] 2cb: 50 push eax 2cc: 8d 85 fc ff ff ff lea eax,[ebp-0x4] 2d2: 8b 00 mov eax,DWORD PTR [eax] 2d4: 59 pop ecx 2d5: 69 c9 02 00 00 00 imul ecx,ecx,0x2 2db: 03 c1 add eax,ecx 2dd: 50 push eax 2de: b8 00 00 00 00 mov eax,0x0 2e3: 50 push eax 2e4: b8 f0 01 00 00 mov eax,0x1f0 -- 3b6: 8b 00 mov eax,DWORD PTR [eax] 3b8: 50 push eax 3b9: 8d 85 fc ff ff ff lea eax,[ebp-0x4] 3bf: 8b 00 mov eax,DWORD PTR [eax] 3c1: 59 pop ecx 3c2: 69 c9 02 00 00 00 imul ecx,ecx,0x2 3c8: 03 c1 add eax,ecx 3ca: 66 8b 00 mov ax,WORD PTR [eax] 3cd: 0f b7 c0 movzx eax,ax 3d0: 50 push eax 3d1: b8 00 00 00 00 mov eax,0x0
$ cd /home/martin/workspace/prototypes/boot-to-claude objdump -d -M intel /tmp/ata_recheck.o 2>&1 | sed -n '/2c9:/,/300:/p'
2c9: 8b 00 mov eax,DWORD PTR [eax] 2cb: 50 push eax 2cc: 8d 85 fc ff ff ff lea eax,[ebp-0x4] 2d2: 8b 00 mov eax,DWORD PTR [eax] 2d4: 59 pop ecx 2d5: 69 c9 02 00 00 00 imul ecx,ecx,0x2 2db: 03 c1 add eax,ecx 2dd: 50 push eax 2de: b8 00 00 00 00 mov eax,0x0 2e3: 50 push eax 2e4: b8 f0 01 00 00 mov eax,0x1f0 2e9: 59 pop ecx 2ea: 03 c1 add eax,ecx 2ec: 50 push eax 2ed: e8 87 fd ff ff call 79 <inw> 2f2: 81 c4 04 00 00 00 add esp,0x4 2f8: 59 pop ecx 2f9: 66 89 01 mov WORD PTR [ecx],ax 000002fc <ata_read_sector.L6>: 2fc: 8d 85 f8 ff ff ff lea eax,[ebp-0x8] 302: 8b c8 mov ecx,eax 304: 8b 01 mov eax,DWORD PTR [ecx] 306: 8b d0 mov edx,eax 308: 81 c0 01 00 00 00 add eax,0x1 30e: 89 01 mov DWORD PTR [ecx],eax 310: 8b c2 mov eax,edx 312: e9 89 ff ff ff jmp 2a0 <ata_read_sector.L4> 00000317 <ata_read_sector.L5>: 317: b8 00 00 00 00 mov eax,0x0 31c: 8b e5 mov esp,ebp 31e: 5d pop ebp 31f: c3 ret 00000320 <ata_write_sector>: 320: 55 push ebp 321: 8b ec mov ebp,esp 323: 81 ec 08 00 00 00 sub esp,0x8 329: e8 6d fd ff ff call 9b <ata_wait_bsy> 32e: 8d 85 08 00 00 00 lea eax,[ebp+0x8] 334: 8b 00 mov eax,DWORD PTR [eax] 336: 50 push eax 337: e8 eb fd ff ff call 127 <ata_select> 33c: 81 c4 04 00 00 00 add esp,0x4 342: b8 30 00 00 00 mov eax,0x30 347: 50 push eax 348: b8 07 00 00 00 mov eax,0x7 34d: 50 push eax 34e: b8 f0 01 00 00 mov eax,0x1f0 353: 59 pop ecx 354: 03 c1 add eax,ecx 356: 50 push eax 357: e8 a4 fc ff ff call 0 <outb> 35c: 81 c4 08 00 00 00 add esp,0x8 362: e8 34 fd ff ff call 9b <ata_wait_bsy> 367: e8 6f fd ff ff call db <ata_wait_drq> 36c: 8d 85 fc ff ff ff lea eax,[ebp-0x4] 372: 50 push eax 373: 8d 85 0c 00 00 00 lea eax,[ebp+0xc] 379: 8b 00 mov eax,DWORD PTR [eax] 37b: 59 pop ecx 37c: 89 01 mov DWORD PTR [ecx],eax 37e: 8d 85 f8 ff ff ff lea eax,[ebp-0x8] 384: 50 push eax 385: b8 00 00 00 00 mov eax,0x0 38a: 59 pop ecx 38b: 89 01 mov DWORD PTR [ecx],eax 0000038d <ata_write_sector.L7>: 38d: b8 00 01 00 00 mov eax,0x100 392: 50 push eax 393: 8d 85 f8 ff ff ff lea eax,[ebp-0x8] 399: 8b 00 mov eax,DWORD PTR [eax] 39b: 59 pop ecx 39c: 3b c1 cmp eax,ecx 39e: 0f 9c c0 setl al 3a1: 0f b6 c0 movzx eax,al 3a4: 81 f8 00 00 00 00 cmp eax,0x0 3aa: 0f 84 56 00 00 00 je 406 <ata_write_sector.L8> 3b0: 8d 85 f8 ff ff ff lea eax,[ebp-0x8] 3b6: 8b 00 mov eax,DWORD PTR [eax] 3b8: 50 push eax 3b9: 8d 85 fc ff ff ff lea eax,[ebp-0x4] 3bf: 8b 00 mov eax,DWORD PTR [eax] 3c1: 59 pop ecx 3c2: 69 c9 02 00 00 00 imul ecx,ecx,0x2 3c8: 03 c1 add eax,ecx 3ca: 66 8b 00 mov ax,WORD PTR [eax] 3cd: 0f b7 c0 movzx eax,ax 3d0: 50 push eax 3d1: b8 00 00 00 00 mov eax,0x0 3d6: 50 push eax 3d7: b8 f0 01 00 00 mov eax,0x1f0 3dc: 59 pop ecx 3dd: 03 c1 add eax,ecx 3df: 50 push eax 3e0: e8 67 fc ff ff call 4c <outw> 3e5: 81 c4 08 00 00 00 add esp,0x8 000003eb <ata_write_sector.L9>: 3eb: 8d 85 f8 ff ff ff lea eax,[ebp-0x8] 3f1: 8b c8 mov ecx,eax 3f3: 8b 01 mov eax,DWORD PTR [ecx] 3f5: 8b d0 mov edx,eax 3f7: 81 c0 01 00 00 00 add eax,0x1 3fd: 89 01 mov DWORD PTR [ecx],eax 3ff: 8b c2 mov eax,edx 401: e9 87 ff ff ff jmp 38d <ata_write_sector.L7> 00000406 <ata_write_sector.L8>: 406: b8 e7 00 00 00 mov eax,0xe7 40b: 50 push eax 40c: b8 07 00 00 00 mov eax,0x7 411: 50 push eax 412: b8 f0 01 00 00 mov eax,0x1f0 417: 59 pop ecx 418: 03 c1 add eax,ecx 41a: 50 push eax 41b: e8 e0 fb ff ff call 0 <outb> 420: 81 c4 08 00 00 00 add esp,0x8 426: e8 70 fc ff ff call 9b <ata_wait_bsy> 42b: b8 00 00 00 00 mov eax,0x0 430: 8b e5 mov esp,ebp 432: 5d pop ebp 433: c3 ret
$ cd /home/martin/workspace/prototypes/boot-to-claude objdump -d -M intel /tmp/ata_recheck.o 2>&1 | grep -B3 -A3 "<inw>:"
77: 5d pop ebp 78: c3 ret 00000079 <inw>: 79: 55 push ebp 7a: 8b ec mov ebp,esp 7c: 8d 85 08 00 00 00 lea eax,[ebp+0x8]
$ cd /home/martin/workspace/prototypes/boot-to-claude objdump -d -M intel /tmp/ata_recheck.o 2>&1 | sed -n '/00000079 <inw>:/,/ret/p'
00000079 <inw>: 79: 55 push ebp 7a: 8b ec mov ebp,esp 7c: 8d 85 08 00 00 00 lea eax,[ebp+0x8] 82: 66 8b 00 mov ax,WORD PTR [eax] 85: 0f b7 c0 movzx eax,ax 88: 8b d0 mov edx,eax 8a: ed in eax,dx 8b: 0f b7 c0 movzx eax,ax 8e: 8b e5 mov esp,ebp 90: 5d pop ebp 91: c3 ret
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n '"in"\|"out"' toolchain/as/encode.c
678: if (strcmp(m, "in") == 0) {
683: if (strcmp(m, "out") == 0) {675 if (is_reg32(a->reg) || is_reg16(a->reg)) { e8(&e, (uint8_t)(0x58 + regnum(a->reg))); return e.len; }
676 }
677
678 if (strcmp(m, "in") == 0) {
679 if (b->kind == OPND_IMM) { e8(&e, 0xE4); e8(&e, (uint8_t)b->disp); return e.len; }
680 if (b->reg == R_DX && a->reg == R_AL) { e8(&e, 0xEC); return e.len; }
681 if (b->reg == R_DX && a->reg == R_AX) { e8(&e, 0xED); return e.len; }
682 }
683 if (strcmp(m, "out") == 0) {
684 if (a->kind == OPND_IMM) { e8(&e, 0xE6); e8(&e, (uint8_t)a->disp); return e.len; }
685 if (a->reg == R_DX && b->reg == R_AL) { e8(&e, 0xEE); return e.len; }
686 if (a->reg == R_DX && b->reg == R_AX) { e8(&e, 0xEF); return e.len; }
687 }
688
689 if (strcmp(m, "int") == 0) { e8(&e, 0xCD); e8(&e, (uint8_t)a->disp); return e.len; }
690 if (strcmp(m, "lgdt") == 0 && st->bits == 16) {
691 /* boot/stage2.S's real-mode form: a direct 16-bit absolute
692 * address, hardcoded to that one ModRM byte (mod=00, reg=2,
693 * rm=110 "disp16 direct address") - distinct from the 32-bit
694 * register-indirect form below, which CC's __builtin_gdt_flush- if (strcmp(m, "in") == 0) {
- if (b->kind == OPND_IMM) { e8(&e, 0xE4); e8(&e, (uint8_t)b->disp); return e.len; }
- if (b->reg == R_DX && a->reg == R_AL) { e8(&e, 0xEC); return e.len; }
- if (b->reg == R_DX && a->reg == R_AX) { e8(&e, 0xED); return e.len; }
- }
- if (strcmp(m, "out") == 0) {
- if (a->kind == OPND_IMM) { e8(&e, 0xE6); e8(&e, (uint8_t)a->disp); return e.len; }
- if (a->reg == R_DX && b->reg == R_AL) { e8(&e, 0xEE); return e.len; }
- if (a->reg == R_DX && b->reg == R_AX) { e8(&e, 0xEF); return e.len; }
- }
+ if (strcmp(m, "in") == 0) {
+ if (b->kind == OPND_IMM) { e8(&e, 0xE4); e8(&e, (uint8_t)b->disp); return e.len; }
+ if (b->reg == R_DX && a->reg == R_AL) { e8(&e, 0xEC); return e.len; }
+ /* IN AX,DX needs the 0x66 operand-size override - 0xED alone,
+ * in 32-bit code, means IN EAX,DX (the default operand size).
+ * Without the prefix this silently read two 16-bit words per
+ * call (packed into EAX, high half discarded by the caller's
+ * own movzx), draining twice as many words off the ATA data
+ * port as intended and dropping every other one - see
+ * kernel/ata.c / docs/toolchain.md. */
+ if (b->reg == R_DX && a->reg == R_AX) { e8(&e, 0x66); e8(&e, 0xED); return e.len; }
+ if (b->reg == R_DX && a->reg == R_EAX) { e8(&e, 0xED); return e.len; }
+ }
+ if (strcmp(m, "out") == 0) {
+ if (a->kind == OPND_IMM) { e8(&e, 0xE6); e8(&e, (uint8_t)a->disp); return e.len; }
+ if (a->reg == R_DX && b->reg == R_AL) { e8(&e, 0xEE); return e.len; }
+ if (a->reg == R_DX && b->reg == R_AX) { e8(&e, 0x66); e8(&e, 0xEF); return e.len; }
+ if (a->reg == R_DX && b->reg == R_EAX) { e8(&e, 0xEF); return e.len; }
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/encode.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -Wall -Wextra -O2 -o build/toolchain/as toolchain/as/*.c 2>&1 | tail -40 ./tools/run_toolchain_tests.sh 2>&1 | tail -13 build/toolchain/cc -Ikernel -Itoolchain/include -o /tmp/ata_final.S kernel/ata.c build/toolchain/as -f elf32 -o /tmp/ata_final.o /tmp/ata_final.S objdump -d -M intel /tmp/ata_final.o 2>&1 | grep -A10 "<inw>:"
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) PASS t10_short (6) run_toolchain_tests: PASS 0000007a <inw>: 7a: 55 push ebp 7b: 8b ec mov ebp,esp 7d: 8d 85 08 00 00 00 lea eax,[ebp+0x8] 83: 66 8b 00 mov ax,WORD PTR [eax] 86: 0f b7 c0 movzx eax,ax 89: 8b d0 mov edx,eax 8b: 66 ed in ax,dx 8d: 0f b7 c0 movzx eax,ax 90: 8b e5 mov esp,ebp 92: 5d pop ebp
$ cd /home/martin/workspace/prototypes/boot-to-claude make clean make 2>&1 | tail -20
rm -rf build build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/compiler/codegen.S kernel/lang/compiler/codegen.c build/toolchain/as -f elf32 -o build/lang/compiler/codegen.o build/lang/compiler/codegen.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/vm.S kernel/lang/vm.c build/toolchain/as -f elf32 -o build/lang/vm.o build/lang/vm.S gcc -Wall -Wextra -O2 -o build/toolchain/ld toolchain/ld/elf_reader.c toolchain/ld/link_flat.c toolchain/ld/link_static_exec.c toolchain/ld/main.c build/toolchain/ld -T kernel/link.ld --format bin -o build/kernel.bin build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_tinyfs.S kernel/fs/tinyfs.c build/toolchain/as -f elf32 -o build/host_tinyfs.o build/host_tinyfs.S build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_string.S kernel/string.c build/toolchain/as -f elf32 -o build/host_string.o build/host_string.S build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_mkfs_tinyfs.S tools/mkfs_tinyfs.c build/toolchain/as -f elf32 -o build/host_mkfs_tinyfs.o build/host_mkfs_tinyfs.S build/toolchain/ld -o build/mkfs_tinyfs build/host_tinyfs.o build/host_string.o build/host_mkfs_tinyfs.o dd if=/dev/zero of=build/disk.img bs=512 count=4096 status=none dd if=build/boot.bin of=build/disk.img conv=notrunc status=none dd if=build/stage2.bin of=build/disk.img bs=512 seek=1 conv=notrunc status=none dd if=build/kernel.bin of=build/disk.img bs=512 seek=5 conv=notrunc status=none build/mkfs_tinyfs build/disk.img tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc mkfs_tinyfs: done rm build/console.S build/shell/shell.S build/idt.S build/lang/script/parser.S build/lang/script/ast.S build/lang/script/eval.S build/lang/vm.S build/lang/compiler/codegen.S build/lang/compiler/parser.S build/kmain.S build/gdt.S build/string.S build/fs/tinyfs.S build/selftest.S build/serial.S build/lang/lexer.S build/fs/tinyfs_disk.S build/ata.S build/keyboard.S build/memory.S
$ cd /home/martin/workspace/prototypes/boot-to-claude ./tools/run_tests.sh 2>&1 | tail -80
run_tests: FAIL - timed out waiting for 'hello.txt' --- serial log --- tiny-os: kernel entered selftest: running SELFTEST PASS tiny-os shell. type 'help' for commands. > qemu-system-i386: terminating on signal 15 from pid 3375094 (bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude sed -n '1,120p' tools/run_tests.sh | grep -n "hello.txt"
75:wait_for "hello.txt" 77:type_line "cat hello.txt"
55 # (in order) somewhere in the serial log - used for .tsh/.tc fixtures run
56 # through the shell's `run`/`build`+`exec` commands.
57 check_fixture_output() {
58 local expected_file="$1"
59 while IFS= read -r want; do
60 [ -z "$want" ] && continue
61 wait_for "$want" 30
62 done < "$expected_file"
63 }
64
65 for i in $(seq 1 25); do
66 [ -S "$MON" ] && break
67 sleep 0.2
68 done
69 [ -S "$MON" ] || fail "qemu monitor socket never appeared"
70
71 wait_for "SELFTEST PASS"
72 wait_for "type 'help'"
73
74 type_line "ls"
75 wait_for "hello.txt"
76
77 type_line "cat hello.txt"
78 wait_for "Hello, tinyfs!"
79
80 type_line "write greet.txt hi there"
81 wait_for "write: ok"
82
83 type_line "cat greet.txt"
84 wait_for "hi there"$ cd /home/martin/workspace/prototypes/boot-to-claude cat build/serial.log 2>/dev/null | tail -40
tiny-os: kernel entered selftest: running SELFTEST PASS tiny-os shell. type 'help' for commands. >
$ cd /home/martin/workspace/prototypes/boot-to-claude cat tools/qemu_type.py | head -60
#!/usr/bin/env python3
"""Types text into a running QEMU guest via its HMP monitor's `sendkey`
command, over a unix socket (-monitor unix:PATH,server,nowait). Used by
tools/run_tests.sh to drive the kernel shell headlessly - there is no
other way to feed the polled PS/2 keyboard driver without real hardware
or a display.
"""
import socket
import sys
import time
KEYMAP = {
" ": "spc",
".": "dot",
"\n": "ret",
"-": "minus",
"_": "shift-minus",
",": "comma",
"/": "slash",
}
def key_name(c):
if c in KEYMAP:
return KEYMAP[c]
if c.isalpha():
return c.lower()
if c.isdigit():
return c
raise ValueError(f"qemu_type: no key mapping for {c!r}")
def main():
if len(sys.argv) != 3:
print(f"usage: {sys.argv[0]} <monitor-socket> <text>", file=sys.stderr)
sys.exit(1)
sock_path, text = sys.argv[1], sys.argv[2]
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect(sock_path)
time.sleep(0.2)
s.recv(4096) # monitor banner
for c in text:
s.sendall(f"sendkey {key_name(c)}\n".encode())
time.sleep(0.05)
s.recv(4096)
s.close()
if __name__ == "__main__":
main()$ cd /home/martin/workspace/prototypes/boot-to-claude ./tools/run_tests.sh 2>&1 | tail -20
run_tests: FAIL - timed out waiting for 'hello.txt' --- serial log --- tiny-os: kernel entered selftest: running SELFTEST PASS tiny-os shell. type 'help' for commands. > qemu-system-i386: terminating on signal 15 from pid 3376433 (bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude cat kernel/keyboard.c
#include "keyboard.h"
#include <stddef.h>
#include <stdint.h>
#include "builtins.h"
#define KBD_DATA 0x60
#define KBD_STATUS 0x64
#define SC_LSHIFT 0x2A
#define SC_RSHIFT 0x36
static inline uint8_t inb(uint16_t port) { return __builtin_inb(port); }
/* Scancode set 1, indexed by make code. 0 = unmapped/non-printable. */
static const char lower_table[] = {
0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
'\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
0 /*ctrl*/, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`',
0 /*lshift*/, '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/',
0 /*rshift*/, '*', 0 /*alt*/, ' ',
};
static const char upper_table[] = {
0, 27, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b',
'\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n',
0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~',
0, '|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?',
0, '*', 0, ' ',
};
#define TABLE_LEN (sizeof(lower_table) / sizeof(lower_table[0]))
static int shift_down = 0;
static int scancode_ready(void) {
return inb(KBD_STATUS) & 1;
}
char keyboard_getchar(void) {
for (;;) {
while (!scancode_ready()) {}
uint8_t sc = inb(KBD_DATA);
int release = sc & 0x80;
uint8_t code = sc & 0x7F;
if (code == SC_LSHIFT || code == SC_RSHIFT) {
shift_down = !release;
continue;
}
if (release || code >= TABLE_LEN) {
continue;
}
char c = shift_down ? upper_table[code] : lower_table[code];
if (c == 0) {
continue;
}
return c;
}
}$ cd /home/martin/workspace/prototypes/boot-to-claude rm -f /tmp/serial_shell.log /tmp/mon_shell.sock qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial file:/tmp/serial_shell.log -monitor unix:/tmp/mon_shell.sock,server,nowait -no-reboot & QPID=$! for i in $(seq 1 25); do [ -S /tmp/mon_shell.sock ] && break; sleep 0.2; done sleep 3 python3 tools/qemu_type.py /tmp/mon_shell.sock "ls " sleep 2 cat /tmp/serial_shell.log kill $QPID 2>/dev/null
tiny-os: kernel entered selftest: running SELFTEST PASS tiny-os shell. type 'help' for commands. > qemu-system-i386: terminating on signal 15 from pid 3379030 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "shell_run\|keyboard_getchar" kernel/shell/shell.c | head -10
36: char c = keyboard_getchar();
206:void shell_run(void) {20
21 static void echo_char(char c) {
22 console_putc(c);
23 serial_putc(c);
24 }
25
26 static void print_line(const char *s) {
27 console_write(s);
28 console_write("\n");
29 serial_write(s);
30 serial_write("\n");
31 }
32
33 static int read_line(char *buf, int max_len) {
34 int len = 0;
35 for (;;) {
36 char c = keyboard_getchar();
37 if (c == '\n') {
38 echo_char('\n');
39 buf[len] = '\0';
40 return len;
41 } else if (c == '\b') {
42 if (len > 0) {
43 len--;
44 echo_char('\b');
45 echo_char(' ');
46 echo_char('\b');
47 }
48 } else if (c >= 32 && c < 127 && len < max_len - 1) {
49 buf[len++] = c;
50 echo_char(c);
51 }
52 }
53 }
54
55 static void skip_spaces(const char **p) {
56 while (**p == ' ') {
57 (*p)++;
58 }
59 }
60
61 static int next_word(const char **p, char *out, int max_len) {
62 skip_spaces(p);
63 int n = 0;
64 while (**p && **p != ' ' && n < max_len - 1) {
65 out[n++] = *(*p)++;
66 }
67 out[n] = '\0';
68 return n;
69 }$ cd /home/martin/workspace/prototypes/boot-to-claude KOBJS="build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o" build/toolchain/ld -T kernel/link.ld -o /tmp/kernel_dbg3.elf $KOBJS nm /tmp/kernel_dbg3.elf | grep -i "keyboard_getchar\|scancode_ready"
00013a34 T keyboard_getchar
$ cd /home/martin/workspace/prototypes/boot-to-claude qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_kbd.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x13a34 continue info registers eip stepi 10 info registers eax stepi 10 info registers eax stepi 10 info registers eax quit EOF timeout 15 gdb -q -batch -x /tmp/gdb_kbd.txt 2>&1 | tail -20 kill $QPID 2>/dev/null
The target architecture is set to "i386". warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000fff0 in ?? () Breakpoint 1 at 0x13a34 Breakpoint 1, 0x00013a34 in ?? () eip 0x13a34 0x13a34 0x00013a19 in ?? () eax 0x64 100 0x000139ff in ?? () eax 0x1c 28 0x00013a4b in ?? () eax 0x1 1 A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached] qemu-system-i386: terminating on signal 15 from pid 3379733 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude
rm -f /tmp/serial_shell2.log /tmp/mon_shell2.sock
qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial file:/tmp/serial_shell2.log -monitor unix:/tmp/mon_shell2.sock,server,nowait -no-reboot &
QPID=$!
for i in $(seq 1 25); do [ -S /tmp/mon_shell2.sock ] && break; sleep 0.2; done
sleep 5
python3 -c "
import socket, time
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect('/tmp/mon_shell2.sock')
time.sleep(0.3)
print(s.recv(4096))
s.sendall(b'sendkey l\n')
time.sleep(0.3)
print(s.recv(4096))
s.sendall(b'sendkey s\n')
time.sleep(0.3)
print(s.recv(4096))
s.sendall(b'sendkey ret\n')
time.sleep(0.3)
print(s.recv(4096))
"
sleep 2
cat /tmp/serial_shell2.log
kill $QPID 2>/dev/nullb"QEMU 10.1.0 monitor - type 'help' for more information\r\n(qemu) " b's\x1b[K\x1b[Dse\x1b[K\x1b[D\x1b[Dsen\x1b[K\x1b[D\x1b[D\x1b[Dsend\x1b[K\x1b[D\x1b[D\x1b[D\x1b[Dsendk\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Dsendke\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Dsendkey\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Dsendkey \x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Dsendkey l\x1b[K\r\n(qemu) ' b's\x1b[K\x1b[Dse\x1b[K\x1b[D\x1b[Dsen\x1b[K\x1b[D\x1b[D\x1b[Dsend\x1b[K\x1b[D\x1b[D\x1b[D\x1b[Dsendk\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Dsendke\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Dsendkey\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Dsendkey \x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Dsendkey s\x1b[K\r\n(qemu) ' b's\x1b[K\x1b[Dse\x1b[K\x1b[D\x1b[Dsen\x1b[K\x1b[D\x1b[D\x1b[Dsend\x1b[K\x1b[D\x1b[D\x1b[D\x1b[Dsendk\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Dsendke\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Dsendkey\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Dsendkey \x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Dsendkey r\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Dsendkey re\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Dsendkey ret\x1b[K\r\n(qemu) ' tiny-os: kernel entered selftest: running SELFTEST PASS tiny-os shell. type 'help' for commands. > qemu-system-i386: terminating on signal 15 from pid 3380145 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude build/toolchain/cc -Ikernel -Itoolchain/include -o /tmp/keyboard_check.S kernel/keyboard.c sed -n '/^keyboard_getchar:/,/^ret$/p' /tmp/keyboard_check.S | head -80
keyboard_getchar:
push ebp
mov ebp, esp
sub esp, 16
.L0:
.L3:
call scancode_ready
cmp eax, 0
sete al
movzx eax, al
cmp eax, 0
je .L4
jmp .L3
.L4:
lea eax, [ebp-4]
push eax
mov eax, 96
push eax
call inb
add esp, 4
pop ecx
mov [ecx], al
lea eax, [ebp-8]
push eax
mov eax, 128
push eax
lea eax, [ebp-4]
movzx eax, byte [eax]
pop ecx
and eax, ecx
pop ecx
mov [ecx], eax
lea eax, [ebp-12]
push eax
mov eax, 127
push eax
lea eax, [ebp-4]
movzx eax, byte [eax]
pop ecx
and eax, ecx
pop ecx
mov [ecx], al
mov eax, 42
push eax
lea eax, [ebp-12]
movzx eax, byte [eax]
pop ecx
cmp eax, ecx
sete al
movzx eax, al
cmp eax, 0
jne .L7
mov eax, 54
push eax
lea eax, [ebp-12]
movzx eax, byte [eax]
pop ecx
cmp eax, ecx
sete al
movzx eax, al
cmp eax, 0
jne .L7
mov eax, 0
jmp .L8
.L7:
mov eax, 1
.L8:
cmp eax, 0
je .L5
lea eax, [shift_down]
push eax
lea eax, [ebp-8]
mov eax, [eax]
cmp eax, 0
sete al
movzx eax, al
pop ecx
mov [ecx], eax
jmp .L2
jmp .L6$ cd /home/martin/workspace/prototypes/boot-to-claude git status --short | head -5 git stash list
M Makefile M kernel/lang/script/eval.c M toolchain/as/encode.c M toolchain/cc/cc.h M toolchain/cc/codegen.c
$ cd /home/martin/workspace/prototypes/boot-to-claude mkdir -p /tmp/old_test git show HEAD:Makefile > /tmp/old_test/Makefile cp -r kernel boot tests tools /tmp/old_test/ 2>/dev/null # use the git HEAD version of the builtins_shim and host_syscalls_shim which the new tree no longer references in the Makefile but files still exist cd /tmp/old_test sed -i 's|^BUILD := build|BUILD := build|' Makefile make 2>&1 | tail -20
gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/lang/script/parser.o kernel/lang/script/parser.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/lang/script/eval.o kernel/lang/script/eval.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/lang/compiler/parser.o kernel/lang/compiler/parser.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/lang/compiler/codegen.o kernel/lang/compiler/codegen.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/lang/vm.o kernel/lang/vm.c ld -m elf_i386 -T kernel/link.ld -nostdlib -o build/kernel.elf build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/builtins_shim.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o ld: warning: build/kernel.elf has a LOAD segment with RWX permissions objcopy -O binary build/kernel.elf build/kernel.bin gcc -m32 -Wall -Wextra -O2 -Ikernel -c -o build/host_tinyfs.o kernel/fs/tinyfs.c gcc -m32 -Wall -Wextra -O2 -Ikernel -c -o build/host_string.o kernel/string.c gcc -m32 -Wall -Wextra -O2 -Ikernel -c -o build/host_syscalls_shim.o tools/host_syscalls_shim.c gcc -m32 -Wall -Wextra -O2 -Ikernel -c -o build/host_mkfs_tinyfs.o tools/mkfs_tinyfs.c gcc -m32 -o build/mkfs_tinyfs build/host_tinyfs.o build/host_string.o build/host_syscalls_shim.o build/host_mkfs_tinyfs.o dd if=/dev/zero of=build/disk.img bs=512 count=4096 status=none dd if=build/boot.bin of=build/disk.img conv=notrunc status=none dd if=build/stage2.bin of=build/disk.img bs=512 seek=1 conv=notrunc status=none dd if=build/kernel.bin of=build/disk.img bs=512 seek=5 conv=notrunc status=none build/mkfs_tinyfs build/disk.img tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc mkfs_tinyfs: done Shell cwd was reset to /home/martin/workspace/prototypes/boot-to-claude
$ cd /tmp/old_test rm -f /tmp/serial_old.log /tmp/mon_old.sock qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial file:/tmp/serial_old.log -monitor unix:/tmp/mon_old.sock,server,nowait -no-reboot & QPID=$! for i in $(seq 1 25); do [ -S /tmp/mon_old.sock ] && break; sleep 0.2; done sleep 3 python3 tools/qemu_type.py /tmp/mon_old.sock "ls " sleep 2 cat /tmp/serial_old.log kill $QPID 2>/dev/null
tiny-os: kernel entered selftest: running SELFTEST PASS tiny-os shell. type 'help' for commands. > ls hello.txt 15 hello.tsh 281 hello.tc 272 > qemu-system-i386: terminating on signal 15 from pid 3381504 (/bin/bash) Shell cwd was reset to /home/martin/workspace/prototypes/boot-to-claude
$ cd /home/martin/workspace/prototypes/boot-to-claude sed -n '200,230p' kernel/shell/shell.c
outb(0x64, 0xFE); /* pulse the keyboard controller's reset line */
for (;;) {
__builtin_hlt();
}
}
void shell_run(void) {
static char line[LINE_MAX];
print_line("tiny-os shell. type 'help' for commands.");
for (;;) {
console_write("> ");
serial_write("> ");
read_line(line, sizeof(line));
const char *p = line;
char cmd[32];
if (next_word(&p, cmd, sizeof(cmd)) == 0) {
continue;
}
if (strcmp(cmd, "help") == 0) {
cmd_help();
} else if (strcmp(cmd, "ls") == 0) {
cmd_ls();
} else if (strcmp(cmd, "clear") == 0) {
console_clear();
} else if (strcmp(cmd, "reboot") == 0) {
cmd_reboot();
} else if (strcmp(cmd, "cat") == 0) {$ cd /home/martin/workspace/prototypes/boot-to-claude sed -n '/^scancode_ready:/,/^ret$/p' /tmp/keyboard_check.S | head -20
scancode_ready:
push ebp
mov ebp, esp
mov eax, 1
push eax
mov eax, 100
push eax
call inb
add esp, 4
pop ecx
and eax, ecx
mov esp, ebp
pop ebp
ret
mov eax, 0
mov esp, ebp
pop ebp
ret
global keyboard_getchar
keyboard_getchar:$ cd /home/martin/workspace/prototypes/boot-to-claude sed -n '/^inb:/,/^ret$/p' /tmp/keyboard_check.S | head -20
inb:
push ebp
mov ebp, esp
lea eax, [ebp+8]
mov ax, [eax]
movzx eax, ax
mov edx, eax
in al, dx
movzx eax, al
mov esp, ebp
pop ebp
ret
mov eax, 0
mov esp, ebp
pop ebp
ret
scancode_ready:
push ebp
mov ebp, esp
mov eax, 1$ cd /home/martin/workspace/prototypes/boot-to-claude rm -f /tmp/mon_final.sock qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -monitor unix:/tmp/mon_final.sock,server,nowait -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_run.txt << 'EOF' set architecture i386 target remote localhost:1234 continue EOF timeout 3 gdb -q -batch -x /tmp/gdb_run.txt > /tmp/gdb_run_out.txt 2>&1 & GDBPID=$! sleep 3 for i in $(seq 1 25); do [ -S /tmp/mon_final.sock ] && break; sleep 0.2; done sleep 3 python3 tools/qemu_type.py /tmp/mon_final.sock "l" sleep 1 kill -INT $QPID 2>/dev/null sleep 1 cat > /tmp/gdb_inspect.txt << 'EOF' set architecture i386 target remote localhost:1234 x/1xb 0x60000 info registers eip quit EOF timeout 10 gdb -q -batch -x /tmp/gdb_inspect.txt 2>&1 | tail -10 kill $QPID 2>/dev/null wait $GDBPID 2>/dev/null
Exit code 124 qemu-system-i386: terminating on signal 2 from pid 3383153 (/bin/bash) Terminated
$ cd /home/martin/workspace/prototypes/boot-to-claude rm -f /tmp/mon_final2.sock /tmp/serial_final2.log qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial file:/tmp/serial_final2.log -monitor unix:/tmp/mon_final2.sock,server,nowait -no-reboot -s & QPID=$! for i in $(seq 1 25); do [ -S /tmp/mon_final2.sock ] && break; sleep 0.2; done sleep 3 python3 tools/qemu_type.py /tmp/mon_final2.sock "l" sleep 1 cat > /tmp/gdb_interrupt.txt << 'EOF' set architecture i386 target remote localhost:1234 interrupt info registers eip x/20i $eip quit EOF timeout 10 gdb -q -batch -x /tmp/gdb_interrupt.txt 2>&1 | tail -30 kill $QPID 2>/dev/null
warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x000139fa in ?? () eip 0x139fa 0x139fa => 0x139fa: movzbl %al,%eax 0x139fd: mov %ebp,%esp 0x139ff: pop %ebp 0x13a00: ret 0x13a01: mov $0x0,%eax 0x13a06: mov %ebp,%esp 0x13a08: pop %ebp 0x13a09: ret 0x13a0a: push %ebp 0x13a0b: mov %esp,%ebp 0x13a0d: mov $0x1,%eax 0x13a12: push %eax 0x13a13: mov $0x64,%eax 0x13a18: push %eax 0x13a19: call 0x139e8 0x13a1e: add $0x4,%esp 0x13a24: pop %ecx 0x13a25: and %ecx,%eax 0x13a27: mov %ebp,%esp 0x13a29: pop %ebp A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached] qemu-system-i386: terminating on signal 15 from pid 3383684 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude objdump -d -M intel /tmp/kernel_dbg3.elf 2>&1 | grep -A10 "^000139e8 <inb>:" 2>/dev/null nm /tmp/kernel_dbg3.elf | grep -i "^000139e8\| inb$"
(Bash completed with no output)
$ cd /home/martin/workspace/prototypes/boot-to-claude rm -f /tmp/mon_final3.sock /tmp/serial_final3.log qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial file:/tmp/serial_final3.log -monitor unix:/tmp/mon_final3.sock,server,nowait -no-reboot -s -S & QPID=$! sleep 1 cat > /tmp/gdb_watch.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x139fa continue c c c c c c c c c c info registers eax quit EOF timeout 15 gdb -q -batch -x /tmp/gdb_watch.txt > /tmp/gdb_watch_out.txt 2>&1 & GDBPID=$! sleep 5 for i in $(seq 1 25); do [ -S /tmp/mon_final3.sock ] && break; sleep 0.2; done python3 tools/qemu_type.py /tmp/mon_final3.sock "l" 2>/dev/null wait $GDBPID 2>/dev/null kill $QPID 2>/dev/null tail -40 /tmp/gdb_watch_out.txt
qemu-system-i386: terminating on signal 15 from pid 3386403 (/bin/bash) The target architecture is set to "i386". warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000fff0 in ?? () Breakpoint 1 at 0x139fa Breakpoint 1, 0x000139fa in ?? () Breakpoint 1, 0x000139fa in ?? () Breakpoint 1, 0x000139fa in ?? () Breakpoint 1, 0x000139fa in ?? () Breakpoint 1, 0x000139fa in ?? () Breakpoint 1, 0x000139fa in ?? () Breakpoint 1, 0x000139fa in ?? () Breakpoint 1, 0x000139fa in ?? () Breakpoint 1, 0x000139fa in ?? () Breakpoint 1, 0x000139fa in ?? () Breakpoint 1, 0x000139fa in ?? () eax 0x1c 28 A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached]
$ cd /home/martin/workspace/prototypes/boot-to-claude rm -f /tmp/mon_final4.sock /tmp/serial_final4.log qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial file:/tmp/serial_final4.log -monitor unix:/tmp/mon_final4.sock,server,nowait -no-reboot -s & QPID=$! for i in $(seq 1 25); do [ -S /tmp/mon_final4.sock ] && break; sleep 0.2; done sleep 3 python3 tools/qemu_type.py /tmp/mon_final4.sock "l" sleep 1 echo "--- key sent, now attaching gdb ---" cat > /tmp/gdb_watch2.txt << 'EOF' set architecture i386 target remote localhost:1234 break *0x139fa continue info registers eax continue info registers eax quit EOF timeout 10 gdb -q -batch -x /tmp/gdb_watch2.txt 2>&1 | tail -20 kill $QPID 2>/dev/null
--- key sent, now attaching gdb --- The target architecture is set to "i386". warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x000139fa in ?? () Breakpoint 1 at 0x139fa Breakpoint 1, 0x000139fa in ?? () eax 0x1c 28 Breakpoint 1, 0x000139fa in ?? () eax 0x1c 28 A debugging session is active. Inferior 1 [process 1] will be detached. Quit anyway? (y or n) [answered Y; input not from terminal] [Inferior 1 (process 1) detached] qemu-system-i386: terminating on signal 15 from pid 3386792 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude
rm -f /tmp/mon_final5.sock /tmp/serial_final5.log
qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial file:/tmp/serial_final5.log -monitor unix:/tmp/mon_final5.sock,server,nowait -no-reboot &
QPID=$!
for i in $(seq 1 25); do [ -S /tmp/mon_final5.sock ] && break; sleep 0.2; done
sleep 3
python3 -c "
import socket, time
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect('/tmp/mon_final5.sock')
time.sleep(0.3)
s.recv(4096)
s.sendall(b'i /b 0x64\n')
time.sleep(0.3)
print('before key:', s.recv(4096))
s.sendall(b'sendkey l\n')
time.sleep(0.3)
s.recv(4096)
s.sendall(b'i /b 0x64\n')
time.sleep(0.3)
print('after key:', s.recv(4096))
"
kill $QPID 2>/dev/nullbefore key: b'i\x1b[K\x1b[Di \x1b[K\x1b[D\x1b[Di /\x1b[K\x1b[D\x1b[D\x1b[Di /b\x1b[K\x1b[D\x1b[D\x1b[D\x1b[Di /b \x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Di /b 0\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Di /b 0x\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Di /b 0x6\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Di /b 0x64\x1b[K\r\nportb[0x0064] = 0x1c\r\n(qemu) ' after key: b'i\x1b[K\x1b[Di \x1b[K\x1b[D\x1b[Di /\x1b[K\x1b[D\x1b[D\x1b[Di /b\x1b[K\x1b[D\x1b[D\x1b[D\x1b[Di /b \x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Di /b 0\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Di /b 0x\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Di /b 0x6\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Di /b 0x64\x1b[K\r\nportb[0x0064] = 0x1c\r\n(qemu) ' qemu-system-i386: terminating on signal 15 from pid 3387276 (/bin/bash)
$ cd /tmp/old_test rm -f /tmp/serial_old2.log /tmp/mon_old2.sock qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial file:/tmp/serial_old2.log -monitor unix:/tmp/mon_old2.sock,server,nowait -no-reboot & QPID=$! for i in $(seq 1 25); do [ -S /tmp/mon_old2.sock ] && break; sleep 0.2; done sleep 3 python3 tools/qemu_type.py /tmp/mon_old2.sock "ls " sleep 2 cat /tmp/serial_old2.log kill $QPID 2>/dev/null
tiny-os: kernel entered selftest: running SELFTEST PASS tiny-os shell. type 'help' for commands. > ls hello.txt 15 hello.tsh 281 hello.tc 272 > qemu-system-i386: terminating on signal 15 from pid 3388100 (/bin/bash) Shell cwd was reset to /home/martin/workspace/prototypes/boot-to-claude
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -rn "outb\|0x64\|0x60\b" kernel/*.c kernel/*/*.c | grep -v "keyboard.c\|// \|/\*"
kernel/ata.c:24:static inline void outb(uint16_t port, uint8_t val) { __builtin_outb(port, val); }
kernel/ata.c:38: outb(ATA_DRIVE, (uint8_t)(0xE0 | ((lba >> 24) & 0x0F)));
kernel/ata.c:39: outb(ATA_SECCOUNT, 1);
kernel/ata.c:40: outb(ATA_LBA_LO, (uint8_t)(lba & 0xFF));
kernel/ata.c:41: outb(ATA_LBA_MID, (uint8_t)((lba >> 8) & 0xFF));
kernel/ata.c:42: outb(ATA_LBA_HI, (uint8_t)((lba >> 16) & 0xFF));
kernel/ata.c:48: outb(ATA_COMMAND, ATA_CMD_READ);
kernel/ata.c:61: outb(ATA_COMMAND, ATA_CMD_WRITE);
kernel/ata.c:70: outb(ATA_COMMAND, ATA_CMD_CACHE_FLUSH);
kernel/builtins_shim.c:11:void __builtin_outb(uint16_t port, uint8_t val) {
kernel/builtins_shim.c:12: __asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
kernel/serial.c:12:static inline void outb(uint16_t port, uint8_t val) { __builtin_outb(port, val); }
kernel/serial.c:31: outb(COM1, (uint8_t)c);
kernel/shell/shell.c:18:static inline void outb(uint16_t port, uint8_t val) { __builtin_outb(port, val); }
kernel/shell/shell.c:198: status = inb(0x64);$ cd /home/martin/workspace/prototypes/boot-to-claude sed -n '190,205p' kernel/shell/shell.c
print_line("rm: ok");
}
}
static void cmd_reboot(void) {
print_line("rebooting...");
uint8_t status;
do {
status = inb(0x64);
} while (status & 0x02);
outb(0x64, 0xFE); /* pulse the keyboard controller's reset line */
for (;;) {
__builtin_hlt();
}
}$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "esp\|stack" kernel/entry.S | head -10 echo "---" ls -la build/kernel.bin /tmp/old_test/build/kernel.bin
2:; segments already flat and a real-mode stack still set up. Establish our 3:; own stack and hand off to C. 14: mov esp, stack_top 32:stack_bottom: 34:stack_top: --- -rwxr-x--- 1 martin martin 48456 Jul 18 18:19 /tmp/old_test/build/kernel.bin -rw-r----- 1 martin martin 63892 Jul 18 18:15 build/kernel.bin
$ cd /home/martin/workspace/prototypes/boot-to-claude sed -n '1,35p' kernel/entry.S
; Kernel entry point. Stage2 jumps here in 32-bit protected mode with
; segments already flat and a real-mode stack still set up. Establish our
; own stack and hand off to C.
BITS 32
section .text.entry
global _start
extern kmain
extern _bss_start
extern _bss_end
_start:
mov esp, stack_top
; Zero .bss: the raw disk-loaded kernel image has no zero-fill
; guarantee (objcopy -O binary drops NOBITS sections entirely).
mov edi, _bss_start
mov ecx, _bss_end
sub ecx, edi
xor eax, eax
cld
rep stosb
call kmain
.hang:
hlt
jmp .hang
section .bss
align 16
stack_bottom:
resb 16384
stack_top:$ cd /home/martin/workspace/prototypes/boot-to-claude rm -f /tmp/mon_final6.sock /tmp/serial_final6.log /tmp/qemu_reset_check.log qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial file:/tmp/serial_final6.log -monitor unix:/tmp/mon_final6.sock,server,nowait -no-reboot -d cpu_reset -D /tmp/qemu_reset_check.log & QPID=$! for i in $(seq 1 25); do [ -S /tmp/mon_final6.sock ] && break; sleep 0.2; done sleep 3 python3 tools/qemu_type.py /tmp/mon_final6.sock "l" sleep 2 kill $QPID 2>/dev/null wc -l /tmp/qemu_reset_check.log grep -c "CPU Reset" /tmp/qemu_reset_check.log
qemu-system-i386: terminating on signal 15 from pid 3390479 (/bin/bash) 56 /tmp/qemu_reset_check.log 2
$ cd /home/martin/workspace/prototypes/boot-to-claude
rm -f /tmp/mon_final7.sock /tmp/serial_final7.log
qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial file:/tmp/serial_final7.log -monitor unix:/tmp/mon_final7.sock,server,nowait -no-reboot &
QPID=$!
for i in $(seq 1 25); do [ -S /tmp/mon_final7.sock ] && break; sleep 0.2; done
sleep 3
python3 -c "
import socket, time
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect('/tmp/mon_final7.sock')
time.sleep(0.3)
s.recv(4096)
for i in range(30):
s.sendall(b'sendkey l\n')
time.sleep(0.1)
s.recv(4096)
"
sleep 2
cat /tmp/serial_final7.log
kill $QPID 2>/dev/nulltiny-os: kernel entered selftest: running SELFTEST PASS tiny-os shell. type 'help' for commands. > qemu-system-i386: terminating on signal 15 from pid 3391022 (/bin/bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "0x64\|0x60\|A20\|a20" boot/stage2.S
2:; Job: enable A20, load the kernel from disk into memory below 1MB, build a 16: call enable_a20 44:enable_a20:
$ cd /home/martin/workspace/prototypes/boot-to-claude sed -n '44,60p' boot/stage2.S
enable_a20:
in al, 0x92
or al, 2
out 0x92, al
ret
; Reads the kernel in CHUNK_SECTORS-sized pieces rather than one single
; INT13h AH=0x42 call - BIOSes (including SeaBIOS) commonly cap how many
; sectors a single extended read will reliably transfer well below 512,
; and silently misbehave past that rather than returning an error.
CHUNK_SECTORS equ 64
load_kernel:
mov word [kernel_remaining], KERNEL_SECTORS
mov dword [kernel_lba], KERNEL_START_LBA
mov word [kernel_dest_seg], (KERNEL_LOAD_ADDR >> 4)$ cd /tmp/old_test
rm -f /tmp/mon_old3.sock
qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial /dev/null -monitor unix:/tmp/mon_old3.sock,server,nowait -no-reboot &
QPID=$!
for i in $(seq 1 25); do [ -S /tmp/mon_old3.sock ] && break; sleep 0.2; done
sleep 3
python3 -c "
import socket, time
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect('/tmp/mon_old3.sock')
time.sleep(0.3)
s.recv(4096)
s.sendall(b'i /b 0x64\n')
time.sleep(0.3)
print('before key:', s.recv(4096))
s.sendall(b'sendkey l\n')
time.sleep(0.3)
s.recv(4096)
s.sendall(b'i /b 0x64\n')
time.sleep(0.3)
print('after key:', s.recv(4096))
"
kill $QPID 2>/dev/nullbefore key: b'i\x1b[K\x1b[Di \x1b[K\x1b[D\x1b[Di /\x1b[K\x1b[D\x1b[D\x1b[Di /b\x1b[K\x1b[D\x1b[D\x1b[D\x1b[Di /b \x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Di /b 0\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Di /b 0x\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Di /b 0x6\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Di /b 0x64\x1b[K\r\nportb[0x0064] = 0x1c\r\n(qemu) ' after key: b'i\x1b[K\x1b[Di \x1b[K\x1b[D\x1b[Di /\x1b[K\x1b[D\x1b[D\x1b[Di /b\x1b[K\x1b[D\x1b[D\x1b[D\x1b[Di /b \x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Di /b 0\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Di /b 0x\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Di /b 0x6\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Di /b 0x64\x1b[K\r\nportb[0x0064] = 0x1c\r\n(qemu) ' qemu-system-i386: terminating on signal 15 from pid 3393946 (/bin/bash) Shell cwd was reset to /home/martin/workspace/prototypes/boot-to-claude
1 #include "keyboard.h"
2
3 #include <stddef.h>
4 #include <stdint.h>
5
6 #include "builtins.h"
7
8 #define KBD_DATA 0x60
9 #define KBD_STATUS 0x64
10
11 #define SC_LSHIFT 0x2A
12 #define SC_RSHIFT 0x36
13
14 static inline uint8_t inb(uint16_t port) { return __builtin_inb(port); }
15
16 /* Scancode set 1, indexed by make code. 0 = unmapped/non-printable. */
17 static const char lower_table[] = {
18 0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
19 '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
20 0 /*ctrl*/, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`',
21 0 /*lshift*/, '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/',
22 0 /*rshift*/, '*', 0 /*alt*/, ' ',
23 };
24
25 static const char upper_table[] = {
26 0, 27, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b',
27 '\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n',
28 0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~',
29 0, '|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?',
30 0, '*', 0, ' ',
31 };
32
33 #define TABLE_LEN (sizeof(lower_table) / sizeof(lower_table[0]))
34
35 static int shift_down = 0;
36
37 static int scancode_ready(void) {
38 return inb(KBD_STATUS) & 1;
39 }
40
41 char keyboard_getchar(void) {
42 for (;;) {
43 while (!scancode_ready()) {}
44 uint8_t sc = inb(KBD_DATA);
45
46 int release = sc & 0x80;
47 uint8_t code = sc & 0x7F;
48
49 if (code == SC_LSHIFT || code == SC_RSHIFT) {
50 shift_down = !release;
51 continue;
52 }
53 if (release || code >= TABLE_LEN) {
54 continue;
55 }
56
57 char c = shift_down ? upper_table[code] : lower_table[code];
58 if (c == 0) {
59 continue;
60 }
61 return c;
62 }
63 }
64 - #include "keyboard.h" - - #include <stddef.h> - #include <stdint.h> - - #include "builtins.h" + #include "keyboard.h" + + #include <stddef.h> + #include <stdint.h> + + #include "builtins.h" + #include "serial.h" + #include "string.h"
The file /home/martin/workspace/prototypes/boot-to-claude/kernel/keyboard.c has been updated successfully. (file state is current in your context — no need to Read it back)
- char keyboard_getchar(void) {
- for (;;) {
- while (!scancode_ready()) {}
- uint8_t sc = inb(KBD_DATA);
+ char keyboard_getchar(void) {
+ for (;;) {
+ uint32_t spins = 0;
+ while (!scancode_ready()) {
+ spins++;
+ if (spins == 3000000u) {
+ char buf[16];
+ serial_write("dbg status=");
+ serial_write(itoa((int)inb(KBD_STATUS), buf, 16));
+ serial_write("\n");
+ }
+ }
+ uint8_t sc = inb(KBD_DATA);
+ {
+ char buf[16];
+ serial_write("dbg sc=");
+ serial_write(itoa((int)sc, buf, 16));
+ serial_write("\n");
+ }The file /home/martin/workspace/prototypes/boot-to-claude/kernel/keyboard.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude make 2>&1 | tail -20
build/toolchain/cc -Ikernel -Itoolchain/include -o build/keyboard.S kernel/keyboard.c build/toolchain/as -f elf32 -o build/keyboard.o build/keyboard.S build/toolchain/ld -T kernel/link.ld --format bin -o build/kernel.bin build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o dd if=/dev/zero of=build/disk.img bs=512 count=4096 status=none dd if=build/boot.bin of=build/disk.img conv=notrunc status=none dd if=build/stage2.bin of=build/disk.img bs=512 seek=1 conv=notrunc status=none dd if=build/kernel.bin of=build/disk.img bs=512 seek=5 conv=notrunc status=none build/mkfs_tinyfs build/disk.img tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc mkfs_tinyfs: done rm build/keyboard.S
$ cd /home/martin/workspace/prototypes/boot-to-claude rm -f /tmp/mon_dbg.sock /tmp/serial_dbg.log qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial file:/tmp/serial_dbg.log -monitor unix:/tmp/mon_dbg.sock,server,nowait -no-reboot & QPID=$! for i in $(seq 1 25); do [ -S /tmp/mon_dbg.sock ] && break; sleep 0.2; done sleep 3 python3 tools/qemu_type.py /tmp/mon_dbg.sock "l" sleep 3 kill $QPID 2>/dev/null tail -30 /tmp/serial_dbg.log
qemu-system-i386: terminating on signal 15 from pid 3394897 (/bin/bash) tiny-os: kernel entered selftest: running SELFTEST PASS tiny-os shell. type 'help' for commands. > dbg status=1c dbg sc=26 dbg sc=a6 dbg status=1c
$ cd /home/martin/workspace/prototypes/boot-to-claude git diff kernel/keyboard.c | head -5 git stash push kernel/keyboard.c 2>&1 | tail -3 build/toolchain/cc -Ikernel -Itoolchain/include -o /tmp/kbd_orig.S kernel/keyboard.c git stash pop 2>&1 | tail -3 sed -n '/^keyboard_getchar:/,/^ret$/p' /tmp/kbd_orig.S | tail -60
diff --git a/kernel/keyboard.c b/kernel/keyboard.c
index 5f42996..81a882f 100644
--- a/kernel/keyboard.c
+++ b/kernel/keyboard.c
@@ -4,6 +4,8 @@
Saved working directory and index state WIP on main: 0f82a8d Retire Phase T6: fold flat-binary output into LD, drop OBJCOPY
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (5bc755c638e8f99ddbde2ebcb8b7d27d83653f7a)
db 0
db 27
db 33
db 64
db 35
db 36
db 37
db 94
db 38
db 42
db 40
db 41
db 95
db 43
db 98
db 9
db 81
db 87
db 69
db 82
db 84
db 89
db 85
db 73
db 79
db 80
db 123
db 125
db 10
db 0
db 65
db 83
db 68
db 70
db 71
db 72
db 74
db 75
db 76
db 58
db 34
db 126
db 0
db 124
db 90
db 88
db 67
db 86
db 66
db 78
db 77
db 60
db 62
db 63
db 0
db 42
db 0
db 32
shift_down:
dd 0$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "^keyboard_getchar:\|^scancode_ready:\|^inb:\|^shift_down:" /tmp/kbd_orig.S
3:inb: 19:scancode_ready: 38:keyboard_getchar: 320:shift_down:
38 keyboard_getchar: 39 push ebp 40 mov ebp, esp 41 sub esp, 16 42 .L0: 43 .L3: 44 call scancode_ready 45 cmp eax, 0 46 sete al 47 movzx eax, al 48 cmp eax, 0 49 je .L4 50 jmp .L3 51 .L4: 52 lea eax, [ebp-4] 53 push eax 54 mov eax, 96 55 push eax 56 call inb 57 add esp, 4 58 pop ecx 59 mov [ecx], al 60 lea eax, [ebp-8] 61 push eax 62 mov eax, 128 63 push eax 64 lea eax, [ebp-4] 65 movzx eax, byte [eax] 66 pop ecx 67 and eax, ecx 68 pop ecx 69 mov [ecx], eax 70 lea eax, [ebp-12] 71 push eax 72 mov eax, 127 73 push eax 74 lea eax, [ebp-4] 75 movzx eax, byte [eax] 76 pop ecx 77 and eax, ecx 78 pop ecx 79 mov [ecx], al 80 mov eax, 42 81 push eax 82 lea eax, [ebp-12] 83 movzx eax, byte [eax] 84 pop ecx 85 cmp eax, ecx 86 sete al 87 movzx eax, al 88 cmp eax, 0 89 jne .L7 90 mov eax, 54 91 push eax 92 lea eax, [ebp-12] 93 movzx eax, byte [eax] 94 pop ecx 95 cmp eax, ecx 96 sete al 97 movzx eax, al 98 cmp eax, 0 99 jne .L7 100 mov eax, 0 101 jmp .L8 102 .L7: 103 mov eax, 1 104 .L8: 105 cmp eax, 0 106 je .L5 107 lea eax, [shift_down] 108 push eax 109 lea eax, [ebp-8] 110 mov eax, [eax] 111 cmp eax, 0 112 sete al 113 movzx eax, al 114 pop ecx 115 mov [ecx], eax 116 jmp .L2 117 jmp .L6 118 .L5: 119 .L6: 120 lea eax, [ebp-8] 121 mov eax, [eax] 122 cmp eax, 0 123 jne .L11 124 mov eax, 1 125 push eax 126 mov eax, 0 127 pop ecx 128 cdq 129 idiv ecx 130 push eax 131 lea eax, [ebp-12] 132 movzx eax, byte [eax] 133 pop ecx 134 cmp eax, ecx 135 setge al 136 movzx eax, al 137 cmp eax, 0 138 jne .L11 139 mov eax, 0 140 jmp .L12 141 .L11: 142 mov eax, 1 143 .L12: 144 cmp eax, 0 145 je .L9 146 jmp .L2 147 jmp .L10 148 .L9: 149 .L10: 150 lea eax, [ebp-16] 151 push eax 152 lea eax, [shift_down] 153 mov eax, [eax] 154 cmp eax, 0 155 je .L13 156 lea eax, [ebp-12] 157 movzx eax, byte [eax] 158 push eax 159 lea eax, [upper_table] 160 pop ecx 161 add eax, ecx 162 movzx eax, byte [eax] 163 jmp .L14 164 .L13: 165 lea eax, [ebp-12] 166 movzx eax, byte [eax] 167 push eax 168 lea eax, [lower_table] 169 pop ecx 170 add eax, ecx 171 movzx eax, byte [eax] 172 .L14: 173 pop ecx 174 mov [ecx], al 175 mov eax, 0 176 push eax 177 lea eax, [ebp-16]
932 static node_t *parse_global_var(parser_t *p, const char *name, type_t *t, int is_extern, int is_static) {
933 add_global(p, name, t);
934 node_t *n = node_new(ND_VAR_DECL);
935 safe_strcpy(n->name, name, MAX_NAME);
936 n->type = t;
937 n->is_global = 1;
938 n->is_extern_decl = is_extern;
939 n->is_file_static = is_static;
940 safe_strcpy(n->asm_label, name, MAX_NAME);
941 if (match(p, TK_ASSIGN)) n->init = parse_initializer(p);
942 return n;
943 }- safe_strcpy(n->asm_label, name, MAX_NAME);
- if (match(p, TK_ASSIGN)) n->init = parse_initializer(p);
- return n;
- }
+ safe_strcpy(n->asm_label, name, MAX_NAME);
+ if (match(p, TK_ASSIGN)) n->init = parse_initializer(p);
+ /* `T name[] = { ... };` - array_len was left at -1 by parse_declarator
+ * (no size between the brackets), and type_size() treats a non-
+ * positive array_len as size 0 - silently making sizeof(name) come
+ * out as 0 instead of the real, initializer-inferred length. Fix it
+ * up now that the initializer list is available to count. */
+ if (t->kind == TY_ARRAY && t->array_len < 0 && n->init && n->init->kind == ND_BLOCK) {
+ int count = 0;
+ for (node_t *v = n->init->list; v; v = v->next) count++;
+ t->array_len = count;
+ }
+ return n;
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/parser.c has been updated successfully. (file state is current in your context — no need to Read it back)
1 #include "keyboard.h"
2
3 #include <stddef.h>
4 #include <stdint.h>
5
6 #include "builtins.h"
7 #include "serial.h"
8 #include "string.h"
9
10 #define KBD_DATA 0x60
11 #define KBD_STATUS 0x64
12
13 #define SC_LSHIFT 0x2A
14 #define SC_RSHIFT 0x36
15
16 static inline uint8_t inb(uint16_t port) { return __builtin_inb(port); }
17
18 /* Scancode set 1, indexed by make code. 0 = unmapped/non-printable. */
19 static const char lower_table[] = {
20 0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
21 '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
22 0 /*ctrl*/, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`',
23 0 /*lshift*/, '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/',
24 0 /*rshift*/, '*', 0 /*alt*/, ' ',
25 };
26
27 static const char upper_table[] = {
28 0, 27, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b',
29 '\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n',
30 0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~',
31 0, '|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?',
32 0, '*', 0, ' ',
33 };
34
35 #define TABLE_LEN (sizeof(lower_table) / sizeof(lower_table[0]))
36
37 static int shift_down = 0;
38
39 static int scancode_ready(void) {
40 return inb(KBD_STATUS) & 1;
41 }
42
43 char keyboard_getchar(void) {
44 for (;;) {
45 uint32_t spins = 0;
46 while (!scancode_ready()) {
47 spins++;
48 if (spins == 3000000u) {
49 char buf[16];
50 serial_write("dbg status=");
51 serial_write(itoa((int)inb(KBD_STATUS), buf, 16));
52 serial_write("\n");
53 }
54 }
55 uint8_t sc = inb(KBD_DATA);
56 {
57 char buf[16];
58 serial_write("dbg sc=");
59 serial_write(itoa((int)sc, buf, 16));
60 serial_write("\n");
61 }
62
63 int release = sc & 0x80;
64 uint8_t code = sc & 0x7F;
65
66 if (code == SC_LSHIFT || code == SC_RSHIFT) {
67 shift_down = !release;
68 continue;
69 }
70 if (release || code >= TABLE_LEN) {
71 continue;
72 }
73
74 char c = shift_down ? upper_table[code] : lower_table[code];
75 if (c == 0) {
76 continue;
77 }
78 return c;
79 }
80 }
81 #include "keyboard.h"
#include <stddef.h>
#include <stdint.h>
#include "builtins.h"
#define KBD_DATA 0x60
#define KBD_STATUS 0x64
#define SC_LSHIFT 0x2A
#define SC_RSHIFT 0x36
static inline uint8_t inb(uint16_t port) { return __builtin_inb(port); }
/* Scancode set 1, indexed by make code. 0 = unmapped/non-printable. */
static const char lower_table[] = {
0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
'\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
0 /*ctrl*/, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`',
0 /*lshift*/, '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/',
0 /*rshift*/, '*', 0 /*alt*/, ' ',
};
static const char upper_table[] = {
0, 27, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b',
'\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n',
0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~',
0, '|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?',
0, '*', 0, ' ',
};
#define TABLE_LEN (sizeof(lower_table) / sizeof(lower_table[0]))
static int shift_down = 0;
static int scancode_ready(void) {
return inb(KBD_STATUS) & 1;
}
char keyboard_getchar(void) {
for (;;) {
while (!scancode_ready()) {}
uint8_t sc = inb(KBD_DATA);
int release = sc & 0x80;
uint8_t code = sc & 0x7F;
if (code == SC_LSHIFT || code == SC_RSHIFT) {
shift_down = !release;
continue;
}
if (release || code >= TABLE_LEN) {
continue;
}
char c = shift_down ? upper_table[code] : lower_table[code];
if (c == 0) {
continue;
}
return c;
}
}
The file /home/martin/workspace/prototypes/boot-to-claude/kernel/keyboard.c has been updated successfully. (file state is current in your context — no need to Read it back)
/* `T arr[] = { ... }` must infer its length from the initializer -
* sizeof/array indexing bounds depend on it (kernel/keyboard.c's
* scancode table used exactly this pattern; a wrong inferred length of
* 0 made every bounds check `code >= TABLE_LEN` vacuously true). */
static const char table[] = { 10, 20, 30, 40, 50 };
#define TABLE_LEN (sizeof(table) / sizeof(table[0]))
int main(void) {
int sum = 0;
for (unsigned int i = 0; i < TABLE_LEN; i++) {
sum += table[i];
}
return sum + (int)TABLE_LEN; /* (10+20+30+40+50) + 5 = 155 */
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/tests/toolchain/t11_array_size.c (file state is current in your context — no need to Read it back)
- [t10_short]=6 - ) + [t10_short]=6 + [t11_array_size]=155 + )
The file /home/martin/workspace/prototypes/boot-to-claude/tools/run_toolchain_tests.sh has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -o build/toolchain/cc toolchain/cc/*.c -Wall 2>&1 | tail -40 ./tools/run_toolchain_tests.sh 2>&1 | tail -14
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) PASS t10_short (6) PASS t11_array_size (155) run_toolchain_tests: PASS
$ cd /home/martin/workspace/prototypes/boot-to-claude make clean make 2>&1 | tail -15
rm -rf build build/toolchain/ld -T kernel/link.ld --format bin -o build/kernel.bin build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_tinyfs.S kernel/fs/tinyfs.c build/toolchain/as -f elf32 -o build/host_tinyfs.o build/host_tinyfs.S build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_string.S kernel/string.c build/toolchain/as -f elf32 -o build/host_string.o build/host_string.S build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_mkfs_tinyfs.S tools/mkfs_tinyfs.c build/toolchain/as -f elf32 -o build/host_mkfs_tinyfs.o build/host_mkfs_tinyfs.S build/toolchain/ld -o build/mkfs_tinyfs build/host_tinyfs.o build/host_string.o build/host_mkfs_tinyfs.o dd if=/dev/zero of=build/disk.img bs=512 count=4096 status=none dd if=build/boot.bin of=build/disk.img conv=notrunc status=none dd if=build/stage2.bin of=build/disk.img bs=512 seek=1 conv=notrunc status=none dd if=build/kernel.bin of=build/disk.img bs=512 seek=5 conv=notrunc status=none build/mkfs_tinyfs build/disk.img tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc mkfs_tinyfs: done rm build/console.S build/shell/shell.S build/idt.S build/lang/script/parser.S build/lang/script/ast.S build/lang/script/eval.S build/lang/vm.S build/lang/compiler/codegen.S build/lang/compiler/parser.S build/kmain.S build/gdt.S build/string.S build/fs/tinyfs.S build/selftest.S build/serial.S build/lang/lexer.S build/fs/tinyfs_disk.S build/ata.S build/keyboard.S build/memory.S
$ cd /home/martin/workspace/prototypes/boot-to-claude ./tools/run_tests.sh 2>&1 | tail -80
run_tests: FAIL - timed out waiting for 'hello from tinyfs script'
--- serial log ---
tiny-os: kernel entered
selftest: running
SELFTEST PASS
tiny-os shell. type 'help' for commands.
> ls
hello.txt 15
hello.tsh 281
hello.tc 272
> cat hello.txt
Hello, tinyfs!
> write greet.txt hi there
write: ok
> cat greet.txt
hi there
> rm greet.txt
rm: ok
> cat greet.txt
cat: no such file
> help
commands: ls, cat <file>, write <file> <text>, rm <file>, run <file>,
build <src> <out>, exec <file>, help, clear, reboot
> run hello.tsh
script: parse error: unexpected token
> qemu-system-i386: terminating on signal 15 from pid 3397377 (bash)$ cd /home/martin/workspace/prototypes/boot-to-claude cat tests/hello.tsh
print("hello from tinyfs script");
x = 2 + 3 * 4;
print("x =", x);
func add(a, b) {
return a + b;
}
print("add(3,4) =", add(3, 4));
i = 0;
while (i < 3) {
print("loop", i);
i = i + 1;
}
fwrite("script_out.txt", "written by script");
print(fread("script_out.txt"));$ cd /home/martin/workspace/prototypes/boot-to-claude
grep -n "\[\] = {" kernel/lang/lexer.c kernel/lang/script/*.c kernel/lang/compiler/*.c kernel/lang/vm.ckernel/lang/compiler/parser.c:156: static const token_type_t ops[] = { TOK_STAR, TOK_SLASH, TOK_PERCENT };
kernel/lang/compiler/parser.c:161: static const token_type_t ops[] = { TOK_PLUS, TOK_MINUS };
kernel/lang/compiler/parser.c:166: static const token_type_t ops[] = { TOK_LT, TOK_LE, TOK_GT, TOK_GE };
kernel/lang/compiler/parser.c:171: static const token_type_t ops[] = { TOK_EQ, TOK_NEQ };
kernel/lang/compiler/parser.c:176: static const token_type_t ops[] = { TOK_AND };
kernel/lang/compiler/parser.c:181: static const token_type_t ops[] = { TOK_OR };
kernel/lang/script/parser.c:157: static const token_type_t ops[] = { TOK_STAR, TOK_SLASH, TOK_PERCENT };
kernel/lang/script/parser.c:162: static const token_type_t ops[] = { TOK_PLUS, TOK_MINUS };
kernel/lang/script/parser.c:167: static const token_type_t ops[] = { TOK_LT, TOK_LE, TOK_GT, TOK_GE };
kernel/lang/script/parser.c:172: static const token_type_t ops[] = { TOK_EQ, TOK_NEQ };
kernel/lang/script/parser.c:177: static const token_type_t ops[] = { TOK_AND };
kernel/lang/script/parser.c:182: static const token_type_t ops[] = { TOK_OR };$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "static node_t \*parse_one_local_decl" toolchain/cc/parser.c
795:static node_t *parse_one_local_decl(parser_t *p, type_t *base, int is_static) {795 static node_t *parse_one_local_decl(parser_t *p, type_t *base, int is_static) {
796 char name[MAX_NAME];
797 type_t *t = parse_declarator(p, base, name, NULL, NULL);
798 sym_t *s = add_local(p, name, t, is_static);
799 node_t *n = node_new(ND_VAR_DECL);
800 safe_strcpy(n->name, name, MAX_NAME);
801 n->type = t;
802 n->is_static_local = is_static;
803 safe_strcpy(n->asm_label, s->asm_label, MAX_NAME);
804 n->frame_offset = s->frame_offset;
805 if (match(p, TK_ASSIGN)) n->init = parse_initializer(p);
806 return n;
807 }
808
809 static node_t *parse_local_decl_stmt(parser_t *p) {
810 int is_static = 0, is_extern = 0;
811 type_t *base = parse_type_spec(p, &is_static, &is_extern);
812 node_t *head = parse_one_local_decl(p, base, is_static);- static node_t *parse_one_local_decl(parser_t *p, type_t *base, int is_static) {
- char name[MAX_NAME];
- type_t *t = parse_declarator(p, base, name, NULL, NULL);
- sym_t *s = add_local(p, name, t, is_static);
- node_t *n = node_new(ND_VAR_DECL);
- safe_strcpy(n->name, name, MAX_NAME);
- n->type = t;
- n->is_static_local = is_static;
- safe_strcpy(n->asm_label, s->asm_label, MAX_NAME);
- n->frame_offset = s->frame_offset;
- if (match(p, TK_ASSIGN)) n->init = parse_initializer(p);
- return n;
- }
+ static node_t *parse_one_local_decl(parser_t *p, type_t *base, int is_static) {
+ char name[MAX_NAME];
+ type_t *t = parse_declarator(p, base, name, NULL, NULL);
+ sym_t *s = add_local(p, name, t, is_static);
+ node_t *n = node_new(ND_VAR_DECL);
+ safe_strcpy(n->name, name, MAX_NAME);
+ n->type = t;
+ n->is_static_local = is_static;
+ safe_strcpy(n->asm_label, s->asm_label, MAX_NAME);
+ n->frame_offset = s->frame_offset;
+ if (match(p, TK_ASSIGN)) n->init = parse_initializer(p);
+ /* Same `T name[] = { ... }` size-inference fix as parse_global_var -
+ * needed here too for e.g. a function-local `static const T ops[] =
+ * {...};` (used throughout kernel/lang/*/parser.c) - see that
+ * function's comment for why array_len otherwise silently means
+ * sizeof(name) == 0. */
+ if (t->kind == TY_ARRAY && t->array_len < 0 && n->init && n->init->kind == ND_BLOCK) {
+ int count = 0;
+ for (node_t *v = n->init->list; v; v = v->next) count++;
+ t->array_len = count;
+ }
+ return n;
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/parser.c has been updated successfully. (file state is current in your context — no need to Read it back)
/* Same as t11, but for a function-local `static const T ops[] = {...}`
* (the exact pattern kernel/lang/*/parser.c uses for its operator
* tables). */
static int count_matches(int x) {
static const int table[] = { 3, 7, 11, 15 };
int n = (int)(sizeof(table) / sizeof(table[0]));
for (int i = 0; i < n; i++) {
if (table[i] == x) return 1;
}
return 0;
}
int main(void) {
int hits = 0;
for (int x = 0; x < 20; x++) {
if (count_matches(x)) hits++;
}
return hits; /* 3,7,11,15 -> 4 */
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/tests/toolchain/t12_local_array_size.c (file state is current in your context — no need to Read it back)
- [t11_array_size]=155 - ) + [t11_array_size]=155 + [t12_local_array_size]=4 + )
The file /home/martin/workspace/prototypes/boot-to-claude/tools/run_toolchain_tests.sh has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -o build/toolchain/cc toolchain/cc/*.c -Wall 2>&1 | tail -40 ./tools/run_toolchain_tests.sh 2>&1 | tail -15
toolchain/cc/parser.c: In function ‘parse_one_local_decl’:
toolchain/cc/parser.c:808:47: error: ‘parser’ undeclared (first use in this function); did you mean ‘parser_t’?
808 | * {...};` (used throughout kernel/lang/*/parser.c) - see that
| ^~~~~~
| parser_t
toolchain/cc/parser.c:808:47: note: each undeclared identifier is reported only once for each function it appears in
toolchain/cc/parser.c:808:55: error: expected ‘;’ before ‘)’ token
808 | * {...};` (used throughout kernel/lang/*/parser.c) - see that
| ^
| ;
toolchain/cc/parser.c:808:55: error: expected statement before ‘)’ token
toolchain/cc/parser.c:808:59: error: ‘see’ undeclared (first use in this function)
808 | * {...};` (used throughout kernel/lang/*/parser.c) - see that
| ^~~
toolchain/cc/parser.c:808:62: error: expected ‘;’ before ‘that’
808 | * {...};` (used throughout kernel/lang/*/parser.c) - see that
| ^~~~~
| ;
toolchain/cc/parser.c:809:16: warning: missing terminating ' character
809 | * function's comment for why array_len otherwise silently means
| ^
toolchain/cc/parser.c:809:16: error: missing terminating ' character
809 | * function's comment for why array_len otherwise silently means
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
toolchain/cc/parser.c:817:1: warning: no return statement in function returning non-void [-Wreturn-type]
817 | }
| ^
PASS t01_basic (5)
PASS t02_control (7)
PASS t02_types (42)
PASS t03_recursion (55)
PASS t04_pointers (31)
PASS t05_structs (17)
PASS t06_switch (139)
PASS t07_comma (53)
PASS t08_forward_struct (6)
PASS t09_local_init (81)
PASS t10_short (6)
PASS t11_array_size (155)
cc: tests/toolchain/t12_local_array_size.c:2: unexpected token (near '?')- /* Same `T name[] = { ... }` size-inference fix as parse_global_var -
- * needed here too for e.g. a function-local `static const T ops[] =
- * {...};` (used throughout kernel/lang/*/parser.c) - see that
- * function's comment for why array_len otherwise silently means
- * sizeof(name) == 0. */
+ /* Same `T name[] = { ... }` size-inference fix as parse_global_var -
+ * needed here too for e.g. a function-local `static const T ops[] =
+ * {...};` (used throughout the kernel's scripting-language parser
+ * files) - see that function's comment for why array_len otherwise
+ * silently means sizeof(name) == 0. */The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/parser.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -o build/toolchain/cc toolchain/cc/*.c -Wall 2>&1 | tail -40 ./tools/run_toolchain_tests.sh 2>&1 | tail -15
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) PASS t10_short (6) PASS t11_array_size (155) cc: tests/toolchain/t12_local_array_size.c:2: unexpected token (near '?')
$ cd /home/martin/workspace/prototypes/boot-to-claude build/toolchain/cc -Itoolchain/include --target=host --parse-only tests/toolchain/t12_local_array_size.c
Exit code 1 cc: tests/toolchain/t12_local_array_size.c:2: unexpected token (near '?')
1 /* Same as t11, but for a function-local `static const T ops[] = {...}`
2 * (the exact pattern kernel/lang/*/parser.c uses for its operator
3 * tables). */- /* Same as t11, but for a function-local `static const T ops[] = {...}`
- * (the exact pattern kernel/lang/*/parser.c uses for its operator
- * tables). */
+ /* Same as t11, but for a function-local `static const T ops[] = {...}`
+ * (the exact pattern the kernel's scripting-language parser files use
+ * for their operator tables). */The file /home/martin/workspace/prototypes/boot-to-claude/tests/toolchain/t12_local_array_size.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude ./tools/run_toolchain_tests.sh 2>&1 | tail -15
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) PASS t10_short (6) PASS t11_array_size (155) PASS t12_local_array_size (4) run_toolchain_tests: PASS
$ cd /home/martin/workspace/prototypes/boot-to-claude make clean make 2>&1 | tail -15 ./tools/run_tests.sh 2>&1 | tail -80
rm -rf build
build/toolchain/ld -T kernel/link.ld --format bin -o build/kernel.bin build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o
build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_tinyfs.S kernel/fs/tinyfs.c
build/toolchain/as -f elf32 -o build/host_tinyfs.o build/host_tinyfs.S
build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_string.S kernel/string.c
build/toolchain/as -f elf32 -o build/host_string.o build/host_string.S
build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_mkfs_tinyfs.S tools/mkfs_tinyfs.c
build/toolchain/as -f elf32 -o build/host_mkfs_tinyfs.o build/host_mkfs_tinyfs.S
build/toolchain/ld -o build/mkfs_tinyfs build/host_tinyfs.o build/host_string.o build/host_mkfs_tinyfs.o
dd if=/dev/zero of=build/disk.img bs=512 count=4096 status=none
dd if=build/boot.bin of=build/disk.img conv=notrunc status=none
dd if=build/stage2.bin of=build/disk.img bs=512 seek=1 conv=notrunc status=none
dd if=build/kernel.bin of=build/disk.img bs=512 seek=5 conv=notrunc status=none
build/mkfs_tinyfs build/disk.img tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc
mkfs_tinyfs: done
rm build/console.S build/shell/shell.S build/idt.S build/lang/script/parser.S build/lang/script/ast.S build/lang/script/eval.S build/lang/vm.S build/lang/compiler/codegen.S build/lang/compiler/parser.S build/kmain.S build/gdt.S build/string.S build/fs/tinyfs.S build/selftest.S build/serial.S build/lang/lexer.S build/fs/tinyfs_disk.S build/ata.S build/keyboard.S build/memory.S
run_tests: FAIL - timed out waiting for 'hello from tinyfs script'
--- serial log ---
tiny-os: kernel entered
selftest: running
SELFTEST PASS
tiny-os shell. type 'help' for commands.
> ls
hello.txt 15
hello.tsh 281
hello.tc 272
> cat hello.txt
Hello, tinyfs!
> write greet.txt hi there
write: ok
> cat greet.txt
hi there
> rm greet.txt
rm: ok
> cat greet.txt
cat: no such file
> help
commands: ls, cat <file>, write <file> <text>, rm <file>, run <file>,
build <src> <out>, exec <file>, help, clear, reboot
> run hello.tsh
script: parse error: unexpected token
> qemu-system-i386: terminating on signal 15 from pid 3401518 (bash)$ cd /home/martin/workspace/prototypes/boot-to-claude
grep -n "\[\] = {\|\[\]=" kernel/lang/lexer.c kernel/lang/script/ast.c kernel/lang/vm.c(Bash completed with no output)
$ cd /home/martin/workspace/prototypes/boot-to-claude rm -f /tmp/mon_script.sock /tmp/serial_script.log qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial file:/tmp/serial_script.log -monitor unix:/tmp/mon_script.sock,server,nowait -no-reboot & QPID=$! for i in $(seq 1 25); do [ -S /tmp/mon_script.sock ] && break; sleep 0.2; done sleep 3 python3 tools/qemu_type.py /tmp/mon_script.sock "write t.tsh print(1); " sleep 1 python3 tools/qemu_type.py /tmp/mon_script.sock "run t.tsh " sleep 2 kill $QPID 2>/dev/null tail -20 /tmp/serial_script.log
Traceback (most recent call last):
File "/home/martin/workspace/prototypes/boot-to-claude/tools/qemu_type.py", line 54, in <module>
main()
~~~~^^
File "/home/martin/workspace/prototypes/boot-to-claude/tools/qemu_type.py", line 46, in main
s.sendall(f"sendkey {key_name(c)}\n".encode())
~~~~~~~~^^^
File "/home/martin/workspace/prototypes/boot-to-claude/tools/qemu_type.py", line 30, in key_name
raise ValueError(f"qemu_type: no key mapping for {c!r}")
ValueError: qemu_type: no key mapping for '('
qemu-system-i386: terminating on signal 15 from pid 3402039 (/bin/bash)
tiny-os: kernel entered
selftest: running
SELFTEST PASS
tiny-os shell. type 'help' for commands.
> write t.tsh printrun t.tsh
write: ok
>1 #include "lexer.h"
2
3 #include "../memory.h"
4 #include "../string.h"
5
6 static int is_alpha(char c) {
7 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
8 }
9
10 static int is_digit(char c) {
11 return c >= '0' && c <= '9';
12 }
13
14 static int is_alnum(char c) {
15 return is_alpha(c) || is_digit(c);
16 }
17
18 static void skip_ws_and_comments(lexer_t *lx) {
19 for (;;) {
20 char c = lx->src[lx->pos];
21 if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
22 lx->pos++;
23 } else if (c == '#') {
24 while (lx->src[lx->pos] && lx->src[lx->pos] != '\n') {
25 lx->pos++;
26 }
27 } else {
28 break;
29 }
30 }
31 }
32
33 void lexer_init(lexer_t *lx, const char *src) {
34 lx->src = src;
35 lx->pos = 0;
36 }
37
38 token_t lexer_next(lexer_t *lx) {
39 token_t tok;
40 tok.string = NULL;
41 tok.number = 0;
42 tok.text[0] = '\0';
43
44 skip_ws_and_comments(lx);
45 char c = lx->src[lx->pos];
46
47 if (c == '\0') {
48 tok.type = TOK_EOF;
49 return tok;
50 }
51
52 if (is_alpha(c)) {
53 int start = lx->pos;
54 while (is_alnum(lx->src[lx->pos])) {
55 lx->pos++;
56 }
57 int len = lx->pos - start;
58 if (len >= TOKEN_TEXT_MAX) {
59 len = TOKEN_TEXT_MAX - 1;
60 }
61 memcpy(tok.text, lx->src + start, (size_t)len);
62 tok.text[len] = '\0';
63 tok.type = TOK_IDENT;
64 return tok;
65 }
66
67 if (is_digit(c)) {
68 int32_t val = 0;
69 while (is_digit(lx->src[lx->pos])) {
70 val = val * 10 + (lx->src[lx->pos] - '0');
71 lx->pos++;
72 }
73 tok.number = val;
74 tok.type = TOK_NUMBER;
75 return tok;
76 }
77
78 if (c == '"') {
79 lx->pos++;
80 int start = lx->pos;
81 while (lx->src[lx->pos] && lx->src[lx->pos] != '"') {
82 lx->pos++;
83 }
84 int len = lx->pos - start;
85 char *s = kmalloc((size_t)len + 1);
86 memcpy(s, lx->src + start, (size_t)len);
87 s[len] = '\0';
88 if (lx->src[lx->pos] == '"') {
89 lx->pos++;
90 }
91 tok.string = s;
92 tok.type = TOK_STRING;
93 return tok;
94 }
95
96 lx->pos++;
97 switch (c) {
98 case '(': tok.type = TOK_LPAREN; break;
99 case ')': tok.type = TOK_RPAREN; break;
100 case '{': tok.type = TOK_LBRACE; break;
101 case '}': tok.type = TOK_RBRACE; break;
102 case ',': tok.type = TOK_COMMA; break;
103 case ';': tok.type = TOK_SEMI; break;
104 case '+': tok.type = TOK_PLUS; break;
105 case '-': tok.type = TOK_MINUS; break;
106 case '*': tok.type = TOK_STAR; break;
107 case '/': tok.type = TOK_SLASH; break;
108 case '%': tok.type = TOK_PERCENT; break;
109 case '=':
110 if (lx->src[lx->pos] == '=') { lx->pos++; tok.type = TOK_EQ; }
111 else { tok.type = TOK_ASSIGN; }
112 break;
113 case '!':
114 if (lx->src[lx->pos] == '=') { lx->pos++; tok.type = TOK_NEQ; }
115 else { tok.type = TOK_NOT; }
116 break;
117 case '<':
118 if (lx->src[lx->pos] == '=') { lx->pos++; tok.type = TOK_LE; }
119 else { tok.type = TOK_LT; }
120 break;
121 case '>':
122 if (lx->src[lx->pos] == '=') { lx->pos++; tok.type = TOK_GE; }
123 else { tok.type = TOK_GT; }
124 break;
125 case '&':
126 if (lx->src[lx->pos] == '&') { lx->pos++; tok.type = TOK_AND; }
127 else { tok.type = TOK_ERROR; }
128 break;
129 case '|':
130 if (lx->src[lx->pos] == '|') { lx->pos++; tok.type = TOK_OR; }
131 else { tok.type = TOK_ERROR; }
132 break;
133 default:
134 tok.type = TOK_ERROR;
135 break;
136 }
137 return tok;
138 }
139 $ cd /home/martin/workspace/prototypes/boot-to-claude cat tests/hello.tc rm -f /tmp/mon_script2.sock /tmp/serial_script2.log qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial file:/tmp/serial_script2.log -monitor unix:/tmp/mon_script2.sock,server,nowait -no-reboot & QPID=$! for i in $(seq 1 25); do [ -S /tmp/mon_script2.sock ] && break; sleep 0.2; done sleep 3 python3 tools/qemu_type.py /tmp/mon_script2.sock "build hello.tc out.tbc " sleep 1 kill $QPID 2>/dev/null tail -10 /tmp/serial_script2.log
x = 2 + 3 * 4;
print(x);
func add(a, b) {
return a + b;
}
print(add(3, 4));
func fib(n) {
if (n < 2) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
print(fib(10));
i = 0;
sum = 0;
while (i < 5) {
sum = sum + i;
i = i + 1;
}
print(sum);
qemu-system-i386: terminating on signal 15 from pid 3404462 (/bin/bash)
tiny-os: kernel entered
selftest: running
SELFTEST PASS
tiny-os shell. type 'help' for commands.
> uild hello.tc out.tb bc
unknown command (type 'help')
>$ cd /home/martin/workspace/prototypes/boot-to-claude rm -f /tmp/mon_script3.sock /tmp/serial_script3.log qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial file:/tmp/serial_script3.log -monitor unix:/tmp/mon_script3.sock,server,nowait -no-reboot & QPID=$! for i in $(seq 1 25); do [ -S /tmp/mon_script3.sock ] && break; sleep 0.2; done sleep 3 python3 tools/qemu_type.py /tmp/mon_script3.sock "build hello.tc out.tbc " sleep 1 kill $QPID 2>/dev/null tail -10 /tmp/serial_script3.log
qemu-system-i386: terminating on signal 15 from pid 3404665 (/bin/bash) tiny-os: kernel entered selftest: running SELFTEST PASS tiny-os shell. type 'help' for commands. > uild hello.tc out.tb bc unknown command (type 'help') >
$ cd /home/martin/workspace/prototypes/boot-to-claude sed -n '1,50p' tools/run_tests.sh
#!/usr/bin/env bash
# Boots the kernel headlessly in QEMU, checks the kernel self-test result,
# then drives the interactive shell via QEMU's monitor `sendkey` (see
# tools/qemu_type.py - there's no other way to feed the polled PS/2
# keyboard driver without real hardware) and asserts on serial output.
set -euo pipefail
cd "$(dirname "$0")/.."
make >/dev/null
BUILD=build
LOG="$BUILD/serial.log"
MON="$BUILD/qemu-monitor.sock"
rm -f "$LOG" "$MON"
qemu-system-i386 \
-drive file="$BUILD/disk.img",format=raw \
-display none \
-serial file:"$LOG" \
-monitor unix:"$MON",server,nowait \
-no-reboot &
QEMU_PID=$!
cleanup() {
kill "$QEMU_PID" 2>/dev/null || true
wait "$QEMU_PID" 2>/dev/null || true
}
trap cleanup EXIT
fail() {
echo "run_tests: FAIL - $1"
echo "--- serial log ---"
cat "$LOG" 2>/dev/null || true
exit 1
}
wait_for() {
local pattern="$1"
local max_tries="${2:-50}" # 50 * 0.2s = 10s
local i=0
while ! grep -qF "$pattern" "$LOG" 2>/dev/null; do
i=$((i + 1))
if [ "$i" -ge "$max_tries" ]; then
fail "timed out waiting for '$pattern'"
fi
sleep 0.2
done
}
type_line() {#!/usr/bin/env bash
set -euo pipefail
cd /home/martin/workspace/prototypes/boot-to-claude
BUILD=build
LOG=/tmp/serial_build_test.log
MON=/tmp/mon_build_test.sock
rm -f "$LOG" "$MON"
qemu-system-i386 \
-drive file="$BUILD/disk.img",format=raw \
-display none \
-serial file:"$LOG" \
-monitor unix:"$MON",server,nowait \
-no-reboot &
QEMU_PID=$!
cleanup() { kill "$QEMU_PID" 2>/dev/null || true; wait "$QEMU_PID" 2>/dev/null || true; }
trap cleanup EXIT
wait_for() {
local pattern="$1"
local max_tries="${2:-50}"
local i=0
while ! grep -qF "$pattern" "$LOG" 2>/dev/null; do
i=$((i + 1))
if [ "$i" -ge "$max_tries" ]; then
echo "TIMEOUT waiting for '$pattern'"
cat "$LOG"
exit 1
fi
sleep 0.2
done
}
type_line() {
python3 tools/qemu_type.py "$MON" "$1
"
}
for i in $(seq 1 25); do
[ -S "$MON" ] && break
sleep 0.2
done
wait_for "type 'help'"
type_line "build hello.tc out.tbc"
sleep 1
cat "$LOG"
File created successfully at: /tmp/test_build.sh (file state is current in your context — no need to Read it back)
$ chmod +x /tmp/test_build.sh /tmp/test_build.sh
tiny-os: kernel entered selftest: running SELFTEST PASS tiny-os shell. type 'help' for commands. > uild hello.tc out.tb bc unknown command (type 'help') > qemu-system-i386: terminating on signal 15 from pid 3405337 (bash)
1 #include "parser.h"
2
3 #include "../../string.h"
4
5 static void copy_name(char *dst, const char *src) {
6 int i = 0;
7 while (src[i] && i < TOKEN_TEXT_MAX - 1) {
8 dst[i] = src[i];
9 i++;
10 }
11 dst[i] = '\0';
12 }
13
14 static void advance(parser_t *p) {
15 p->cur = lexer_next(&p->lexer);
16 }
17
18 void parser_init(parser_t *p, const char *src) {
19 lexer_init(&p->lexer, src);
20 p->error = 0;
21 p->error_msg[0] = '\0';
22 advance(p);
23 }
24
25 static void parse_error(parser_t *p, const char *msg) {
26 if (!p->error) {
27 p->error = 1;
28 copy_name(p->error_msg, msg); /* error_msg is 64 bytes, plenty for our short messages */
29 }
30 }
31
32 static int check(parser_t *p, token_type_t t) {
33 return p->cur.type == t;
34 }
35
36 static int match(parser_t *p, token_type_t t) {
37 if (check(p, t)) {
38 advance(p);
39 return 1;
40 }
41 return 0;
42 }
43
44 static void expect(parser_t *p, token_type_t t) {
45 if (!match(p, t)) {
46 parse_error(p, "unexpected token");
47 }
48 }
49
50 static int is_kw(parser_t *p, const char *kw) {
51 return check(p, TOK_IDENT) && strcmp(p->cur.text, kw) == 0;
52 }
53
54 static ast_node_t *parse_expression(parser_t *p);
55 static ast_node_t *parse_statement(parser_t *p);
56
57 static ast_node_t *parse_block(parser_t *p) {
58 expect(p, TOK_LBRACE);
59 ast_node_t *blk = ast_new(AST_BLOCK);
60 ast_node_t *tail = NULL;$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "parse_primary\|parse_call\|TOK_STRING\|TOK_COMMA" kernel/lang/script/parser.c | head -30
70:static ast_node_t *parse_primary(parser_t *p) {
77: if (check(p, TOK_STRING)) {
97: if (!match(p, TOK_COMMA)) {
128: return parse_primary(p);
269: if (!match(p, TOK_COMMA)) {70 static ast_node_t *parse_primary(parser_t *p) {
71 if (check(p, TOK_NUMBER)) {
72 ast_node_t *n = ast_new(AST_NUM);
73 n->num = p->cur.number;
74 advance(p);
75 return n;
76 }
77 if (check(p, TOK_STRING)) {
78 ast_node_t *n = ast_new(AST_STR);
79 n->str = p->cur.string;
80 advance(p);
81 return n;
82 }
83 if (check(p, TOK_IDENT)) {
84 char name[TOKEN_TEXT_MAX];
85 copy_name(name, p->cur.text);
86 advance(p);
87 if (check(p, TOK_LPAREN)) {
88 advance(p);
89 ast_node_t *n = ast_new(AST_CALL);
90 copy_name(n->name, name);
91 ast_node_t *tail = NULL;
92 if (!check(p, TOK_RPAREN)) {
93 for (;;) {
94 ast_node_t *arg = parse_expression(p);
95 if (!n->list) { n->list = arg; } else { tail->next = arg; }
96 tail = arg;
97 if (!match(p, TOK_COMMA)) {
98 break;
99 }
100 }
101 }
102 expect(p, TOK_RPAREN);
103 return n;
104 }
105 ast_node_t *n = ast_new(AST_VAR);
106 copy_name(n->name, name);
107 return n;
108 }
109 if (match(p, TOK_LPAREN)) {
110 ast_node_t *n = parse_expression(p);
111 expect(p, TOK_RPAREN);
112 return n;
113 }
114 parse_error(p, "expected expression");
115 advance(p); /* avoid getting stuck */
116 return ast_new(AST_NUM);
117 }
118
119 static ast_node_t *parse_unary(parser_t *p) {
120 if (check(p, TOK_MINUS) || check(p, TOK_NOT)) {
121 token_type_t op = p->cur.type;
122 advance(p);
123 ast_node_t *n = ast_new(AST_UNOP);
124 n->op = op;
125 n->a = parse_unary(p);
126 return n;
127 }
128 return parse_primary(p);
129 }190 copy_name(name, p->cur.text);
191 advance(p);
192 if (check(p, TOK_ASSIGN)) {
193 advance(p);
194 ast_node_t *value = parse_assignment(p); /* right-associative */
195 ast_node_t *n = ast_new(AST_ASSIGN);
196 copy_name(n->name, name);
197 n->a = value;
198 return n;
199 }
200 *p = save; /* not an assignment - rewind and parse as a normal expression */
201 }
202 return parse_or(p);
203 }
204
205 static ast_node_t *parse_expression(parser_t *p) {
206 return parse_assignment(p);
207 }
208
209 static ast_node_t *parse_statement(parser_t *p) {
210 if (check(p, TOK_LBRACE)) {
211 return parse_block(p);
212 }
213
214 if (is_kw(p, "if")) {
215 advance(p);
216 expect(p, TOK_LPAREN);
217 ast_node_t *cond = parse_expression(p);
218 expect(p, TOK_RPAREN);
219 ast_node_t *then_blk = parse_block(p);
220 ast_node_t *else_blk = NULL;
221 if (is_kw(p, "else")) {
222 advance(p);
223 else_blk = is_kw(p, "if") ? parse_statement(p) : parse_block(p);
224 }
225 ast_node_t *n = ast_new(AST_IF);
226 n->a = cond;
227 n->b = then_blk;
228 n->c = else_blk;
229 return n;
230 }
231
232 if (is_kw(p, "while")) {
233 advance(p);
234 expect(p, TOK_LPAREN);
235 ast_node_t *cond = parse_expression(p);
236 expect(p, TOK_RPAREN);
237 ast_node_t *body = parse_block(p);
238 ast_node_t *n = ast_new(AST_WHILE);
239 n->a = cond;
240 n->b = body;
241 return n;
242 }
243
244 if (is_kw(p, "func")) {
245 advance(p);
246 char name[TOKEN_TEXT_MAX];
247 if (check(p, TOK_IDENT)) {
248 copy_name(name, p->cur.text);
249 advance(p);
250 } else {
251 parse_error(p, "expected function name");
252 name[0] = '\0';
253 }
254 expect(p, TOK_LPAREN);
255 ast_node_t *params = NULL;
256 ast_node_t *ptail = NULL;
257 if (!check(p, TOK_RPAREN)) {
258 for (;;) {
259 if (check(p, TOK_IDENT)) {
260 ast_node_t *pn = ast_new(AST_VAR);
261 copy_name(pn->name, p->cur.text);
262 advance(p);
263 if (!params) { params = pn; } else { ptail->next = pn; }
264 ptail = pn;
265 } else {
266 parse_error(p, "expected parameter name");
267 break;
268 }
269 if (!match(p, TOK_COMMA)) {
270 break;
271 }
272 }
273 }
274 expect(p, TOK_RPAREN);
275 ast_node_t *body = parse_block(p);
276 ast_node_t *n = ast_new(AST_FUNC_DEF);
277 copy_name(n->name, name);
278 n->list = params;
279 n->b = body;
280 return n;
281 }
282
283 if (is_kw(p, "return")) {
284 advance(p);
285 ast_node_t *n = ast_new(AST_RETURN);
286 if (!check(p, TOK_SEMI)) {
287 n->a = parse_expression(p);
288 }
289 expect(p, TOK_SEMI);183 return parse_binop_level(p, parse_and, ops, 1);
184 }
185
186 static ast_node_t *parse_assignment(parser_t *p) {
187 if (check(p, TOK_IDENT)) {
188 parser_t save = *p; /* plain-old-data struct, safe to snapshot/restore */
189 char name[TOKEN_TEXT_MAX];
190 copy_name(name, p->cur.text);
191 advance(p);
192 if (check(p, TOK_ASSIGN)) {
193 advance(p);
194 ast_node_t *value = parse_assignment(p); /* right-associative */
195 ast_node_t *n = ast_new(AST_ASSIGN);
196 copy_name(n->name, name);
197 n->a = value;
198 return n;
199 }$ cd /home/martin/workspace/prototypes/boot-to-claude cat kernel/lang/script/parser.h
#pragma once
#include "../lexer.h"
#include "ast.h"
typedef struct {
lexer_t lexer;
token_t cur;
int error;
char error_msg[64];
} parser_t;
void parser_init(parser_t *p, const char *src);
/* Parses the whole source as a top-level AST_BLOCK of statements. Check
* p->error afterwards before evaluating the result. */
ast_node_t *parse_program(parser_t *p);$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "ND_DEREF" toolchain/cc/codegen.c
103: case ND_DEREF: 258: case ND_DEREF:
255 case ND_ADDR:
256 codegen_addr(n->a);
257 return;
258 case ND_DEREF:
259 codegen_expr(n->a);
260 load_value(n->type);
261 return;
262 case ND_PREINC: case ND_PREDEC: case ND_POSTINC: case ND_POSTDEC: {
263 /* int/pointer only (4-byte) - ++/-- on a char never appears
264 * in this codebase, see docs/toolchain.md. */
265 int is_dec = (n->kind == ND_PREDEC || n->kind == ND_POSTDEC);
266 int is_post = (n->kind == ND_POSTINC || n->kind == ND_POSTDEC);
267 int step = (n->a->type->kind == TY_PTR) ? type_size(n->a->type->base) : 1;
268 codegen_addr(n->a);
269 out(" mov ecx, eax\n"); /* ecx = address */$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "case ND_ASSIGN" toolchain/cc/codegen.c
179: case ND_ASSIGN: {179 case ND_ASSIGN: {
180 codegen_addr(n->a);
181 out(" push eax\n");
182 codegen_expr(n->b);
183 out(" pop ecx\n");
184 store_value(n->a->type);
185 return;
186 }
187 case ND_COMPOUND_ASSIGN: {
188 /* eax/ecx get clobbered (idiv even clobbers edx) while
189 * computing the new value, so &a is kept on the real stack
190 * throughout and only popped fresh at the very end - a
191 * register copy of it wouldn't survive an idiv/cdq. */
192 codegen_addr(n->a);
193 out(" push eax\n"); /* [&a] */$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n '"rep"\|"movsb"\|"stosb"\|"cld"\|"push esi"\|R_ESI\|R_EDI' toolchain/as/encode.c | head -20
15: case R_DH: case R_SI: case R_ESI: return 6;
16: case R_BH: case R_DI: case R_EDI: return 7;
28:static int is_reg32(reg_t r) { return r >= R_EAX && r <= R_EDI; }
726: if (strcmp(m, "cld") == 0) { e8(&e, 0xFC); return e.len; }
727: if (strcmp(m, "rep") == 0 && strcmp(a->sym, "stosb") == 0) { e8(&e, 0xF3); e8(&e, 0xAA); return e.len; }44 /* ---------------- loads/stores sized per C type ---------------- */
45
46 static int is_byte_type(type_t *t) { return t->kind == TY_CHAR; }
47 static int is_short_type(type_t *t) { return t->kind == TY_SHORT; }
48
49 /* Address already in EAX; loads the value (sized per `t`) into EAX. */
50 static void load_value(type_t *t) {
51 if (t->kind == TY_ARRAY) return; /* arrays decay to their own address - nothing to load */
52 if (is_byte_type(t)) {
53 out(" movzx eax, byte [eax]\n");
54 } else if (is_short_type(t)) {
55 /* mov ax, [eax] reads through the same register that holds the
56 * address (ax is eax's low 16 bits) - fine, the CPU reads the
57 * memory operand before writing the result back. movzx then
58 * clears the high 16 bits, which the mov left as whatever
59 * garbage eax's upper half held before. */
60 out(" mov ax, [eax]\n movzx eax, ax\n");
61 } else {
62 out(" mov eax, [eax]\n");
63 }
64 }
65
66 /* Stores EAX's value into the address in ECX. */
67 static void store_value(type_t *t) {
68 if (is_byte_type(t)) {
69 out(" mov [ecx], al\n");
70 } else if (is_short_type(t)) {
71 out(" mov [ecx], ax\n");
72 } else {
73 out(" mov [ecx], eax\n");- /* Address already in EAX; loads the value (sized per `t`) into EAX. */
- static void load_value(type_t *t) {
- if (t->kind == TY_ARRAY) return; /* arrays decay to their own address - nothing to load */
- if (is_byte_type(t)) {
- out(" movzx eax, byte [eax]\n");
- } else if (is_short_type(t)) {
- /* mov ax, [eax] reads through the same register that holds the
- * address (ax is eax's low 16 bits) - fine, the CPU reads the
- * memory operand before writing the result back. movzx then
- * clears the high 16 bits, which the mov left as whatever
- * garbage eax's upper half held before. */
- out(" mov ax, [eax]\n movzx eax, ax\n");
- } else {
- out(" mov eax, [eax]\n");
- }
- }
-
- /* Stores EAX's value into the address in ECX. */
- static void store_value(type_t *t) {
- if (is_byte_type(t)) {
- out(" mov [ecx], al\n");
- } else if (is_short_type(t)) {
- out(" mov [ecx], ax\n");
- } else {
- out(" mov [ecx], eax\n");
+ /* Address already in EAX; loads the value (sized per `t`) into EAX. */
+ static void load_value(type_t *t) {
+ if (t->kind == TY_ARRAY) return; /* arrays decay to their own address - nothing to load */
+ if (t->kind == TY_STRUCT || t->kind == TY_UNION) return; /* same - see store_value's struct-copy branch */
+ if (is_byte_type(t)) {
+ out(" movzx eax, byte [eax]\n");
+ } else if (is_short_type(t)) {
+ /* mov ax, [eax] reads through the same register that holds the
+ * address (ax is eax's low 16 bits) - fine, the CPU reads the
+ * memory operand before writing the result back. movzx then
+ * clears the high 16 bits, which the mov left as whatever
+ * garbage eax's upper half held before. */
+ out(" mov ax, [eax]\n movzx eax, ax\n");
+ } else {
+ out(" mov eax, [eax]\n");
+ }
+ }
+
+ /* Byte-by-byte copy, EAX=src address, ECX=dst address (the same
+ * convention store_value's other branches use for "value" and
+ * "destination"). Needed because a struct/union doesn't fit in a
+ * register - whole-struct assignment (`a = b;`, or a non-brace
+ * initializer like `parser_t save = *p;`) needs an actual memcpy, not a
+ * single mov. AS has no rep movsb, so this is a plain compare-and-loop
+ * like every other loop this codegen emits. */
+ static void codegen_struct_copy(type_t *t) {
+ char ltop[MAX_NAME], lend[MAX_NAME];
+ new_label(ltop); new_label(lend);
+ out(" push eax\n"); /* [src] */
+ out(" push ecx\n"); /* [dst, src] */
+ out(" push 0\n"); /* [i, dst, src] */
+ out("%s:\n", ltop);
+ out(" mov eax, %d\n", type_size(t));
+ out(" cmp DWORD [esp], eax\n");
+ out(" jge %s\n", lend);
+ out(" mov eax, [esp+8]\n"); /* src */
+ out(" add eax, [esp]\n"); /* + i */
+ out(" movzx eax, byte [eax]\n"); /* eax = src[i] */
+ out(" mov ecx, [esp+4]\n"); /* dst */
+ out(" add ecx, [esp]\n"); /* + i */
+ out(" mov [ecx], al\n"); /* dst[i] = src[i] */
+ out(" add DWORD [esp], 1\n"); /* i++ */
+ out(" jmp %s\n", ltop);
+ out("%s:\n", lend);
+ out(" add esp, 12\n");
+ }
+
+ /* Stores EAX's value into the address in ECX. */
+ static void store_value(type_t *t) {
+ if (t->kind == TY_STRUCT || t->kind == TY_UNION) {
+ codegen_struct_copy(t);
+ } else if (is_byte_type(t)) {
+ out(" mov [ecx], al\n");
+ } else if (is_short_type(t)) {
+ out(" mov [ecx], ax\n");
+ } else {
+ out(" mov [ecx], eax\n");The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "DWORD\|dword" toolchain/as/parser.c toolchain/as/encode.c | head -10
toolchain/as/parser.c:237: if (ieq(w, "dword")) { *size = 4; return 1; }
toolchain/as/parser.c:270: /* `word|dword [mem]` */
toolchain/as/parser.c:276: /* `dword N` - a size hint on an immediate (e.g. `push dword 0`) */- static void codegen_struct_copy(type_t *t) {
- char ltop[MAX_NAME], lend[MAX_NAME];
- new_label(ltop); new_label(lend);
- out(" push eax\n"); /* [src] */
- out(" push ecx\n"); /* [dst, src] */
- out(" push 0\n"); /* [i, dst, src] */
- out("%s:\n", ltop);
- out(" mov eax, %d\n", type_size(t));
- out(" cmp DWORD [esp], eax\n");
- out(" jge %s\n", lend);
- out(" mov eax, [esp+8]\n"); /* src */
- out(" add eax, [esp]\n"); /* + i */
- out(" movzx eax, byte [eax]\n"); /* eax = src[i] */
- out(" mov ecx, [esp+4]\n"); /* dst */
- out(" add ecx, [esp]\n"); /* + i */
- out(" mov [ecx], al\n"); /* dst[i] = src[i] */
- out(" add DWORD [esp], 1\n"); /* i++ */
- out(" jmp %s\n", ltop);
- out("%s:\n", lend);
- out(" add esp, 12\n");
- }
+ static void codegen_struct_copy(type_t *t) {
+ char ltop[MAX_NAME], lend[MAX_NAME];
+ new_label(ltop); new_label(lend);
+ out(" push eax\n"); /* [esp+4] = src */
+ out(" push ecx\n"); /* [esp] = dst */
+ out(" mov edx, 0\n"); /* edx = i - a register, not a stack slot,
+ * so every compare/add below already has
+ * a register operand and never needs an
+ * explicit dword-size hint on bare
+ * memory + immediate. */
+ out("%s:\n", ltop);
+ out(" cmp edx, %d\n", type_size(t));
+ out(" jge %s\n", lend);
+ out(" mov eax, [esp+4]\n"); /* src */
+ out(" add eax, edx\n"); /* + i */
+ out(" movzx eax, byte [eax]\n"); /* eax = src[i] */
+ out(" mov ecx, [esp]\n"); /* dst */
+ out(" add ecx, edx\n"); /* + i */
+ out(" mov [ecx], al\n"); /* dst[i] = src[i] */
+ out(" add edx, 1\n");
+ out(" jmp %s\n", ltop);
+ out("%s:\n", lend);
+ out(" add esp, 8\n");
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
/* Whole-struct assignment (`a = b;`, or a non-brace initializer like
* `T save = *p;`) needs a real byte-for-byte copy, not a single 4-byte
* mov - the pattern the kernel's script-language parser relies on for
* backtracking (`parser_t save = *p; ... *p = save;`). */
typedef struct {
int a;
int b;
char tag[8];
} rec_t;
int main(void) {
rec_t x;
x.a = 1;
x.b = 2;
x.tag[0] = 'h';
x.tag[1] = 'i';
x.tag[2] = 0;
rec_t save = x; /* non-brace initializer, struct copy */
x.a = 100;
x.b = 200;
x.tag[0] = 'z';
x = save; /* plain struct assignment, restore */
return x.a + x.b + x.tag[0] + x.tag[1];
/* 1 + 2 + 'h'(104) + 'i'(105) = 212 */
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/tests/toolchain/t13_struct_copy.c (file state is current in your context — no need to Read it back)
- [t12_local_array_size]=4 - ) + [t12_local_array_size]=4 + [t13_struct_copy]=212 + )
The file /home/martin/workspace/prototypes/boot-to-claude/tools/run_toolchain_tests.sh has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -o build/toolchain/cc toolchain/cc/*.c -Wall 2>&1 | tail -40 ./tools/run_toolchain_tests.sh 2>&1 | tail -16
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) PASS t10_short (6) PASS t11_array_size (155) PASS t12_local_array_size (4) PASS t13_struct_copy (212) run_toolchain_tests: PASS
$ cd /home/martin/workspace/prototypes/boot-to-claude make clean make 2>&1 | tail -15 ./tools/run_tests.sh 2>&1 | tail -80
rm -rf build build/toolchain/ld -T kernel/link.ld --format bin -o build/kernel.bin build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_tinyfs.S kernel/fs/tinyfs.c build/toolchain/as -f elf32 -o build/host_tinyfs.o build/host_tinyfs.S build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_string.S kernel/string.c build/toolchain/as -f elf32 -o build/host_string.o build/host_string.S build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_mkfs_tinyfs.S tools/mkfs_tinyfs.c build/toolchain/as -f elf32 -o build/host_mkfs_tinyfs.o build/host_mkfs_tinyfs.S build/toolchain/ld -o build/mkfs_tinyfs build/host_tinyfs.o build/host_string.o build/host_mkfs_tinyfs.o dd if=/dev/zero of=build/disk.img bs=512 count=4096 status=none dd if=build/boot.bin of=build/disk.img conv=notrunc status=none dd if=build/stage2.bin of=build/disk.img bs=512 seek=1 conv=notrunc status=none dd if=build/kernel.bin of=build/disk.img bs=512 seek=5 conv=notrunc status=none build/mkfs_tinyfs build/disk.img tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc mkfs_tinyfs: done rm build/console.S build/shell/shell.S build/idt.S build/lang/script/parser.S build/lang/script/ast.S build/lang/script/eval.S build/lang/vm.S build/lang/compiler/codegen.S build/lang/compiler/parser.S build/kmain.S build/gdt.S build/string.S build/fs/tinyfs.S build/selftest.S build/serial.S build/lang/lexer.S build/fs/tinyfs_disk.S build/ata.S build/keyboard.S build/memory.S 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2qemu-system-i386: terminating on signal 15 from pid 3414156 (bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude timeout 60 ./tools/run_tests.sh 2>&1 | tail -100
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2qemu-system-i386: terminating on signal 15 from pid 3414603 (bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude timeout 20 make 2>&1 | head -20
make: Nothing to be done for 'all'.
84 wait_for "hi there" 85 86 type_line "rm greet.txt" 87 wait_for "rm: ok" 88 89 type_line "cat greet.txt" 90 wait_for "cat: no such file" 91 92 type_line "help" 93 wait_for "commands:" 94 95 type_line "run hello.tsh" 96 check_fixture_output tests/expected/hello.tsh.txt 97 98 type_line "build hello.tc hello.tbc" 99 wait_for "build: ok" 100 101 type_line "exec hello.tbc" 102 check_fixture_output tests/expected/hello.tc.txt 103 104 echo "run_tests: PASS" 105
$ cd /home/martin/workspace/prototypes/boot-to-claude cat tests/expected/hello.tsh.txt echo "---" cat tests/expected/hello.tc.txt
hello from tinyfs script x = 14 add(3,4) = 7 loop 0 loop 1 loop 2 written by script --- 14 7 55 10
$ ps aux | grep qemu | grep -v grep
(Bash completed with no output)
$ jobs -l ps aux | grep -E "python3|qemu" | grep -v grep
root 1924 0.0 0.0 54896 19840 ? Ss Jul11 0:00 /usr/bin/python3 /usr/bin/networkd-dispatcher --run-startup-triggers root 2516 0.0 0.1 870612 40792 ? Ssl Jul11 3:23 /usr/bin/python3 /usr/bin/fail2ban-server -xf start martin 3961 0.0 0.0 525032 18948 ? Sl Jul11 0:08 /usr/bin/python3 /usr/bin/redshift-gtk -l 50.37:18.06 martin 3993 0.0 0.0 72660 20660 ? S Jul11 0:11 /usr/bin/python3 /usr/share/system-config-printer/applet.py martin 4045 0.0 0.0 686876 29764 ? Sl Jul11 0:10 /usr/bin/python3 /usr/bin/blueman-applet martin 4172 0.0 0.1 449336 50432 ? Sl Jul11 4:58 /usr/bin/python3 /usr/bin/blueman-tray martin 8514 1.2 0.5 891268 172468 ? Sl Jul11 136:33 /usr/bin/python3 /usr/bin/terminator martin 745005 0.0 0.0 202440 30360 pts/11 S+ Jul16 0:00 python3 /home/martin/bin/agent-deepseek-v4-flash-zen.py martin 1848321 0.0 0.0 40060 17588 pts/14 S+ Jul17 0:00 python3 -m agent_workflow.runner claude martin 1879064 0.0 0.0 40648 18096 pts/14 S+ Jul17 0:00 /bin/python3.13 /home/martin/.local/bin/pyright-langserver --stdio martin 1879228 0.0 0.2 1060876 87704 pts/14 Sl+ Jul17 0:04 /snap/node/current/bin/node /home/martin/.local/lib/python3.13/site-packages/pyright/dist/langserver.index.js -- --stdio martin 2916528 0.0 0.0 61948 24476 ? SN Jul15 0:00 python3 /home/martin/bin/copilot-usage-billing.py martin 3305705 0.0 0.0 40060 23944 pts/26 S+ 17:30 0:00 python3 -m agent_workflow.runner codex martin 3381723 0.2 0.0 41476 25676 ? S 18:20 0:03 python3 /home/martin/workspace/prototypes/sync-trahectories/workspace_trajectory_sync.py --remote trajectory-rootmab-crypt: --server-id local-sync-trajectories martin 4152276 0.0 0.1 547944 57436 ? Sl Jul16 0:03 /usr/bin/python3 /usr/share/system-config-printer/scp-dbus-service.py
$ cd /home/martin/workspace/prototypes/boot-to-claude
rm -f build/serial.log build/qemu-monitor.sock
timeout 15 qemu-system-i386 \
-drive file=build/disk.img,format=raw \
-display none \
-serial file:build/serial.log \
-monitor unix:build/qemu-monitor.sock,server,nowait \
-no-reboot 2>&1
echo "EXIT: $?"qemu-system-i386: terminating on signal 15 from pid 3417862 (timeout) EXIT: 124
$ cd /home/martin/workspace/prototypes/boot-to-claude cat build/serial.log
tiny-os: kernel entered selftest: running SELFTEST PASS tiny-os shell. type 'help' for commands. >
$ cd /home/martin/workspace/prototypes/boot-to-claude timeout 60 ./tools/run_tests.sh > /tmp/run_tests_stdout.log 2>&1 & RTPID=$! sleep 55 kill $RTPID 2>/dev/null wait $RTPID 2>/dev/null echo "=== stdout tail ===" tail -20 /tmp/run_tests_stdout.log echo "=== serial log ===" cat build/serial.log
=== stdout tail ===
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2qemu-system-i386: terminating on signal 15 from pid 3420409 (bash)
=== serial log ===
tiny-os: kernel entered
selftest: running
SELFTEST PASS
tiny-os shell. type 'help' for commands.
> ls
hello.txt 15
hello.tsh 281
hello.tc 272
> cat hello.txt
Hello, tinyfs!
> write greet.txt hi there
write: ok
> cat greet.txt
hi there
> rm greet.txt
rm: ok
> cat greet.txt
cat: no such file
> help
commands: ls, cat <file>, write <file> <text>, rm <file>, run <file>,
build <src> <out>, exec <file>, help, clear, reboot
> run hello.tsh
0
0 0
0 0
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2
2 2$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "AST_WHILE\|\"print\"\|value_to_str\|print_value" kernel/lang/script/eval.c | head -20
127:static void print_value(value_t v) {
218: if (strcmp(node->name, "print") == 0) {
224: print_value(args[i]);
342: case AST_WHILE:120 char *r = kmalloc(la + lb + 1);
121 memcpy(r, a, la);
122 memcpy(r + la, b, lb);
123 r[la + lb] = '\0';
124 return r;
125 }
126
127 static void print_value(value_t v) {
128 if (v.type == VAL_STR) {
129 console_write(v.as.s);
130 serial_write(v.as.s);
131 } else {
132 char buf[16];
133 itoa(v.as.i, buf, 10);
134 console_write(buf);
135 serial_write(buf);
136 }
137 }
138
139 static value_t eval_binop(token_type_t op, value_t l, value_t r) {
140 if (op == TOK_PLUS && (l.type == VAL_STR || r.type == VAL_STR)) {
141 char lb[16], rb[16];
142 const char *ls = (l.type == VAL_STR) ? l.as.s : itoa(l.as.i, lb, 10);
143 const char *rs = (r.type == VAL_STR) ? r.as.s : itoa(r.as.i, rb, 10);
144 return mkstr(str_concat(ls, rs));
145 }
146 if (op == TOK_EQ || op == TOK_NEQ) {
147 int eq;
148 if (l.type == VAL_STR && r.type == VAL_STR) {
149 eq = strcmp(l.as.s, r.as.s) == 0;
150 } else if (l.type == VAL_INT && r.type == VAL_INT) {
151 eq = l.as.i == r.as.i;
152 } else {
153 eq = 0;
154 }
155 return mkint((op == TOK_EQ) ? eq : !eq);
156 }
157
158 int li = to_int(l);
159 int ri = to_int(r);944 node_t *n = node_new(ND_VAR_DECL);
945 safe_strcpy(n->name, name, MAX_NAME);
946 n->type = t;
947 n->is_global = 1;
948 n->is_extern_decl = is_extern;
949 n->is_file_static = is_static;
950 safe_strcpy(n->asm_label, name, MAX_NAME);
951 if (match(p, TK_ASSIGN)) n->init = parse_initializer(p);
952 /* `T name[] = { ... };` - array_len was left at -1 by parse_declarator
953 * (no size between the brackets), and type_size() treats a non-
954 * positive array_len as size 0 - silently making sizeof(name) come
955 * out as 0 instead of the real, initializer-inferred length. Fix it
956 * up now that the initializer list is available to count. */
957 if (t->kind == TY_ARRAY && t->array_len < 0 && n->init && n->init->kind == ND_BLOCK) {
958 int count = 0;
959 for (node_t *v = n->init->list; v; v = v->next) count++;
960 t->array_len = count;
961 }
962 return n;
963 }
964
965 static node_t *parse_func(parser_t *p, const char *name, type_t *functy,
966 char param_names[][MAX_NAME], int n_params, int is_static) {
967 sym_t *fs = add_global(p, name, functy);
968
969 if (match(p, TK_SEMI)) return NULL; /* prototype only */
970
971 p->locals = NULL;
972 p->frame_size = 0;
973 p->cur_func_ret = functy->base;
974
975 node_t *n = node_new(ND_FUNC_DEF);
976 safe_strcpy(n->name, name, MAX_NAME);
977 n->type = functy->base;
978 n->is_global = !is_static; /* controls whether codegen emits `global` - C's static limits linkage */
979 safe_strcpy(n->asm_label, name, MAX_NAME);
980
981 /* cdecl: EBP+8, +12, ... for params, in order */
982 type_t *pt = functy->param;
983 int off = 8;981 /* cdecl: EBP+8, +12, ... for params, in order */
982 type_t *pt = functy->param;
983 int off = 8;
984 node_t *ptail = NULL;
985 for (int i = 0; i < n_params && pt; i++, pt = pt->next) {
986 sym_t *s = calloc(1, sizeof(sym_t));
987 safe_strcpy(s->name, param_names[i], MAX_NAME);
988 s->type = pt;
989 s->frame_offset = off;
990 s->next = p->locals;
991 p->locals = s;
992 off += 4;
993
994 node_t *pn = node_new(ND_VAR_DECL);
995 safe_strcpy(pn->name, param_names[i], MAX_NAME);
996 pn->type = pt;
997 pn->frame_offset = s->frame_offset;
998 if (!n->list) n->list = pn; else ptail->next = pn;
999 ptail = pn;
1000 }- /* cdecl: EBP+8, +12, ... for params, in order */ - type_t *pt = functy->param; - int off = 8; - node_t *ptail = NULL; + /* cdecl: EBP+8, +12, ... for params, in order - except when the + * return type is a struct/union, which doesn't fit in EAX. That + * uses the classic hidden-pointer convention instead: the caller + * passes the address to write the result into as an implicit first + * argument, at [ebp+8], pushing every real parameter's offset out + * by 4 (see codegen.c's ND_RETURN and codegen_call_or_builtin). */ + int returns_struct = functy->base->kind == TY_STRUCT || functy->base->kind == TY_UNION; + type_t *pt = functy->param; + int off = returns_struct ? 12 : 8; + node_t *ptail = NULL;
The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/parser.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "node_t \*call = node_new(ND_CALL)" toolchain/cc/parser.c
521: node_t *call = node_new(ND_CALL);
515 if (check(p, TK_IDENT)) {
516 char name[MAX_NAME];
517 token_t ident_tok = advance(p);
518 safe_strcpy(name, ident_tok.text, MAX_NAME);
519 if (check(p, TK_LPAREN)) {
520 advance(p);
521 node_t *call = node_new(ND_CALL);
522 safe_strcpy(call->name, name, MAX_NAME);
523 node_t *tail = NULL;
524 while (!check(p, TK_RPAREN)) {
525 node_t *arg = parse_assign(p);
526 if (!call->list) call->list = arg; else tail->next = arg;
527 tail = arg;
528 if (!match(p, TK_COMMA)) break;
529 }
530 expect(p, TK_RPAREN);
531 sym_t *fs = sym_find_global(p, name);
532 call->type = (fs && fs->type->kind == TY_FUNC) ? fs->type->base : g_int_type;
533 if (!(fs && fs->type->kind == TY_FUNC)) {
534 /* Not a directly-defined function - if it resolves to a
535 * variable (local or global) instead, this is a call
536 * through a function-pointer value (e.g. tinyfs_init's
537 * read_fn/write_fn callbacks), not a direct `call name`.
538 * Locate the pointer the same way an ordinary variable
539 * reference would (see resolve_ident/codegen_addr) so
540 * codegen can load its value and `call eax`. */
541 sym_t *vs = sym_find(p, name);
542 if (vs && vs->type->kind == TY_PTR && vs->type->base->kind == TY_FUNC) {
543 call->is_indirect_call = 1;
544 call->is_global = vs->is_global;
545 call->is_static_local = vs->is_static_local;
546 call->frame_offset = vs->frame_offset;
547 safe_strcpy(call->asm_label, vs->asm_label, MAX_NAME);
548 call->type = vs->type->base->base;
549 }
550 }
551 return call;
552 }
553 return resolve_ident(p, name, ident_tok);
554 }
555 if (match(p, TK_LPAREN)) {
556 node_t *n = parse_expr(p);
557 expect(p, TK_RPAREN);
558 return n;
559 }- call->type = vs->type->base->base;
- }
- }
- return call;
+ call->type = vs->type->base->base;
+ }
+ }
+ /* Struct/union-returning call: needs a hidden temp slot in
+ * *this* function's frame to hold the result (the callee
+ * writes into it via a hidden pointer argument - see
+ * parse_func/codegen.c's ND_RETURN). Stashed in call->num
+ * (unused by ND_CALL otherwise) rather than
+ * call->frame_offset, which is_indirect_call above may
+ * already be using for the callee variable's own location. */
+ if (call->type->kind == TY_STRUCT || call->type->kind == TY_UNION) {
+ p->frame_size += type_size(call->type);
+ p->frame_size = (p->frame_size + 3) & ~3;
+ call->num = -p->frame_size;
+ }
+ return call;The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/parser.c has been updated successfully. (file state is current in your context — no need to Read it back)
330 out("%s:\n", lend);
331 return;
332 }
333 case ND_MEMBER: case ND_ARROW:
334 codegen_addr(n);
335 load_value(n->type);
336 return;
337 case ND_COMMA:
338 /* Evaluate and discard the left operand, then evaluate the
339 * right - both land in eax by convention, so evaluating `b`
340 * second naturally overwrites `a`'s (unused) result. */
341 codegen_expr(n->a);
342 codegen_expr(n->b);
343 return;
344 default:
345 fprintf(stderr, "cc: internal error: unsupported expression kind %d\n", n->kind);
346 exit(1);
347 }
348 }
349
350 /* ---------------- calls + builtins ---------------- */
351
352 extern int builtin_emit_call(const char *name, node_t *args, cc_target_t target,
353 void (*emit_arg)(node_t *), void (*emitf)(const char *, ...));
354
355 static void codegen_call_or_builtin(node_t *n) {
356 if (!n->is_indirect_call && builtin_emit_call(n->name, n->list, g_target, codegen_expr, out)) return;
357
358 int argc = 0;
359 node_t *args[16];358 int argc = 0;
359 node_t *args[16];
360 for (node_t *a = n->list; a; a = a->next) args[argc++] = a;
361 for (int i = argc - 1; i >= 0; i--) {
362 codegen_expr(args[i]);
363 out(" push eax\n");
364 }
365 if (n->is_indirect_call) {
366 /* Callee is a function-pointer variable (e.g. tinyfs_init's
367 * read_fn/write_fn callbacks), not a directly callable symbol -
368 * load its stored address (always a plain 4-byte pointer, so no
369 * load_value/is_byte_type dance needed) and call through it.
370 * Done last, after all args are already pushed, so this doesn't
371 * need to protect eax from being clobbered by arg evaluation. */
372 codegen_var_addr(n);
373 out(" mov eax, [eax]\n");
374 out(" call eax\n");
375 } else {
376 out(" call %s\n", n->name);
377 }
378 if (argc) out(" add esp, %d\n", argc * 4);
379 }
380
381 /* ---------------- statements ---------------- */
382
383 static void codegen_stmt(node_t *n) {
384 switch (n->kind) {
385 case ND_BLOCK:
386 for (node_t *s = n->list; s; s = s->next) codegen_stmt(s);
387 return;- int argc = 0;
- node_t *args[16];
- for (node_t *a = n->list; a; a = a->next) args[argc++] = a;
- for (int i = argc - 1; i >= 0; i--) {
- codegen_expr(args[i]);
- out(" push eax\n");
- }
- if (n->is_indirect_call) {
- /* Callee is a function-pointer variable (e.g. tinyfs_init's
- * read_fn/write_fn callbacks), not a directly callable symbol -
- * load its stored address (always a plain 4-byte pointer, so no
- * load_value/is_byte_type dance needed) and call through it.
- * Done last, after all args are already pushed, so this doesn't
- * need to protect eax from being clobbered by arg evaluation. */
- codegen_var_addr(n);
- out(" mov eax, [eax]\n");
- out(" call eax\n");
- } else {
- out(" call %s\n", n->name);
- }
- if (argc) out(" add esp, %d\n", argc * 4);
- }
+ int argc = 0;
+ node_t *args[16];
+ for (node_t *a = n->list; a; a = a->next) args[argc++] = a;
+ for (int i = argc - 1; i >= 0; i--) {
+ codegen_expr(args[i]);
+ out(" push eax\n");
+ }
+ /* Struct/union return: pass the hidden temp slot's address (see
+ * parse_primary's ND_CALL construction) as an implicit extra
+ * argument, pushed last so it lands first from the callee's side
+ * ([ebp+8] - matches parse_func's `returns_struct` offset shift). */
+ int is_struct_return = n->type->kind == TY_STRUCT || n->type->kind == TY_UNION;
+ if (is_struct_return) {
+ out(" lea eax, [ebp%+lld]\n", (long long)n->num);
+ out(" push eax\n");
+ }
+ if (n->is_indirect_call) {
+ /* Callee is a function-pointer variable (e.g. tinyfs_init's
+ * read_fn/write_fn callbacks), not a directly callable symbol -
+ * load its stored address (always a plain 4-byte pointer, so no
+ * load_value/is_byte_type dance needed) and call through it.
+ * Done last, after all args are already pushed, so this doesn't
+ * need to protect eax from being clobbered by arg evaluation. */
+ codegen_var_addr(n);
+ out(" mov eax, [eax]\n");
+ out(" call eax\n");
+ } else {
+ out(" call %s\n", n->name);
+ }
+ int total_args = argc + (is_struct_return ? 1 : 0);
+ if (total_args) out(" add esp, %d\n", total_args * 4);
+ /* eax already holds the hidden slot's address on return (ND_RETURN
+ * leaves it there) - exactly the "address of a struct-typed rvalue"
+ * convention load_value/codegen_expr already use elsewhere, so
+ * nothing further to do here for is_struct_return. */
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "case ND_RETURN" toolchain/cc/codegen.c
479: case ND_RETURN:
479 case ND_RETURN:
480 if (n->a) codegen_expr(n->a);
481 out(" mov esp, ebp\n pop ebp\n ret\n");
482 return;
483 case ND_BREAK:
484 out(" jmp %s\n", g_loop->break_label);
485 return;
486 case ND_CONTINUE:- case ND_RETURN:
- if (n->a) codegen_expr(n->a);
- out(" mov esp, ebp\n pop ebp\n ret\n");
- return;
+ case ND_RETURN:
+ if (n->a && (n->a->type->kind == TY_STRUCT || n->a->type->kind == TY_UNION)) {
+ /* Hidden-pointer struct return (see parse_func /
+ * codegen_call_or_builtin): copy the result into the
+ * caller-supplied slot at [ebp+8], then hand that same
+ * address back in eax - the call site relies on it
+ * being there, same convention as any other
+ * struct-typed rvalue address. */
+ codegen_expr(n->a); /* eax = src address */
+ out(" mov ecx, [ebp+8]\n"); /* ecx = dst address */
+ codegen_struct_copy(n->a->type);
+ out(" mov eax, [ebp+8]\n");
+ } else if (n->a) {
+ codegen_expr(n->a);
+ }
+ out(" mov esp, ebp\n pop ebp\n ret\n");
+ return;The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -o build/toolchain/cc toolchain/cc/*.c -Wall 2>&1 | tail -60 ./tools/run_toolchain_tests.sh 2>&1 | tail -18
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) PASS t10_short (6) PASS t11_array_size (155) PASS t12_local_array_size (4) PASS t13_struct_copy (212) run_toolchain_tests: PASS
/* Struct-by-value function return (hidden-pointer ABI) - the pattern
* kernel/lang/script/eval.c's mkint()/mkstr() constructors rely on
* throughout the scripting language's evaluator. */
typedef struct {
int type;
int val;
} tagged_t;
static tagged_t make(int type, int val) {
tagged_t t;
t.type = type;
t.val = val;
return t;
}
static tagged_t identity(tagged_t t) {
return t;
}
int main(void) {
tagged_t a = make(1, 10);
tagged_t b = make(2, 20);
tagged_t c = identity(a);
int sum = a.type + a.val + b.type + b.val + c.type + c.val;
/* also exercise a struct-returning call used directly as an
* argument-like expression, not just assigned to a local first */
sum += make(3, 30).val;
return sum; /* (1+10)+(2+20)+(1+10) + 30 = 11+22+11+30 = 74 */
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/tests/toolchain/t14_struct_return.c (file state is current in your context — no need to Read it back)
- int sum = a.type + a.val + b.type + b.val + c.type + c.val; - /* also exercise a struct-returning call used directly as an - * argument-like expression, not just assigned to a local first */ - sum += make(3, 30).val; - return sum; /* (1+10)+(2+20)+(1+10) + 30 = 11+22+11+30 = 74 */ + int sum = a.type + a.val + b.type + b.val + c.type + c.val; + return sum; /* (1+10)+(2+20)+(1+10) = 11+22+11 = 44 */
The file /home/martin/workspace/prototypes/boot-to-claude/tests/toolchain/t14_struct_return.c has been updated successfully. (file state is current in your context — no need to Read it back)
- [t13_struct_copy]=212 - ) + [t13_struct_copy]=212 + [t14_struct_return]=44 + )
The file /home/martin/workspace/prototypes/boot-to-claude/tools/run_toolchain_tests.sh has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude ./tools/run_toolchain_tests.sh 2>&1 | tail -17
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) PASS t10_short (6) PASS t11_array_size (155) PASS t12_local_array_size (4) PASS t13_struct_copy (212) FAIL t14_struct_return: expected 44, got 145 run_toolchain_tests: FAIL
$ cd /home/martin/workspace/prototypes/boot-to-claude build/toolchain/cc -Itoolchain/include --target=host -o /tmp/t14.S tests/toolchain/t14_struct_return.c cat /tmp/t14.S
BITS 32
section .text
make:
push ebp
mov ebp, esp
sub esp, 8
lea eax, [ebp-8]
push eax
lea eax, [ebp+12]
mov eax, [eax]
pop ecx
mov [ecx], eax
lea eax, [ebp-8]
add eax, 4
push eax
lea eax, [ebp+16]
mov eax, [eax]
pop ecx
mov [ecx], eax
lea eax, [ebp-8]
mov ecx, [ebp+8]
push eax
push ecx
mov edx, 0
.L0:
cmp edx, 8
jge .L1
mov eax, [esp+4]
add eax, edx
movzx eax, byte [eax]
mov ecx, [esp]
add ecx, edx
mov [ecx], al
add edx, 1
jmp .L0
.L1:
add esp, 8
mov eax, [ebp+8]
mov esp, ebp
pop ebp
ret
mov eax, 0
mov esp, ebp
pop ebp
ret
identity:
push ebp
mov ebp, esp
lea eax, [ebp+12]
mov ecx, [ebp+8]
push eax
push ecx
mov edx, 0
.L2:
cmp edx, 8
jge .L3
mov eax, [esp+4]
add eax, edx
movzx eax, byte [eax]
mov ecx, [esp]
add ecx, edx
mov [ecx], al
add edx, 1
jmp .L2
.L3:
add esp, 8
mov eax, [ebp+8]
mov esp, ebp
pop ebp
ret
mov eax, 0
mov esp, ebp
pop ebp
ret
global main
main:
push ebp
mov ebp, esp
sub esp, 52
lea eax, [ebp-8]
push eax
mov eax, 10
push eax
mov eax, 1
push eax
lea eax, [ebp-16]
push eax
call make
add esp, 12
pop ecx
push eax
push ecx
mov edx, 0
.L4:
cmp edx, 8
jge .L5
mov eax, [esp+4]
add eax, edx
movzx eax, byte [eax]
mov ecx, [esp]
add ecx, edx
mov [ecx], al
add edx, 1
jmp .L4
.L5:
add esp, 8
lea eax, [ebp-24]
push eax
mov eax, 20
push eax
mov eax, 2
push eax
lea eax, [ebp-32]
push eax
call make
add esp, 12
pop ecx
push eax
push ecx
mov edx, 0
.L6:
cmp edx, 8
jge .L7
mov eax, [esp+4]
add eax, edx
movzx eax, byte [eax]
mov ecx, [esp]
add ecx, edx
mov [ecx], al
add edx, 1
jmp .L6
.L7:
add esp, 8
lea eax, [ebp-40]
push eax
lea eax, [ebp-8]
push eax
lea eax, [ebp-48]
push eax
call identity
add esp, 8
pop ecx
push eax
push ecx
mov edx, 0
.L8:
cmp edx, 8
jge .L9
mov eax, [esp+4]
add eax, edx
movzx eax, byte [eax]
mov ecx, [esp]
add ecx, edx
mov [ecx], al
add edx, 1
jmp .L8
.L9:
add esp, 8
lea eax, [ebp-52]
push eax
lea eax, [ebp-40]
add eax, 4
mov eax, [eax]
push eax
lea eax, [ebp-40]
mov eax, [eax]
push eax
lea eax, [ebp-24]
add eax, 4
mov eax, [eax]
push eax
lea eax, [ebp-24]
mov eax, [eax]
push eax
lea eax, [ebp-8]
add eax, 4
mov eax, [eax]
push eax
lea eax, [ebp-8]
mov eax, [eax]
pop ecx
add eax, ecx
pop ecx
add eax, ecx
pop ecx
add eax, ecx
pop ecx
add eax, ecx
pop ecx
add eax, ecx
pop ecx
mov [ecx], eax
lea eax, [ebp-52]
mov eax, [eax]
mov esp, ebp
pop ebp
ret
mov eax, 0
mov esp, ebp
pop ebp
ret
global _start
_start:
mov eax, [esp]
lea ecx, [esp+4]
push ecx
push eax
call main
mov ebx, eax
mov eax, 1
int 0x80985 p->cur_func_ret = functy->base;
986
987 node_t *n = node_new(ND_FUNC_DEF);
988 safe_strcpy(n->name, name, MAX_NAME);
989 n->type = functy->base;
990 n->is_global = !is_static; /* controls whether codegen emits `global` - C's static limits linkage */
991 safe_strcpy(n->asm_label, name, MAX_NAME);
992
993 /* cdecl: EBP+8, +12, ... for params, in order - except when the
994 * return type is a struct/union, which doesn't fit in EAX. That
995 * uses the classic hidden-pointer convention instead: the caller
996 * passes the address to write the result into as an implicit first
997 * argument, at [ebp+8], pushing every real parameter's offset out
998 * by 4 (see codegen.c's ND_RETURN and codegen_call_or_builtin). */
999 int returns_struct = functy->base->kind == TY_STRUCT || functy->base->kind == TY_UNION;
1000 type_t *pt = functy->param;
1001 int off = returns_struct ? 12 : 8;
1002 node_t *ptail = NULL;
1003 for (int i = 0; i < n_params && pt; i++, pt = pt->next) {
1004 sym_t *s = calloc(1, sizeof(sym_t));1003 for (int i = 0; i < n_params && pt; i++, pt = pt->next) {
1004 sym_t *s = calloc(1, sizeof(sym_t));
1005 safe_strcpy(s->name, param_names[i], MAX_NAME);
1006 s->type = pt;
1007 s->frame_offset = off;
1008 s->next = p->locals;
1009 p->locals = s;
1010 off += 4;
1011
1012 node_t *pn = node_new(ND_VAR_DECL);
1013 safe_strcpy(pn->name, param_names[i], MAX_NAME);
1014 pn->type = pt;
1015 pn->frame_offset = s->frame_offset;
1016 if (!n->list) n->list = pn; else ptail->next = pn;
1017 ptail = pn;
1018 }
1019
1020 n->b = parse_block(p);
1021 n->num = p->frame_size;
1022 fs->type = functy;- s->frame_offset = off;
- s->next = p->locals;
- p->locals = s;
- off += 4;
+ s->frame_offset = off;
+ s->next = p->locals;
+ p->locals = s;
+ /* Struct/union parameters are passed by value as their real
+ * (4-byte-rounded) size, not a flat 4 bytes like every scalar/
+ * pointer param - the caller copies the whole struct onto the
+ * stack (see codegen_call_or_builtin), so subsequent params'
+ * offsets need to account for its actual width. */
+ if (pt->kind == TY_STRUCT || pt->kind == TY_UNION) {
+ int sz = (type_size(pt) + 3) & ~3;
+ off += sz < 4 ? 4 : sz;
+ } else {
+ off += 4;
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/parser.c has been updated successfully. (file state is current in your context — no need to Read it back)
355 static void codegen_call_or_builtin(node_t *n) {
356 if (!n->is_indirect_call && builtin_emit_call(n->name, n->list, g_target, codegen_expr, out)) return;
357
358 int argc = 0;
359 node_t *args[16];
360 for (node_t *a = n->list; a; a = a->next) args[argc++] = a;
361 for (int i = argc - 1; i >= 0; i--) {
362 codegen_expr(args[i]);
363 out(" push eax\n");
364 }
365 /* Struct/union return: pass the hidden temp slot's address (see
366 * parse_primary's ND_CALL construction) as an implicit extra
367 * argument, pushed last so it lands first from the callee's side
368 * ([ebp+8] - matches parse_func's `returns_struct` offset shift). */
369 int is_struct_return = n->type->kind == TY_STRUCT || n->type->kind == TY_UNION;
370 if (is_struct_return) {
371 out(" lea eax, [ebp%+lld]\n", (long long)n->num);
372 out(" push eax\n");
373 }
374 if (n->is_indirect_call) {
375 /* Callee is a function-pointer variable (e.g. tinyfs_init's
376 * read_fn/write_fn callbacks), not a directly callable symbol -
377 * load its stored address (always a plain 4-byte pointer, so no
378 * load_value/is_byte_type dance needed) and call through it.
379 * Done last, after all args are already pushed, so this doesn't
380 * need to protect eax from being clobbered by arg evaluation. */
381 codegen_var_addr(n);
382 out(" mov eax, [eax]\n");
383 out(" call eax\n");
384 } else {
385 out(" call %s\n", n->name);
386 }
387 int total_args = argc + (is_struct_return ? 1 : 0);
388 if (total_args) out(" add esp, %d\n", total_args * 4);
389 /* eax already holds the hidden slot's address on return (ND_RETURN
390 * leaves it there) - exactly the "address of a struct-typed rvalue"
391 * convention load_value/codegen_expr already use elsewhere, so
392 * nothing further to do here for is_struct_return. */
393 }
394 - int argc = 0;
- node_t *args[16];
- for (node_t *a = n->list; a; a = a->next) args[argc++] = a;
- for (int i = argc - 1; i >= 0; i--) {
- codegen_expr(args[i]);
- out(" push eax\n");
- }
- /* Struct/union return: pass the hidden temp slot's address (see
- * parse_primary's ND_CALL construction) as an implicit extra
- * argument, pushed last so it lands first from the callee's side
- * ([ebp+8] - matches parse_func's `returns_struct` offset shift). */
- int is_struct_return = n->type->kind == TY_STRUCT || n->type->kind == TY_UNION;
- if (is_struct_return) {
- out(" lea eax, [ebp%+lld]\n", (long long)n->num);
- out(" push eax\n");
- }
- if (n->is_indirect_call) {
- /* Callee is a function-pointer variable (e.g. tinyfs_init's
- * read_fn/write_fn callbacks), not a directly callable symbol -
- * load its stored address (always a plain 4-byte pointer, so no
- * load_value/is_byte_type dance needed) and call through it.
- * Done last, after all args are already pushed, so this doesn't
- * need to protect eax from being clobbered by arg evaluation. */
- codegen_var_addr(n);
- out(" mov eax, [eax]\n");
- out(" call eax\n");
- } else {
- out(" call %s\n", n->name);
- }
- int total_args = argc + (is_struct_return ? 1 : 0);
- if (total_args) out(" add esp, %d\n", total_args * 4);
- /* eax already holds the hidden slot's address on return (ND_RETURN
- * leaves it there) - exactly the "address of a struct-typed rvalue"
- * convention load_value/codegen_expr already use elsewhere, so
- * nothing further to do here for is_struct_return. */
- }
+ int argc = 0;
+ node_t *args[16];
+ for (node_t *a = n->list; a; a = a->next) args[argc++] = a;
+ int total_bytes = 0;
+ for (int i = argc - 1; i >= 0; i--) {
+ codegen_expr(args[i]);
+ if (args[i]->type->kind == TY_STRUCT || args[i]->type->kind == TY_UNION) {
+ /* Struct/union by value: codegen_expr already yields its
+ * address (see load_value), not its bytes - copy the whole
+ * thing onto the stack rather than pushing a 4-byte pointer,
+ * matching parse_func's real-size parameter offsets. */
+ int sz = type_size(args[i]->type);
+ sz = (sz + 3) & ~3;
+ if (sz < 4) sz = 4;
+ out(" mov ecx, eax\n"); /* save src - sub esp doesn't touch ecx */
+ out(" sub esp, %d\n", sz);
+ out(" mov eax, ecx\n"); /* src */
+ out(" mov ecx, esp\n"); /* dst = the space just reserved */
+ codegen_struct_copy(args[i]->type);
+ total_bytes += sz;
+ } else {
+ out(" push eax\n");
+ total_bytes += 4;
+ }
+ }
+ /* Struct/union return: pass the hidden temp slot's address (see
+ * parse_primary's ND_CALL construction) as an implicit extra
+ * argument, pushed last so it lands first from the callee's side
+ * ([ebp+8] - matches parse_func's `returns_struct` offset shift). */
+ int is_struct_return = n->type->kind == TY_STRUCT || n->type->kind == TY_UNION;
+ if (is_struct_return) {
+ out(" lea eax, [ebp%+lld]\n", (long long)n->num);
+ out(" push eax\n");
+ total_bytes += 4;
+ }
+ if (n->is_indirect_call) {
+ /* Callee is a function-pointer variable (e.g. tinyfs_init's
+ * read_fn/write_fn callbacks), not a directly callable symbol -
+ * load its stored address (always a plain 4-byte pointer, so no
+ * load_value/is_byte_type dance needed) and call through it.
+ * Done last, after all args are already pushed, so this doesn't
+ * need to protect eax from being clobbered by arg evaluation. */
+ codegen_var_addr(n);
+ out(" mov eax, [eax]\n");
+ out(" call eax\n");
+ } else {
+ out(" call %s\n", n->name);
+ }
+ if (total_bytes) out(" add esp, %d\n", total_bytes);
+ /* eax already holds the hidden slot's address on return (ND_RETURN
+ * leaves it there) - exactly the "address of a struct-typed rvalue"
+ * convention load_value/codegen_expr already use elsewhere, so
+ * nothing further to do here for is_struct_return. */
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -o build/toolchain/cc toolchain/cc/*.c -Wall 2>&1 | tail -60 ./tools/run_toolchain_tests.sh 2>&1 | tail -17
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) PASS t10_short (6) PASS t11_array_size (155) PASS t12_local_array_size (4) PASS t13_struct_copy (212) PASS t14_struct_return (44) run_toolchain_tests: PASS
$ cd /home/martin/workspace/prototypes/boot-to-claude make clean make 2>&1 | tail -15 timeout 60 ./tools/run_tests.sh 2>&1 | tail -100
rm -rf build build/toolchain/cc -Ikernel -Itoolchain/include -o build/keyboard.S kernel/keyboard.c build/toolchain/as -f elf32 -o build/keyboard.o build/keyboard.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/shell/shell.S kernel/shell/shell.c build/toolchain/as -f elf32 -o build/shell/shell.o build/shell/shell.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/lexer.S kernel/lang/lexer.c build/toolchain/as -f elf32 -o build/lang/lexer.o build/lang/lexer.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/script/ast.S kernel/lang/script/ast.c build/toolchain/as -f elf32 -o build/lang/script/ast.o build/lang/script/ast.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/script/parser.S kernel/lang/script/parser.c build/toolchain/as -f elf32 -o build/lang/script/parser.o build/lang/script/parser.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/script/eval.S kernel/lang/script/eval.c build/toolchain/as -f elf32 -o build/lang/script/eval.o build/lang/script/eval.S as: too many statements make: *** [Makefile:108: build/lang/script/eval.o] Error 1 rm build/console.S build/shell/shell.S build/idt.S build/lang/script/parser.S build/lang/script/ast.S build/lang/script/eval.S build/kmain.S build/gdt.S build/string.S build/fs/tinyfs.S build/selftest.S build/serial.S build/lang/lexer.S build/fs/tinyfs_disk.S build/ata.S build/keyboard.S build/memory.S as: too many statements make: *** [Makefile:108: build/lang/script/eval.o] Error 1
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -rn "too many statements\|MAX_STMTS\|MAX_STATEMENTS" toolchain/as/*.c toolchain/as/*.h
toolchain/as/as.h:19:#define MAX_STMTS 4096
toolchain/as/as.h:101: stmt_t stmts[MAX_STMTS];
toolchain/as/parser.c:317: if (ctx->nstmts >= MAX_STMTS) { fprintf(stderr, "as: too many statements\n"); exit(1); }15 dst[i] = '\0'; 16 } 17 18 #define MAX_SYMS 512 19 #define MAX_STMTS 4096 20 #define MAX_RELOCS 256 21 #define MAX_LINE 512 22 #define MAX_NAME 64
- #define MAX_SYMS 512 - #define MAX_STMTS 4096 - #define MAX_RELOCS 256 + /* Generous fixed limits - struct-by-value codegen (each copy/return/ + * argument expands into a whole compare-and-loop, see docs/toolchain.md) + * pushed kernel/lang/script/eval.c well past the old, tighter numbers. */ + #define MAX_SYMS 4096 + #define MAX_STMTS 32768 + #define MAX_RELOCS 4096
The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/as.h has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -Wall -Wextra -O2 -o build/toolchain/as toolchain/as/*.c 2>&1 | tail -40 ./tools/run_toolchain_tests.sh 2>&1 | tail -17
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) PASS t10_short (6) PASS t11_array_size (155) PASS t12_local_array_size (4) PASS t13_struct_copy (212) PASS t14_struct_return (44) run_toolchain_tests: PASS
$ cd /home/martin/workspace/prototypes/boot-to-claude make clean make 2>&1 | tail -30
rm -rf build build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/lexer.S kernel/lang/lexer.c build/toolchain/as -f elf32 -o build/lang/lexer.o build/lang/lexer.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/script/ast.S kernel/lang/script/ast.c build/toolchain/as -f elf32 -o build/lang/script/ast.o build/lang/script/ast.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/script/parser.S kernel/lang/script/parser.c build/toolchain/as -f elf32 -o build/lang/script/parser.o build/lang/script/parser.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/script/eval.S kernel/lang/script/eval.c build/toolchain/as -f elf32 -o build/lang/script/eval.o build/lang/script/eval.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/compiler/parser.S kernel/lang/compiler/parser.c build/toolchain/as -f elf32 -o build/lang/compiler/parser.o build/lang/compiler/parser.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/compiler/codegen.S kernel/lang/compiler/codegen.c build/toolchain/as -f elf32 -o build/lang/compiler/codegen.o build/lang/compiler/codegen.S build/toolchain/cc -Ikernel -Itoolchain/include -o build/lang/vm.S kernel/lang/vm.c build/toolchain/as -f elf32 -o build/lang/vm.o build/lang/vm.S gcc -Wall -Wextra -O2 -o build/toolchain/ld toolchain/ld/elf_reader.c toolchain/ld/link_flat.c toolchain/ld/link_static_exec.c toolchain/ld/main.c build/toolchain/ld -T kernel/link.ld --format bin -o build/kernel.bin build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_tinyfs.S kernel/fs/tinyfs.c build/toolchain/as -f elf32 -o build/host_tinyfs.o build/host_tinyfs.S build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_string.S kernel/string.c build/toolchain/as -f elf32 -o build/host_string.o build/host_string.S build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_mkfs_tinyfs.S tools/mkfs_tinyfs.c build/toolchain/as -f elf32 -o build/host_mkfs_tinyfs.o build/host_mkfs_tinyfs.S build/toolchain/ld -o build/mkfs_tinyfs build/host_tinyfs.o build/host_string.o build/host_mkfs_tinyfs.o dd if=/dev/zero of=build/disk.img bs=512 count=4096 status=none dd if=build/boot.bin of=build/disk.img conv=notrunc status=none dd if=build/stage2.bin of=build/disk.img bs=512 seek=1 conv=notrunc status=none dd if=build/kernel.bin of=build/disk.img bs=512 seek=5 conv=notrunc status=none build/mkfs_tinyfs build/disk.img tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc mkfs_tinyfs: done rm build/console.S build/shell/shell.S build/idt.S build/lang/script/parser.S build/lang/script/ast.S build/lang/script/eval.S build/lang/vm.S build/lang/compiler/codegen.S build/lang/compiler/parser.S build/kmain.S build/gdt.S build/string.S build/fs/tinyfs.S build/selftest.S build/serial.S build/lang/lexer.S build/fs/tinyfs_disk.S build/ata.S build/keyboard.S build/memory.S
$ cd /home/martin/workspace/prototypes/boot-to-claude timeout 90 ./tools/run_tests.sh > /tmp/final_test.log 2>&1 echo "exit=$?" tail -60 /tmp/final_test.log
exit=1
run_tests: FAIL - timed out waiting for 'build: ok'
--- serial log ---
tiny-os: kernel entered
selftest: running
SELFTEST PASS
tiny-os shell. type 'help' for commands.
> ls
hello.txt 15
hello.tsh 281
hello.tc 272
> cat hello.txt
Hello, tinyfs!
> write greet.txt hi there
write: ok
> cat greet.txt
hi there
> rm greet.txt
rm: ok
> cat greet.txt
cat: no such file
> help
commands: ls, cat <file>, write <file> <text>, rm <file>, run <file>,
build <src> <out>, exec <file>, help, clear, reboot
> run hello.tsh
hello from tinyfs script
x = 14
add(3,4) = 7
loop 0
loop 1
loop 2
written by script
> uild hello.tc hello.tb bc
unknown command (type 'help')
> qemu-system-i386: terminating on signal 15 from pid 3573580 (bash)#!/usr/bin/env bash
set -euo pipefail
cd /home/martin/workspace/prototypes/boot-to-claude
BUILD=build
LOG=/tmp/serial_build_test2.log
MON=/tmp/mon_build_test2.sock
rm -f "$LOG" "$MON"
qemu-system-i386 \
-drive file="$BUILD/disk.img",format=raw \
-display none \
-serial file:"$LOG" \
-monitor unix:"$MON",server,nowait \
-no-reboot &
QEMU_PID=$!
cleanup() { kill "$QEMU_PID" 2>/dev/null || true; wait "$QEMU_PID" 2>/dev/null || true; }
trap cleanup EXIT
wait_for() {
local pattern="$1"
local max_tries="${2:-50}"
local i=0
while ! grep -qF "$pattern" "$LOG" 2>/dev/null; do
i=$((i + 1))
if [ "$i" -ge "$max_tries" ]; then
echo "TIMEOUT waiting for '$pattern'"
cat "$LOG"
exit 1
fi
sleep 0.2
done
}
type_line() {
python3 tools/qemu_type.py "$MON" "$1
"
}
for i in $(seq 1 25); do
[ -S "$MON" ] && break
sleep 0.2
done
wait_for "type 'help'"
type_line "build hello.tc hello.tbc"
sleep 2
cat "$LOG"
File created successfully at: /tmp/test_build2.sh (file state is current in your context — no need to Read it back)
$ chmod +x /tmp/test_build2.sh /tmp/test_build2.sh
tiny-os: kernel entered selftest: running SELFTEST PASS tiny-os shell. type 'help' for commands. > uild hello.tc hello.tb bc unknown command (type 'help') > qemu-system-i386: terminating on signal 15 from pid 3576053 (bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude
cat > /tmp/qemu_type_slow.py << 'PYEOF'
import socket
import sys
import time
KEYMAP = {
" ": "spc", ".": "dot", "\n": "ret", "-": "minus", "_": "shift-minus",
",": "comma", "/": "slash",
}
def key_name(c):
if c in KEYMAP: return KEYMAP[c]
if c.isalpha(): return c.lower()
if c.isdigit(): return c
raise ValueError(f"no key mapping for {c!r}")
def main():
sock_path, text = sys.argv[1], sys.argv[2]
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect(sock_path)
time.sleep(0.2)
s.recv(4096)
for c in text:
s.sendall(f"sendkey {key_name(c)}\n".encode())
time.sleep(0.3)
s.recv(4096)
s.close()
if __name__ == "__main__":
main()
PYEOF
rm -f /tmp/mon_build_test3.sock /tmp/serial_build_test3.log
qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial file:/tmp/serial_build_test3.log -monitor unix:/tmp/mon_build_test3.sock,server,nowait -no-reboot &
QPID=$!
for i in $(seq 1 25); do [ -S /tmp/mon_build_test3.sock ] && break; sleep 0.2; done
sleep 3
python3 /tmp/qemu_type_slow.py /tmp/mon_build_test3.sock "build hello.tc hello.tbc
"
sleep 2
kill $QPID 2>/dev/null
cat /tmp/serial_build_test3.logqemu-system-i386: terminating on signal 15 from pid 3576274 (/bin/bash) tiny-os: kernel entered selftest: running SELFTEST PASS tiny-os shell. type 'help' for commands. > uild hello.tc hello.tb bc unknown command (type 'help') >
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "'\\\\\\\\'\|TK_CHAR\|parse.*char.*literal\|case '\\\\''" toolchain/cc/lexer.c | head -20
49: case '\\': return '\\'; 50: case '\'': return '\''; 115: if (ch == '\\') ch = read_escape(lx); 129: if (ch == '\\') ch = read_escape(lx); 131: token_t t = mk(lx, TK_CHAR);
<tool_use_error>InputValidationError: Read was called with input that could not be parsed as JSON.
You sent (first 105 of 105 bytes): {"file_path": "/home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/lexer.c", "offset": 30, 140}
Common causes: unescaped backslashes in file paths (use / or \\), unescaped control characters, or truncated output. Retry with valid JSON.</tool_use_error>30 }
31
32 static token_t mk(lexer_t *lx, tok_kind_t k) {
33 token_t t;
34 memset(&t, 0, sizeof(t));
35 t.kind = k;
36 t.line = lx->line;
37 t.file = lx->filename;
38 t.bol = lx->bol;
39 return t;
40 }
41
42 static int read_escape(lexer_t *lx) {
43 int c = advc(lx);
44 switch (c) {
45 case 'n': return '\n';
46 case 't': return '\t';
47 case 'r': return '\r';
48 case '0': return '\0';
49 case '\\': return '\\';
50 case '\'': return '\'';
51 case '"': return '"';
52 default: return c;
53 }
54 }
55
56 token_t lexer_next(lexer_t *lx) {
57 if (lx->has_pushback) { lx->has_pushback = 0; return lx->pushback; }
58 for (;;) {
59 int c = peekc(lx);
60 if (c == ' ' || c == '\t' || c == '\r') { lx->pos++; continue; }
61 if (c == '\n') { advc(lx); lx->bol = 1; continue; }
62 if (c == '/' && peekc2(lx) == '/') {
63 while (peekc(lx) != -1 && peekc(lx) != '\n') lx->pos++;
64 continue;
65 }
66 if (c == '/' && peekc2(lx) == '*') {
67 lx->pos += 2;
68 while (peekc(lx) != -1 && !(peekc(lx) == '*' && peekc2(lx) == '/')) advc(lx);
69 if (peekc(lx) != -1) lx->pos += 2;
70 continue;
71 }
72 break;
73 }
74
75 int was_bol = lx->bol;
76 lx->bol = 0;
77 int c = peekc(lx);
78 if (c == -1) { token_t t = mk(lx, TK_EOF); t.bol = was_bol; return t; }
79
80 if (c == '#' && was_bol) { lx->pos++; token_t t = mk(lx, TK_HASH); t.bol = 1; return t; }
81
82 if (isalpha(c) || c == '_') {
83 size_t start = lx->pos;
84 while (isalnum(peekc(lx)) || peekc(lx) == '_') lx->pos++;
85 token_t t = mk(lx, TK_IDENT);
86 t.bol = was_bol;
87 size_t n = lx->pos - start;
88 if (n >= sizeof(t.text)) n = sizeof(t.text) - 1;
89 memcpy(t.text, lx->src + start, n);
90 t.text[n] = '\0';
91 return t;
92 }
93
94 if (isdigit(c)) {
95 size_t start = lx->pos;
96 while (isalnum(peekc(lx))) lx->pos++;
97 token_t t = mk(lx, TK_NUM);
98 t.bol = was_bol;
99 int len = (int)(lx->pos - start);
100 const char *p = lx->src + start;
101 if (len > 2 && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
102 t.num = strtoll(p + 2, NULL, 16);
103 } else {
104 t.num = strtoll(p, NULL, 10);
105 }
106 return t;
107 }
108
109 if (c == '"') {
110 lx->pos++;
111 char buf[256];
112 int n = 0;
113 while (peekc(lx) != -1 && peekc(lx) != '"') {
114 int ch = advc(lx);
115 if (ch == '\\') ch = read_escape(lx);
116 if (n < (int)sizeof(buf) - 1) buf[n++] = (char)ch;
117 }
118 if (peekc(lx) == '"') lx->pos++;
119 buf[n] = '\0';
120 token_t t = mk(lx, TK_STR);
121 t.bol = was_bol;
122 memcpy(t.text, buf, (size_t)n + 1);
123 return t;
124 }
125
126 if (c == '\'') {
127 lx->pos++;
128 int ch = advc(lx);
129 if (ch == '\\') ch = read_escape(lx);
130 if (peekc(lx) == '\'') lx->pos++;
131 token_t t = mk(lx, TK_CHAR);
132 t.bol = was_bol;
133 t.num = ch;
134 return t;
135 }
136
137 #define TWO(c1, c2, kind) if (c == (c1) && peekc2(lx) == (c2)) { lx->pos += 2; token_t t = mk(lx, kind); t.bol = was_bol; return t; }
138 TWO('=', '=', TK_EQ) TWO('!', '=', TK_NEQ) TWO('<', '=', TK_LE) TWO('>', '=', TK_GE)
139 TWO('&', '&', TK_AND) TWO('|', '|', TK_OR) TWO('+', '+', TK_INC) TWO('-', '-', TK_DEC)- static int read_escape(lexer_t *lx) {
- int c = advc(lx);
- switch (c) {
- case 'n': return '\n';
- case 't': return '\t';
- case 'r': return '\r';
- case '0': return '\0';
- case '\\': return '\\';
- case '\'': return '\'';
- case '"': return '"';
- default: return c;
- }
- }
+ static int read_escape(lexer_t *lx) {
+ int c = advc(lx);
+ switch (c) {
+ case 'n': return '\n';
+ case 't': return '\t';
+ case 'r': return '\r';
+ case 'b': return '\b';
+ case 'f': return '\f';
+ case 'v': return '\v';
+ case 'a': return '\a';
+ case '0': return '\0';
+ case '\\': return '\\';
+ case '\'': return '\'';
+ case '"': return '"';
+ default: return c;
+ }
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/lexer.c has been updated successfully. (file state is current in your context — no need to Read it back)
/* '\b' (and friends) must lex to their real control-character values,
* not fall through to the literal letter - kernel/keyboard.c's
* lower_table[] and kernel/shell/shell.c's `if (c == '\b')` both depend
* on this (a wrong '\b' == 'b' made every typed letter 'b' act like a
* backspace keypress instead). */
int main(void) {
char b = '\b';
char t = '\t';
char n = '\n';
char bs = '\\';
char q = '\'';
return (int)b + (int)t + (int)n + (int)bs + (int)q;
/* 8 + 9 + 10 + 92 + 39 = 158 */
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/tests/toolchain/t15_escapes.c (file state is current in your context — no need to Read it back)
- [t14_struct_return]=44 - ) + [t14_struct_return]=44 + [t15_escapes]=158 + )
The file /home/martin/workspace/prototypes/boot-to-claude/tools/run_toolchain_tests.sh has been updated successfully. (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -o build/toolchain/cc toolchain/cc/*.c -Wall 2>&1 | tail -40 ./tools/run_toolchain_tests.sh 2>&1 | tail -18
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) PASS t10_short (6) PASS t11_array_size (155) PASS t12_local_array_size (4) PASS t13_struct_copy (212) PASS t14_struct_return (44) PASS t15_escapes (158) run_toolchain_tests: PASS
$ cd /home/martin/workspace/prototypes/boot-to-claude make clean make 2>&1 | tail -15 timeout 90 ./tools/run_tests.sh > /tmp/final_test2.log 2>&1 echo "exit=$?" tail -100 /tmp/final_test2.log
rm -rf build build/toolchain/ld -T kernel/link.ld --format bin -o build/kernel.bin build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_tinyfs.S kernel/fs/tinyfs.c build/toolchain/as -f elf32 -o build/host_tinyfs.o build/host_tinyfs.S build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_string.S kernel/string.c build/toolchain/as -f elf32 -o build/host_string.o build/host_string.S build/toolchain/cc --target=host -Ikernel -Itools -Itoolchain/include -o build/host_mkfs_tinyfs.S tools/mkfs_tinyfs.c build/toolchain/as -f elf32 -o build/host_mkfs_tinyfs.o build/host_mkfs_tinyfs.S build/toolchain/ld -o build/mkfs_tinyfs build/host_tinyfs.o build/host_string.o build/host_mkfs_tinyfs.o dd if=/dev/zero of=build/disk.img bs=512 count=4096 status=none dd if=build/boot.bin of=build/disk.img conv=notrunc status=none dd if=build/stage2.bin of=build/disk.img bs=512 seek=1 conv=notrunc status=none dd if=build/kernel.bin of=build/disk.img bs=512 seek=5 conv=notrunc status=none build/mkfs_tinyfs build/disk.img tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc mkfs_tinyfs: done rm build/console.S build/shell/shell.S build/idt.S build/lang/script/parser.S build/lang/script/ast.S build/lang/script/eval.S build/lang/vm.S build/lang/compiler/codegen.S build/lang/compiler/parser.S build/kmain.S build/gdt.S build/string.S build/fs/tinyfs.S build/selftest.S build/serial.S build/lang/lexer.S build/fs/tinyfs_disk.S build/ata.S build/keyboard.S build/memory.S exit=0 run_tests: PASS qemu-system-i386: terminating on signal 15 from pid 3576813 (bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude echo "=== toolchain tests ===" ./tools/run_toolchain_tests.sh 2>&1 | tail -18 echo "=== full OS tests ===" timeout 90 ./tools/run_tests.sh 2>&1 | tail -10
=== toolchain tests === PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) PASS t10_short (6) PASS t11_array_size (155) PASS t12_local_array_size (4) PASS t13_struct_copy (212) PASS t14_struct_return (44) PASS t15_escapes (158) run_toolchain_tests: PASS === full OS tests === run_tests: PASS qemu-system-i386: terminating on signal 15 from pid 3576984 (bash)
$ rm -f /tmp/*.log /tmp/*.sock /tmp/*.S /tmp/*.o /tmp/*.elf /tmp/*.bin /tmp/*.img /tmp/gdb_*.txt /tmp/test_build*.sh /tmp/qemu_type_slow.py /tmp/kbd_orig.S /tmp/kernel_dbg*.elf /tmp/tinyfs_o_text.bin /tmp/t10_debug* 2>/dev/null rm -rf /tmp/old_test cd /home/martin/workspace/prototypes/boot-to-claude git status --short
M Makefile M kernel/lang/script/eval.c M toolchain/as/as.h M toolchain/as/encode.c M toolchain/cc/cc.h M toolchain/cc/codegen.c M toolchain/cc/lexer.c M toolchain/cc/parser.c M tools/run_toolchain_tests.sh ?? tests/toolchain/t06_switch.c ?? tests/toolchain/t07_comma.c ?? tests/toolchain/t08_forward_struct.c ?? tests/toolchain/t09_local_init.c ?? tests/toolchain/t10_short.c ?? tests/toolchain/t11_array_size.c ?? tests/toolchain/t12_local_array_size.c ?? tests/toolchain/t13_struct_copy.c ?? tests/toolchain/t14_struct_return.c ?? tests/toolchain/t15_escapes.c
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "^## " docs/toolchain.md
9:## AS (assembler) 129:## CC (compiler) 259:## Kernel source adaptation (Phase T3) 293:## LD (linker, Phase T5) 423:## Bootstrap
415 final sum still came out right despite the underlying addresses being 416 wrong. It only surfaced when reading data written through a *different* 417 mechanism using real offsets - the kernel-provided `argv[]` array. 418 Fixed by extending `stmt_t.ops[]` to 3 slots and adding the proper 419 `imul r32, r/m32, imm32` encoding (`0x69 /r id`, always full imm32 - 420 same "no imm8 sign-extend optimization" convention the group1 ALU ops 421 already use). 422 423 ## Bootstrap 424 425 _To be filled in once Phase T8 lands: the Stage0/1/2/3 procedure and how 426 to re-verify Stage2 == Stage3._ 427
- ## Bootstrap
-
- _To be filled in once Phase T8 lands: the Stage0/1/2/3 procedure and how
- to re-verify Stage2 == Stage3._
+ ## Full switch-over (Phase T7)
+
+ The Makefile now builds `toolchain/{as,cc,ld}` from source with the
+ host's real `gcc` (`BOOTSTRAP_CC`, invoked exactly once, only on that
+ source - see `docs/toolchain.md`'s Bootstrap section below), then uses
+ those binaries for every kernel and host-tool source file. `AS`/`CC`/`LD`
+ point at `$(BUILD)/toolchain/{as,cc,ld}`; `HOSTCC` is `CC --target=host`.
+ Since CC never emits object files directly (only AS-syntax assembly
+ text), every C source now goes through two steps where gcc's `-c` used
+ to be one: `$(BUILD)/%.S: kernel/%.c` (CC) then `$(BUILD)/%.o:
+ $(BUILD)/%.S` (AS) - GNU Make's implicit-rule search resolves this
+ alongside the unrelated `$(BUILD)/%.o: kernel/%.S` rule for the two
+ hand-written assembly files (`entry.S`/`idt_stubs.S`) without conflict,
+ since the two rules' prerequisite directories never contain the same
+ stem. `kernel/builtins_shim.c` and `tools/host_syscalls_shim.c` are
+ gone - no longer referenced anywhere - since CC intercepts every
+ `__builtin_*`/`__sys_*` call site directly (see the CC section above).
+
+ Verified with the full existing test suite: `tools/run_tests.sh` (boot,
+ tinyfs mount, every shell command, the scripting language, the bytecode
+ compiler+VM) and `tools/run_toolchain_tests.sh`, both green, with zero
+ invocations of gcc/nasm/ld/objcopy anywhere in the OS build path.
+
+ Getting there surfaced a long tail of real bugs - unlike Phase T5's
+ handful, most of these were latent because nothing had ever exercised
+ the exact code pattern before (a kernel-only builtin, a language feature
+ only the scripting interpreter happened to use, an instruction only one
+ driver emitted). Each was found by an actual QEMU boot/GDB session
+ against real, previously-untested code, not by inspection:
+
+ - **AS - `lgdt`/`lidt` only supported one operand form**: hardcoded to
+ the 16-bit absolute-address form `boot/stage2.S` uses; `__builtin_gdt_flush`'s
+ `lgdt [ecx]` (register-indirect, 32-bit) silently mis-assembled.
+ Added the general `encode_rm32`-based `0F 01 /2` (`lgdt`) and `/3`
+ (`lidt`) form alongside the existing one.
+ - **AS - far jump (`jmp SEG:OFFSET`) always used a 16-bit offset**: in
+ 32-bit code (`__builtin_gdt_flush`'s CS-reload jump) this is 2 bytes
+ short of the real `ptr16:32` encoding, corrupting every byte after it.
+ Fixed by sizing the offset from `st->bits`, matching every other
+ 16-vs-32 branch in the encoder.
+ - **CC codegen - `walk_collect`'s extern-duplication fix had a second
+ bug**: temporarily NULLing a top-level node's `->next` to stop
+ re-walking the whole remaining chain (Phase T5's fix) also corrupted
+ `is_local`'s scan of `prog` *while that scan was still in progress*,
+ whenever it reached the currently-truncated node - any same-file
+ function called from code defined *earlier* in the file looked
+ "not local" and got a spurious `extern`, later colliding with its own
+ real definition. Replaced the mutation with `walk_collect_ex(...,
+ skip_next)`, which never touches the shared list at all.
+ - **CC codegen - indirect-call detection didn't exclude the extern-
+ function-name pass**: `block_read(...)` (call through a function-
+ pointer variable) still ran through the "not a locally-defined
+ function, so `extern` it" check, `extern`-declaring the *variable*
+ name - which then also got a real local `global` definition in the
+ same file, leaving AS unable to tell which one a `lea eax,
+ [block_read]` reference meant. Skipped for `is_indirect_call` calls.
+ - **CC codegen - file-scope `static` variables/functions were always
+ global**: `parse_global_var` never looked at the `static` keyword
+ (only `parse_func` did), so two files' own file-private `static int
+ foo;` would collide as duplicate global symbols. Added
+ `is_file_static`, mirroring the function case.
+ - **CC - `switch`/`case`/`default` weren't implemented at all**: not a
+ bug so much as a real gap Phase T2's dialect survey missed (used by
+ `kernel/lang/lexer.c`'s tokenizer switch, among others) - `switch`
+ parsed as an unknown identifier followed by a call, producing garbage.
+ Added as a flat list of case/default label markers inside the switch's
+ block (parsed the same as any other block), with a linear
+ compare-and-jump chain and a hidden per-switch frame slot holding the
+ switch value across the chain - naturally gives C's fallthrough
+ semantics for free, since falling off the end of one case's code just
+ runs into the next label in emission order.
+ - **CC - the comma operator was declared (`ND_COMMA` existed) but never
+ actually implemented** - `parse_expr` was just `parse_assign`, so a
+ for-loop's multi-clause increment (`i++, j--`) silently dropped
+ everything after the first comma, desyncing the parser from that point
+ on. Added real comma-operator parsing (distinct from comma-as-
+ separator in call args/declarators/initializers, which already call
+ `parse_assign` directly per element) and trivial codegen (evaluate
+ and discard the left side, keep the right).
+ - **CC - self-referential and forward-declared struct tags didn't
+ work**: a struct's own tag was only registered *after* its body was
+ parsed, so `struct node { struct node *next; }` failed to resolve its
+ own self-reference; and `typedef struct node node_t;` with no body
+ (forward declaration before the real definition, `kernel/lang/script/ast.h`'s
+ exact pattern) had no path but "error" at all. Fixed by registering
+ the tag before parsing the body (reusing the same `type_t` if one
+ already exists, so an earlier forward declaration's typedef still sees
+ the real member layout once the full definition arrives) and by
+ creating an incomplete tag on first forward-reference instead of
+ erroring.
+ - **CC - function-pointer *parameters* weren't parsed**:
+ `parse_declarator` already handled `T (*name)(params)` for ordinary
+ declarations, but `parse_param_list` had its own simpler inline
+ parameter parser that never checked for the parenthesized form (used
+ by `kernel/lang/compiler/parser.c`'s `parse_binop_level(parser_t *p,
+ ast_node_t *(*next)(parser_t *), ...)`). Added the same form directly
+ to the parameter-parsing loop.
+ - **AS - `movzx r32, r16` (register-to-register) had a spurious `0x66`
+ prefix**: legal-looking but wrong - the prefix reinterprets *both*
+ operands as 16-bit, silently downgrading `movzx eax, ax` to a
+ near-no-op `movzx ax, ax` instead of really zero-extending into the
+ full register. Never exercised before: every earlier `movzx` use was
+ the byte-from-memory form (`0F B6`); only CC's new `uint16_t` load path
+ (`mov ax, [eax]` then `movzx eax, ax`, added this phase - see below)
+ hit the register-to-register form (`0F B7`) at all.
+ - **CC codegen - no 16-bit (`short`/`uint16_t`) load/store support**:
+ `load_value`/`store_value` only distinguished byte (1) from
+ "everything else, 4 bytes" - a `uint16_t` array element write emitted
+ a plain 4-byte `mov`, silently overwriting the next element's first
+ two bytes on every store. This is exactly `kernel/ata.c`'s PIO loop
+ (`uint16_t *p = buf; p[i] = inw(ATA_DATA);`), which had never been
+ compiled by CC before. Added real 16-bit forms to both, plus the
+ matching `mov r16, r/m16` / `mov r/m16, r16` encodings in AS (only a
+ 16-bit-code, bare-label form existed before, for `boot/stage2.S`).
+ - **AS - `in`/`out` with a 16-bit accumulator (`in ax, dx` / `out dx,
+ ax`) also needed the `0x66` prefix and didn't have it** - `0xED`/`0xEF`
+ alone, in 32-bit code, means the *32-bit* accumulator form. Every
+ `inw()`/`outw()` call (ATA sector transfers, 256 words per sector) was
+ silently reading/writing **two** 16-bit port words per instruction
+ instead of one, packed into EAX with the low half kept and the high
+ half discarded by the caller's own `movzx` - draining twice as many
+ words off the device as intended and dropping every other one. This
+ produced the session's most confusing bug: correct-looking code,
+ correct-looking disassembly, and a superblock read that silently came
+ back with every other 16-bit word missing.
+ - **CC - `T name[] = { ... }` (array size inferred from its
+ initializer) left `array_len` at -1** in both the global
+ (`parse_global_var`) and local (`parse_one_local_decl`, needed for
+ function-local `static const T ops[] = {...}` tables) declaration
+ paths - `type_size` treats a non-positive `array_len` as size 0, so
+ `sizeof(name)` silently came out as 0. `kernel/keyboard.c`'s scancode
+ table used exactly this pattern for its `TABLE_LEN` bounds check
+ (`sizeof(lower_table)/sizeof(lower_table[0])`); with the check always
+ seeing `TABLE_LEN == 0`, `code >= TABLE_LEN` was vacuously true for
+ every scancode, so `keyboard_getchar` detected every keypress
+ correctly but silently discarded all of them instead of ever
+ returning one. Fixed by counting the initializer list's elements once
+ parsing reaches it, in both places.
+ - **CC - struct/union-by-value wasn't supported at all**: assignment
+ (`a = b;`), a non-brace initializer (`T save = *p;` -
+ `kernel/lang/script/parser.c`'s assignment-lookahead backtracking:
+ `parser_t save = *p; ... *p = save;`), function return
+ (`kernel/lang/script/eval.c`'s `mkint`/`mkstr` constructors), and
+ by-value parameters (`print_value(value_t v)`) all silently used the
+ scalar 4-byte `mov` path, corrupting or truncating anything bigger
+ than a pointer. This was the session's deepest fix: `load_value` now
+ leaves a struct/union as its own address instead of dereferencing it
+ (the same "decay" `TY_ARRAY` already got), `store_value` gained a
+ byte-copy loop (`codegen_struct_copy` - AS has no `rep movsb`, so it's
+ a plain indexed compare-and-loop like every other loop this codegen
+ emits) for struct/union targets, function returns use the classic
+ hidden-pointer convention (caller allocates a temp slot, passes its
+ address as an implicit first argument at `[ebp+8]`, shifting every
+ real parameter's offset out by 4 - `parse_func`/`ND_RETURN`/
+ `codegen_call_or_builtin` all coordinate on this), and by-value
+ parameters are copied onto the stack at the call site instead of
+ having their address pushed, with `parse_func`'s parameter-offset
+ loop sized per-parameter instead of assuming a flat 4 bytes. (AS's
+ `MAX_STMTS`/`MAX_SYMS`/`MAacross_RELOCS` also had to grow - the
+ struct-copy loop's handful of instructions per copy added up fast in
+ `eval.c`, which does little else.)
+ - **CC lexer - `read_escape` had no case for `\b` (or `\f`/`\v`/`\a`)**:
+ fell through to the `default: return c;` branch, so `'\b'` silently
+ lexed to the *letter* `'b'` (0x62) instead of backspace (0x08). This
+ broke two unrelated things built from the same character constant:
+ `kernel/keyboard.c`'s scancode table entry for the real Backspace key
+ (now mapped to the letter b), and - far more visibly -
+ `kernel/shell/shell.c`'s `read_line`'s `if (c == '\b')` check, which
+ became `if (c == 'b')`: typing the *letter* b anywhere in a shell
+ command made `read_line` treat it as a backspace keypress and erase
+ the previous character instead of appending 'b'. Found via the exact
+ symptom: every shell command in the test suite worked except the one
+ containing a 'b' (`build hello.tc hello.tbc`), consistently, regardless
+ of keystroke timing - which ruled out a timing race and pointed at the
+ character itself.
+
+ ## Bootstrap
+
+ _To be filled in once Phase T8 lands: the Stage0/1/2/3 procedure and how
+ to re-verify Stage2 == Stage3._The file /home/martin/workspace/prototypes/boot-to-claude/docs/toolchain.md has been updated successfully. (file state is current in your context — no need to Read it back)
This session is being continued from a previous conversation that ran out of context. The summary below covers the earlier portion of the conversation.
Summary:
1. Primary Request and Intent:
This is a long-running project building a from-scratch tiny OS ("boot-to-claude" at `/home/martin/workspace/prototypes/boot-to-claude`) for QEMU/KVM with the constraint that 100% of the code is Claude-authored. The overarching multi-phase plan (documented at `/home/martin/.claude/plans/swirling-mixing-bee.md`) is to build a self-hosted toolchain (AS assembler, CC compiler, LD linker — OBJCOPY was retired/folded into LD in the immediately preceding turn) that can build the entire OS without gcc/nasm/ld/objcopy.
Prior turns (already completed and committed): Phase T1 (AS), T2/T4 (CC), T3 (kernel source adaptation for CC), T5 (LD), and the OBJCOPY retirement (folded into LD as `--format bin`).
**This turn's sole, explicit user instruction: "Do Phase T7."** — Phase T7 is "Full switch-over": point the Makefile's AS/CC/LD/HOSTCC variables at the self-built tool binaries, rebuild the entire OS (bootloader, kernel, mkfs_tinyfs) using only the self-hosted toolchain, with host gcc/nasm/ld/objcopy no longer invoked anywhere in the OS build path (only used once to bootstrap the toolchain itself). The plan's stated verification bar: `tools/run_tests.sh` green against the fully self-tool-built disk image. This is consistent with the project's established rigor throughout all prior phases: every phase must be verified via actual execution (QEMU boot, real command-line testing), not just "compiles clean," and real bugs found via testing must be fixed, not worked around.
No other explicit user messages occurred in this turn — this was a single, long, autonomous execution arc in response to "Do Phase T7," involving extensive debugging (GDB, QEMU monitor scripting, manual test harnesses) to find and fix numerous real bugs uncovered only by actually booting and interactively testing the self-hosted-toolchain-built OS.
2. Key Technical Concepts:
- GNU Make mechanics: `:=` immediate variable expansion (order-sensitive — `BUILD` must be defined before variables that reference it), order-only prerequisites (`|`) for build-tool bootstrapping, implicit/pattern rule disambiguation for chained `.c→.S→.o` compilation alongside hand-written `.S→.o` assembly (GNU Make correctly resolves this via prerequisite satisfiability, since the stem-sets for hand-written `.S` files and CC-compiled `.c` files are disjoint in this codebase).
- x86-32 protected-mode kernel dev: PS/2 keyboard controller (ports 0x60/0x64) polling, i8042 status-register bit 0 ("output buffer full"), ATA PIO-mode 16-bit word transfers via port 0x1F0, GDT/IDT setup, far jumps for CS reload (`jmp SEG:OFFSET`, opcode `0xEA`, 16-bit-vs-32-bit offset width distinction), x86 operand-size override prefix `0x66` (critical for `IN AX,DX`/`OUT DX,AX`/`MOVZX r32,r16` correctness — a 32-bit-mode `IN`/`OUT` without the prefix operates on the full 32-bit register/EAX, silently consuming double the intended port data).
- C-to-assembly codegen: cdecl calling convention, hidden-pointer ("sret") ABI for struct-by-value function returns (caller allocates space, passes its address as an implicit first parameter; callee copies into it and returns that same address in EAX), struct-by-value parameter passing (caller copies the struct's bytes onto the stack rather than passing a pointer), array-size inference for `T name[] = {...}` (must be computed from the initializer element count at parse time, not left as a sentinel), designated initializers (unsupported — worked around by refactoring source), the comma operator (distinct precedence from parse_assign, must be added at `parse_expr` only, not everywhere comma appears as a separator), switch/case/default with fallthrough semantics (implemented as a flat statement list with jump-chain codegen, sharing loop_ctx-style break/continue tracking), self-referential and forward-declared struct tags (tag must be registered in the symbol table before/independent of full body definition).
- Two-pass assembler design invariants: `stmt_encode` is called once in a dry-run pass (length-only) and once for real emission — any encoding bug is automatically consistent across both passes since they share the same function, but any *feature gap* (e.g., missing operand forms) affects both equally too.
- Symbol linkage nuances: numbered-per-file synthetic labels (`__str_N`, `__static_N`) must never be marked `global` (collide across files since numbering resets per translation unit); `extern` variable declarations must never receive their own storage AND must be explicitly declared `extern` in the emitted assembly (AS silently drops relocations against names with no local definition and no `extern` directive); file-scope `static` variables need internal linkage distinct from ordinary globals (same collision risk as `__str_N` if treated as global).
- GDB remote debugging against QEMU's gdbstub (`-s -S` / `-s` without `-S`), reading raw port I/O state via QEMU's HMP monitor (`i /b PORT`) as a (found to be unreliable/non-representative) way to probe hardware state independent of guest code, breaking at computed addresses derived by cross-referencing `nm`/`readelf`/`objdump` output against a debug-only ELF link (`write_flat_elf` used instead of `write_flat_bin` specifically to get symbols for debugging, since the production kernel.bin has none).
- Root-causing "keyboard doesn't work" via a debug-instrumentation-in-kernel-source technique (temporarily adding `serial_write`+`itoa` calls inside `keyboard_getchar`, rebuilding, observing the real polled register/scancode values via the serial log — far more reliable than external GDB/monitor probing for this class of bug) — reverted before finishing.
3. Files and Code Sections:
**`Makefile`** (fully rewritten) — the core deliverable of Phase T7. Key structure (BUILD must be defined first, due to `:=` immediate expansion):
```makefile
BUILD := build
BOOTSTRAP_CC := gcc # one-time host compiler, only builds toolchain/{as,cc,ld}
AS := $(BUILD)/toolchain/as
CC := $(BUILD)/toolchain/cc
LD := $(BUILD)/toolchain/ld
HOSTCC := $(BUILD)/toolchain/cc --target=host
QEMU := qemu-system-i386
CFLAGS := -Ikernel -Itoolchain/include
HOSTCFLAGS := -Ikernel -Itools -Itoolchain/include
```
`KERNEL_C_SRCS` no longer includes `kernel/builtins_shim.c`; `MKFS_OBJS` no longer includes `host_syscalls_shim.o`. Bootstrap rules build `$(BUILD)/toolchain/{as,cc,ld}` via `$(BOOTSTRAP_CC) -Wall -Wextra -O2 -o $@ $(wildcard toolchain/{as,cc,ld}/*.c)`. Two-step compile chain:
```makefile
$(BUILD)/%.o: kernel/%.S | $(BUILD_SUBDIRS) $(AS)
$(AS) -f elf32 -o $@ $<
$(BUILD)/%.S: kernel/%.c | $(BUILD_SUBDIRS) $(CC)
$(CC) $(CFLAGS) -o $@ $<
$(BUILD)/%.o: $(BUILD)/%.S | $(BUILD_SUBDIRS) $(AS)
$(AS) -f elf32 -o $@ $<
```
Kernel link: `$(LD) -T kernel/link.ld --format bin -o $@ $(KERNEL_OBJS)` (direct to `kernel.bin`, no ELF/objcopy step). Host tool objects via `$(HOSTCC)` then `$(AS)`; `$(MKFS_BIN)` via `$(LD) -o $@ $(MKFS_OBJS)` (static_exec, no `-T`). `BUILD_SUBDIRS` includes `$(BUILD)/toolchain`.
**`toolchain/cc/parser.c`** — extensively modified, the single most-changed file:
- Self-referential struct fix: tag registered into `p->tags` BEFORE `parse_struct_members(p)` is called (was after), and reuses `existing->type` if the tag was already forward-declared.
- Forward-declared struct/union tag branch: when no body and no existing tag, creates an incomplete `type_t` and registers it (for `typedef struct ast_node ast_node_t;`-style patterns).
- `parse_param_list`: added a branch detecting `(` followed by `*` to parse function-pointer parameters like `ast_node_t *(*next)(parser_t *)` inline (duplicated logic rather than delegating to `parse_declarator`, since plain params tolerate being unnamed).
- `parse_expr`: changed from `return parse_assign(p);` to a loop implementing the comma operator, building `ND_COMMA` nodes on `,`, careful this ONLY affects `parse_expr` (call-arg/declarator/initializer-list parsing already uses `parse_assign` directly).
- `switch`/`case`/`default` parsing added to `parse_stmt`: `switch (EXPR) { ... }` parses the expression, allocates a hidden frame slot (`n->frame_offset`, via `p->frame_size` bump) for the switch value, then parses the body as an ordinary `parse_block` (case/default are zero-width label statements, not statement-owning constructs, so the block's normal statement-list parsing naturally produces a flat list mixing markers and real statements — this yields correct fallthrough for free).
- `parse_global_var(parser_t *p, const char *name, type_t *t, int is_extern, int is_static)` — signature extended with a new `is_static` parameter (both call sites updated); sets `n->is_file_static = is_static;`. Also added array-size inference: `if (t->kind == TY_ARRAY && t->array_len < 0 && n->init && n->init->kind == ND_BLOCK) { count n->init->list elements; t->array_len = count; }`.
- `parse_one_local_decl`: same array-size-inference fix added, for `static const T ops[] = {...}` locals.
- `parse_func`: parameter offset computation now shifts by the hidden struct-return pointer when applicable:
```c
int returns_struct = functy->base->kind == TY_STRUCT || functy->base->kind == TY_UNION;
type_t *pt = functy->param;
int off = returns_struct ? 12 : 8;
...
for (...) {
...
if (pt->kind == TY_STRUCT || pt->kind == TY_UNION) {
int sz = (type_size(pt) + 3) & ~3;
off += sz < 4 ? 4 : sz;
} else {
off += 4;
}
}
```
- `parse_primary`'s ND_CALL construction: after finalizing `call->type` (including the `is_indirect_call` branch), added:
```c
if (call->type->kind == TY_STRUCT || call->type->kind == TY_UNION) {
p->frame_size += type_size(call->type);
p->frame_size = (p->frame_size + 3) & ~3;
call->num = -p->frame_size;
}
```
(uses `call->num`, not `call->frame_offset`, since the latter is already used by `is_indirect_call` for the callee-variable's own location.)
**`toolchain/cc/codegen.c`** — extensively modified:
- `is_short_type(type_t *t)` helper added; `load_value`/`store_value` gained TY_SHORT branches (`mov ax,[eax]; movzx eax,ax` / `mov [ecx],ax`).
- `load_value` gained: `if (t->kind == TY_STRUCT || t->kind == TY_UNION) return;` (struct rvalues decay to their address, like arrays).
- New function `codegen_struct_copy(type_t *t)`: assumes EAX=src addr, ECX=dst addr; emits a byte-copy loop using `edx` as a register counter (deliberately avoiding stack-slot-based counting to sidestep uncertainty about AS's `DWORD [mem]` size-hint support):
```c
static void codegen_struct_copy(type_t *t) {
char ltop[MAX_NAME], lend[MAX_NAME];
new_label(ltop); new_label(lend);
out(" push eax\n"); /* [esp+4] = src */
out(" push ecx\n"); /* [esp] = dst */
out(" mov edx, 0\n");
out("%s:\n", ltop);
out(" cmp edx, %d\n", type_size(t));
out(" jge %s\n", lend);
out(" mov eax, [esp+4]\n");
out(" add eax, edx\n");
out(" movzx eax, byte [eax]\n");
out(" mov ecx, [esp]\n");
out(" add ecx, edx\n");
out(" mov [ecx], al\n");
out(" add edx, 1\n");
out(" jmp %s\n", ltop);
out("%s:\n", lend);
out(" add esp, 8\n");
}
```
- `store_value`: added `if (t->kind == TY_STRUCT || t->kind == TY_UNION) { codegen_struct_copy(t); }` branch before the byte/short/default cases.
- `walk_collect_ex(node_t *n, node_t *prog, int skip_next)` — renamed/refactored from the old `walk_collect`; the top-level driver in `codegen_emit` now passes `skip_next=1` instead of the old temporary-null-restore-of-`n->next` hack (which corrupted the AST during `is_local` scans). Also added `&& !n->is_indirect_call` to the `ND_CALL` extern-collection condition, since indirect-call callee names are variables, not functions, and shouldn't be treated as possibly-external function symbols.
- `codegen_emit`: added a new pass scanning `prog` for `ND_VAR_DECL && n->is_extern_decl` nodes, adding their names to `g_externs[]` (mirroring the existing call-based extern collection) — fixes `extern uint32_t isr_stub_table[32];` needing an `extern` directive.
- `.data`/`.bss` emission loops: filtered out `is_extern_decl` nodes (no storage for pure declarations); made the `global` directive conditional on `!n->is_file_static` for real globals, and unconditionally removed `global` for `__str_N`/`__static_N` synthetic labels.
- `codegen_call_or_builtin`: extended to (a) push struct/union arguments by copying their bytes onto the stack via `codegen_struct_copy` (using `sub esp,sz` then `mov ecx,esp` as dst) instead of pushing a 4-byte address; (b) track `total_bytes` (not `argc*4`) for correct stack cleanup; (c) when `n->type` is struct/union, push `&hidden_slot` (`lea eax,[ebp+n->num]`) as an implicit last-pushed (first-from-callee) argument.
- `ND_RETURN` case: when `n->a->type` is struct/union, evaluates `n->a` (yields src address), loads dest from `[ebp+8]` into `ecx`, calls `codegen_struct_copy`, reloads `eax=[ebp+8]` before the epilogue (since the copy clobbers eax/ecx/edx).
**`toolchain/cc/lexer.c`** — `read_escape()` gained missing cases:
```c
case 'b': return '\b';
case 'f': return '\f';
case 'v': return '\v';
case 'a': return '\a';
```
(Previously fell through to `default: return c;`, making `'\b'` literally equal `'b'` — root cause of the final keyboard-input bug, corrupting both `kernel/keyboard.c`'s `lower_table[14]` and `kernel/shell/shell.c`'s `read_line()`'s `if (c == '\b')` backspace-detection check.)
**`toolchain/cc/cc.h`** — added `int is_file_static;` field to `struct node` (documented as: file-scope `static T name;` still needs storage via `is_global` but must not get a `global` directive, to avoid cross-file symbol collisions).
**`toolchain/as/as.h`** — bumped fixed limits (hit `AS: too many statements` after struct-copy codegen expansion in `eval.c`):
```c
#define MAX_SYMS 4096 /* was 512 */
#define MAX_STMTS 32768 /* was 4096 */
#define MAX_RELOCS 4096 /* was 256 */
```
**`toolchain/as/encode.c`** — multiple real encoding bugs fixed:
- `lgdt`/`lidt` split into a 16-bit branch (kept the old hardcoded direct-address form for boot/stage2.S) and a new 32-bit branch using `encode_rm32` with reg_field 2 (lgdt) / 3 (lidt), for register-indirect forms like `lgdt [ecx]` used by `__builtin_gdt_flush`.
- `OPND_FAR` (`jmp SEG:LABEL`) handler: now checks `st->bits` and emits a 32-bit offset (`e32`) in 32-bit code instead of always emitting 16-bit (`e16`), fixing a corrupting bug in `__builtin_gdt_flush`'s CS-reload far jump.
- `in`/`out` AX-form encoding: added the missing `0x66` operand-size prefix for `IN AX,DX`/`OUT DX,AX` (was emitting the 32-bit `IN EAX,DX`/`OUT DX,EAX` form unprefixed, silently draining two 16-bit words per port access instead of one), plus explicit `R_EAX` (no-prefix, 32-bit) branches for completeness.
- `movzx r32, r16` (register-to-register form): removed an erroneous leading `0x66` prefix that was downgrading it to a near-no-op `movzx r16,r16`.
- Added new 32-bit-mode `mov r16, r/m16` (`66 8B /r`) and `mov r/m16, r16` (`66 89 /r`) forms using `encode_rm32`, needed for CC's new TY_SHORT load/store codegen.
**`kernel/lang/script/eval.c`** — `VAL_ZERO` changed from `{ VAL_INT, { .i = 0 } }` (designated initializer, unsupported by CC) to `{ VAL_INT, { 0 } }` (positional init of the union's first member — verified via `grep` that VAL_ZERO is only ever read as a generic zero sentinel, never specifically via `.s`).
**`kernel/keyboard.c`** — temporarily modified with debug instrumentation (serial_write/itoa prints of raw status/scancode values inside `keyboard_getchar`'s polling loop) during investigation, then FULLY REVERTED to the original file content via an explicit `Write` call; confirmed clean via `git status` showing no diff for this file.
**`tools/run_toolchain_tests.sh`** — `EXPECTED` associative array extended with ten new entries: `t06_switch=139`, `t07_comma=53`, `t08_forward_struct=6`, `t09_local_init=81`, `t10_short=6`, `t11_array_size=155`, `t12_local_array_size=4`, `t13_struct_copy=212`, `t14_struct_return=44`, `t15_escapes=158`.
**New test files created (`tests/toolchain/`)**, each with a specific expected exit code registered above:
- `t06_switch.c` — switch/case/default with fallthrough, plus break/continue interaction with an enclosing loop (expected 139).
- `t07_comma.c` — comma operator in a for-loop increment and standalone (expected 53).
- `t08_forward_struct.c` — forward-declared typedef + self-referential struct member, linked-list sum (expected 6).
- `t09_local_init.c` — local scalar/array/nested-struct brace initialization (expected 81).
- `t10_short.c` — `unsigned short` array load/store correctness (expected 6).
- `t11_array_size.c` — file-scope `T arr[] = {...}` size inference (expected 155).
- `t12_local_array_size.c` — function-local `static const T ops[] = {...}` size inference (expected 4).
- `t13_struct_copy.c` — whole-struct assignment and non-brace copy-initialization (expected 212).
- `t14_struct_return.c` — struct-by-value function return AND struct-by-value parameter passing (`make()`/`identity()`) (expected 44).
- `t15_escapes.c` — `'\b'`/`'\t'`/`'\n'`/`'\\'`/`'\''` escape sequence values (expected 158).
**`docs/toolchain.md`** — NOT yet updated for Phase T7 (still ends with `## Bootstrap` stub saying "_To be filled in once Phase T8 lands..._"). This was the file I was reading (lines 415-427) when the summary was requested, planning where to insert a new T7 section.
**`/home/martin/.claude/plans/swirling-mixing-bee.md`** — NOT yet updated to mark T7 as done (still shows the original T7 phase description; needs updating to reflect completion and the bugs found, matching the pattern used for T5/T6 in prior turns).
4. Errors and fixes:
All of the following were REAL bugs discovered only by actually building and running the self-hosted-toolchain-built OS (per the project's established verification rigor):
- **`lgdt`/`lidt` unsupported for 32-bit register-indirect form**: AS only had a hardcoded 16-bit absolute-address `lgdt` (for boot/stage2.S) and no `lidt` at all. Fixed by adding a 32-bit branch using `encode_rm32`.
- **`OPND_FAR` far-jump offset always 16-bit**: corrupted `__builtin_gdt_flush`'s CS-reload jump in 32-bit code, causing a #GP fault. Fixed by branching on `st->bits`.
- **`__str_N`/`__static_N` synthetic labels marked `global`**: cross-file numbering collisions (`duplicate symbol '__str_0'`). Fixed by never marking them `global`.
- **`extern` variable declarations getting real storage AND no `extern` directive**: `isr_stub_table` duplicated across files. Fixed by filtering `is_extern_decl` from `.data`/`.bss` emission and adding a variable-extern-collection pass.
- **File-scope `static` variables treated as fully global**: cross-file collisions (e.g., `gdt`, later `block_read`/`block_write`). Fixed via new `is_file_static` field threaded through `parse_global_var`.
- **`walk_collect_ex`'s temporary-AST-mutation quadratic-duplication fix (from a prior turn) corrupting `is_local` scans mid-traversal**: caused forward-referenced same-file functions to be spuriously marked `extern`. Fixed by replacing the mutation hack with a proper `skip_next` parameter.
- **`walk_collect_ex` treating indirect-call callee names as function names**: caused both `extern X` and `global X` for the same variable in the same file. Fixed by excluding `is_indirect_call` nodes from that check.
- **`in ax,dx`/`out dx,ax` missing `0x66` prefix**: silently drained two 16-bit ATA data-port words per call, discarding every other one — root cause of `tinyfs: mount FAILED` (corrupted superblock read). Fixed by adding the prefix and an explicit `R_EAX` (unprefixed) branch.
- **16-bit (`TY_SHORT`) load/store entirely unimplemented in CC codegen**: `uint16_t*` array stores were 4-byte instead of 2-byte, doubling every write. Fixed via `is_short_type` + new load_value/store_value branches, plus new AS `mov r16,r/m16`/`mov r/m16,r16` 32-bit forms.
- **Pre-existing `movzx r32,r16` (reg-to-reg) had an erroneous `0x66` prefix**, silently downgrading it to a near-no-op — never previously exercised since only the byte-from-memory form had been used. Fixed by removing the prefix.
- **Unsized array `T name[] = {...}` never had its length inferred from the initializer** (`array_len` stayed at the sentinel -1, treated as size 0 by `type_size`), making `sizeof(lower_table)`-based `TABLE_LEN` evaluate to 0 in `kernel/keyboard.c`, making `code >= TABLE_LEN` always true, silently discarding every real keypress forever — root cause of "keyboard doesn't work at all" despite scancodes being correctly detected. Fixed in both `parse_global_var` and `parse_one_local_decl` (the latter needed for `kernel/lang/*/parser.c`'s operator tables). Hit a comment-authoring gotcha along the way: writing a comment containing the literal sequence `*/` (from "kernel/lang/*/parser.c") prematurely closed the enclosing C block comment, breaking compilation in both `toolchain/cc/parser.c` and the newly-created `t12_local_array_size.c` test file — fixed by rewording.
- **Struct-by-value assignment/copy entirely unsupported**: `parser_t save = *p; ... *p = save;` (used for parser backtracking in `kernel/lang/script/parser.c`) silently copied only 4 of ~90+ bytes, corrupting parser state on every failed assignment-lookahead — root cause of `run hello.tsh` failing with "script: parse error: unexpected token". Fixed via `codegen_struct_copy` + `load_value`/`store_value` struct branches.
- **Struct-by-value function return unsupported**: after the copy fix, discovered `value_t`-returning functions (`mkint`/`mkstr` in `eval.c`) returned dangling addresses into deallocated stack frames. Fixed via the hidden-pointer ("sret") ABI implemented across `parse_func`, `parse_primary`'s ND_CALL construction, `codegen_call_or_builtin`, and `ND_RETURN`.
- **Struct-by-value function parameter passing unsupported**: `print_value(value_t v)` in `eval.c` needed this. Fixed via real-size parameter offsets in `parse_func` and stack-copy argument passing in `codegen_call_or_builtin`.
- **`AS: too many statements`**: struct-copy loop expansion exceeded AS's fixed `MAX_STMTS`/`MAX_SYMS`/`MAX_RELOCS`. Fixed by bumping all three limits generously.
- **`\b` escape sequence lexed as literal `'b'`**: `read_escape()` had no case for `'b'`, corrupting `kernel/keyboard.c`'s backspace-scancode table entry AND (more impactfully) `kernel/shell/shell.c`'s `if (c == '\b')` backspace-detection check into `if (c == 'b')`, causing every typed letter 'b' in shell commands to be silently misinterpreted as a backspace keypress — root cause of `build hello.tc hello.tbc` being garbled to `uild hello.tc hello.tb bc`. Confirmed via the observation that ALL passing test commands contained zero occurrences of 'b' while the one failing command contained it twice; also ruled out a timing/race explanation by testing with a 6x-longer inter-keystroke delay (still failed identically) before finding the real cause. Fixed by adding `case 'b': return '\b';` (plus `f`/`v`/`a` for completeness).
5. Problem Solving:
All problems described in section 4 were fully solved and verified. The overall arc was: (1) pre-flight `--parse-only` survey across kernel sources found and fixed five parser-level gaps (self-referential structs, forward-declared structs, function-pointer parameters, designated initializers, comma operator — plus switch/case/default support, which appears to have been added in this same session's early phase); (2) wrote the new self-hosted Makefile; (3) iterative build-boot-test-fix loop uncovered and fixed twelve+ additional, deeper bugs spanning both AS (encoding correctness: lgdt/lidt, far-jmp width, in/out AX prefix, movzx prefix) and CC (symbol linkage: static/extern/synthetic-label handling, AST-corruption in extern-collection, array-size inference, and the large struct-by-value feature set: copy/return/parameter-passing) plus one CC lexer bug (`\b` escape). Each bug was root-caused via direct evidence (disassembly byte inspection, GDB register/memory tracing, controlled A/B comparison against the pre-T7 gcc/nasm build in a separate `/tmp/old_test` checkout, and — most decisively for the final keyboard bug — temporary debug instrumentation compiled directly into the kernel to observe real runtime values via the serial log) rather than speculation, consistent with the project's established debugging discipline. Every fix was accompanied by a new, minimal, targeted regression test added to `tests/toolchain/` and registered in `run_toolchain_tests.sh`, and the toolchain test battery was kept green (currently 15/15 passing) throughout. The final state: **both `tools/run_toolchain_tests.sh` and `tools/run_tests.sh` pass cleanly** on a fresh `make clean && make` using ONLY the self-hosted toolchain — this is the Phase T7 plan's explicit stated verification bar, now met.
6. All user messages:
- "Do Phase T7." — the single, sole explicit instruction for this entire turn. No other user messages occurred; the rest of this turn was autonomous execution, debugging, and verification in response to this one instruction.
7. Pending Tasks:
- Write a new Phase T7 section in `docs/toolchain.md` (currently ends at `## Bootstrap`, a stub for Phase T8), documenting the Makefile switchover and the full list of bugs found and fixed during this phase, matching the documentation style/rigor already established for T1–T5 (and the OBJCOPY retirement) in that same file.
- Update the plan file at `/home/martin/.claude/plans/swirling-mixing-bee.md` to mark Phase T7 as done, following the same pattern used when T5 and T6 were marked complete in prior turns.
- Mark task #15 ("Phase T7 — Full switch-over") as `completed` via TaskUpdate (currently still shown as `[pending]` in the task list; note task #14, T6/OBJCOPY, was already deleted in a prior turn).
- Commit all of Phase T7's changes (the rewritten `Makefile`; all modified toolchain/CC/AS source files; the `kernel/lang/script/eval.c` VAL_ZERO fix; the ten new `tests/toolchain/t06–t15` files; `tools/run_toolchain_tests.sh`'s updated EXPECTED table; and the docs/plan updates once written) with a detailed commit message enumerating the bugs found, matching the style of prior phase commits (e.g., the T5 and T6 commit messages, which itemized each real bug discovered during verification).
- **Not yet requested by the user, part of the original plan but explicitly out of scope until asked** (per the established "complete phase → report → wait for next instruction" pattern that has held throughout this whole multi-turn project): Phase T8 (self-hosting bootstrap — 3-stage Stage0/1/2/3 verification) and Phase T9 (final docs + cleanup).
8. Current Work:
Immediately before this summary was requested, Phase T7's actual implementation and verification work was **fully complete and passing** — the last substantive action was re-running both test suites one final time to confirm consistency (`./tools/run_toolchain_tests.sh` → all 15 tests PASS; `timeout 90 ./tools/run_tests.sh` → `run_tests: PASS`), then cleaning up scratch files in `/tmp` (numerous GDB/QEMU debugging artifacts and the `/tmp/old_test` comparison checkout), then running `git status --short` to confirm the exact set of modified/new files (listed in full in section 3 above).
I had then begun the **documentation phase**: I ran `grep -n "^## " docs/toolchain.md` to see the file's section structure (`## AS (assembler)`, `## CC (compiler)`, `## Kernel source adaptation (Phase T3)`, `## LD (linker, Phase T5)`, `## Bootstrap`), then read the tail of the file (lines 415-427) to see the exact current content of the `## Bootstrap` stub section:
```
## Bootstrap
_To be filled in once Phase T8 lands: the Stage0/1/2/3 procedure and how
to re-verify Stage2 == Stage3._
```
This was the very last tool output received before the summary request — I had not yet written any new documentation content, not yet decided exactly where the new "## Full switch-over (Phase T7)" section should be inserted relative to `## Bootstrap` (logically it should go BEFORE `## Bootstrap`, since Bootstrap is Phase T8's future content), and had not yet drafted any of its text.
9. Optional Next Step:
Continue directly from where I was cut off: write a new documentation section in `docs/toolchain.md` (inserted before the existing `## Bootstrap` stub, likely titled something like `## Full switch-over (Phase T7)`) covering: the Makefile's new structure (BOOTSTRAP_CC bootstrap step, AS/CC/LD/HOSTCC pointing at self-built binaries, the two-step `.c→.S→.o` compile chain, direct-to-flat-binary kernel linking, host-tool build via HOSTCC+AS+LD), and a itemized list of the bugs found during T7's verification (mirroring the LD section's existing style of "That last check surfaced N real bugs..." bullet list) — covering AS encoding bugs (lgdt/lidt, far-jmp width, in/out AX prefix, movzx prefix) and CC bugs (symbol-linkage issues, AST-corruption in extern collection, array-size inference, struct-by-value support, the `\b` escape sequence bug). This directly continues the exact task in progress when interrupted: I had just read `docs/toolchain.md`'s tail (lines 415-427) specifically to find the insertion point for this new section, per my own immediately-preceding action. After the docs are written, the remaining pending tasks (update the plan file, mark task #15 complete via TaskUpdate, and commit with a detailed message) should follow directly, matching the exact pattern already established and executed for Phase T5 and the T6/OBJCOPY retirement in the immediately preceding turns of this same session.
If you need specific details from before compaction (like exact code snippets, error messages, or content you generated), read the full transcript at: /home/martin/.claude/projects/-home-martin-workspace-prototypes-boot-to-claude/03468d1c-5b29-4e24-8f0d-2a7435d227ab.jsonl
Continue the conversation from where it left off without asking the user any further questions. Resume directly — do not acknowledge the summary, do not recap what was happening, do not preface with "I'll continue" or similar. Pick up the last task as if the break never happened.- **Phase T7 - Full switch-over** - Makefile's `AS`/`CC`/`LD`/`HOSTCC` variables point at the newly built - tool binaries (`HOSTCC` = `CC --target=host`; `LD` invoked with - `--format bin` for the kernel image). Rebuild the entire OS - boot - loader, kernel, `mkfs_tinyfs` - using only our own tools, with host - gcc/nasm/ld/objcopy no longer invoked anywhere in the OS build path (only - used to have built the toolchain itself, one directory up). Verify: - `tools/run_tests.sh` green against the fully self-tool-built disk image. + **Phase T7 - Full switch-over** - done + Makefile's `AS`/`CC`/`LD`/`HOSTCC` variables point at the newly built + tool binaries (`HOSTCC` = `CC --target=host`; `LD` invoked with + `--format bin` for the kernel image). Rebuild the entire OS - boot + loader, kernel, `mkfs_tinyfs` - using only our own tools, with host + gcc/nasm/ld/objcopy no longer invoked anywhere in the OS build path (only + used to have built the toolchain itself, one directory up). Verified: + `tools/run_tests.sh` and `tools/run_toolchain_tests.sh` both green + against a fresh `make clean && make` using only the self-hosted + toolchain. Getting there surfaced a long tail of real bugs - deeper and + more numerous than T5's - since most of the codebase's less-common C + patterns and a few AS instruction forms had never actually been + exercised through the new toolchain before (self-referential/forward- + declared structs, function-pointer parameters, the comma operator, + `switch`/`case`/`default`, struct-by-value copy/return/parameter-passing, + 16-bit load/store, file-scope `static` linkage, extern-variable + handling, `lgdt`/`lidt`/far-jmp/`in`/`out`/`movzx` encoding bugs, and a + lexer bug where `'\b'` lexed to the letter `b` and silently turned every + typed 'b' in a shell command into a backspace keypress). Full list in + `docs/toolchain.md`'s "Full switch-over (Phase T7)" section.
The file /home/martin/.claude/plans/swirling-mixing-bee.md has been updated successfully. (file state is current in your context — no need to Read it back)
{
"query": "select:TaskUpdate,TaskList",
"max_results": 5
}{}#9 [completed] Phase T1 — AS (assembler) #10 [completed] Phase T2 — CC front end #11 [completed] Phase T3 — Kernel source adaptation #12 [completed] Phase T4 — CC codegen + builtins #13 [completed] Phase T5 — LD (linker) #15 [pending] Phase T7 — Full switch-over #16 [pending] Phase T8 — Self-hosting bootstrap #17 [pending] Phase T9 — Docs + cleanup
{
"taskId": "15",
"status": "completed"
}Updated task #15 status
$ git status --short
M Makefile M docs/toolchain.md M kernel/lang/script/eval.c M toolchain/as/as.h M toolchain/as/encode.c M toolchain/cc/cc.h M toolchain/cc/codegen.c M toolchain/cc/lexer.c M toolchain/cc/parser.c M tools/run_toolchain_tests.sh ?? tests/toolchain/t06_switch.c ?? tests/toolchain/t07_comma.c ?? tests/toolchain/t08_forward_struct.c ?? tests/toolchain/t09_local_init.c ?? tests/toolchain/t10_short.c ?? tests/toolchain/t11_array_size.c ?? tests/toolchain/t12_local_array_size.c ?? tests/toolchain/t13_struct_copy.c ?? tests/toolchain/t14_struct_return.c ?? tests/toolchain/t15_escapes.c
$ git add -A -- Makefile docs/toolchain.md kernel/lang/script/eval.c toolchain/as/as.h toolchain/as/encode.c toolchain/cc/cc.h toolchain/cc/codegen.c toolchain/cc/lexer.c toolchain/cc/parser.c tools/run_toolchain_tests.sh tests/toolchain/ && git status --short
M Makefile M docs/toolchain.md M kernel/lang/script/eval.c A tests/toolchain/t06_switch.c A tests/toolchain/t07_comma.c A tests/toolchain/t08_forward_struct.c A tests/toolchain/t09_local_init.c A tests/toolchain/t10_short.c A tests/toolchain/t11_array_size.c A tests/toolchain/t12_local_array_size.c A tests/toolchain/t13_struct_copy.c A tests/toolchain/t14_struct_return.c A tests/toolchain/t15_escapes.c M toolchain/as/as.h M toolchain/as/encode.c M toolchain/cc/cc.h M toolchain/cc/codegen.c M toolchain/cc/lexer.c M toolchain/cc/parser.c M tools/run_toolchain_tests.sh