$ git commit -m "$(cat <<'EOF'
Phase T7: full switch-over to the self-hosted toolchain
Makefile now builds toolchain/{as,cc,ld} with host gcc once (bootstrap
only) and uses them for every kernel and host-tool source file after
that — no gcc/nasm/ld/objcopy anywhere in the OS build path. Verified
via a fresh make clean && make: tools/run_tests.sh and
tools/run_toolchain_tests.sh both green.
Getting there surfaced a long tail of real bugs, each found by actually
booting/running previously-untested code paths through the new
toolchain, not by inspection:
AS (encoding):
- lgdt/lidt missing the general register-indirect (32-bit) form
- far jmp always emitted a 16-bit offset in 32-bit code
- in ax,dx / out dx,ax missing the 0x66 operand-size prefix, silently
draining two 16-bit port words per access instead of one — root
cause of tinyfs superblock corruption
- movzx r32,r16 (reg-to-reg) had a spurious 0x66 prefix, downgrading
it to a near no-op
- missing 32-bit mov r16,r/m16 / r/m16,r16 forms
- MAX_STMTS/MAX_SYMS/MAX_RELOCS too small for struct-copy codegen
CC (codegen/parser/lexer):
- walk_collect's extern-dedup fix corrupted an in-progress is_local
scan; replaced with a proper skip_next parameter
- indirect-call sites mis-treated as extern function-name references
- file-scope static variables/functions had no internal linkage
- switch/case/default and the comma operator were unimplemented
- self-referential and forward-declared struct tags didn't resolve
- function-pointer parameters weren't parsed
- no 16-bit load/store support (uint16_t writes clobbered neighbors)
- T name[] = {...} never inferred array_len from its initializer —
root cause of keyboard input being silently discarded entirely
- struct/union by-value copy, return (hidden-pointer ABI), and
parameter passing were all unsupported — corrupted parser
backtracking state and script-language value construction
- read_escape had no case for \b (also \f/\v/\a): '\b' lexed to the
letter 'b', turning every typed 'b' in a shell command into a
backspace keypress
Full writeup in docs/toolchain.md's "Full switch-over (Phase T7)"
section. Ten new regression tests added (tests/toolchain/t06–t15),
one per bug class; run_toolchain_tests.sh's EXPECTED table extended
to match.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
EOF
)"
git status --short[main 1d62d14] Phase T7: full switch-over to the self-hosted toolchain 20 files changed, 1022 insertions(+), 136 deletions(-) create mode 100644 tests/toolchain/t06_switch.c create mode 100644 tests/toolchain/t07_comma.c create mode 100644 tests/toolchain/t08_forward_struct.c create mode 100644 tests/toolchain/t09_local_init.c create mode 100644 tests/toolchain/t10_short.c create mode 100644 tests/toolchain/t11_array_size.c create mode 100644 tests/toolchain/t12_local_array_size.c create mode 100644 tests/toolchain/t13_struct_copy.c create mode 100644 tests/toolchain/t14_struct_return.c create mode 100644 tests/toolchain/t15_escapes.c
$ ls toolchain/as toolchain/cc toolchain/ld && echo --- && wc -l toolchain/as/*.c toolchain/as/*.h toolchain/cc/*.c toolchain/cc/*.h toolchain/ld/*.c toolchain/ld/*.h 2>/dev/null | tail -1
toolchain/as: as.h bin_writer.c elf_format.h elf_writer.c encode.c lexer.c main.c parser.c toolchain/cc: builtins.c cc.h codegen.c lexer.c main.c parser.c preprocessor.c types.c toolchain/ld: elf_format.h elf_reader.c ld.h link_flat.c link_static_exec.c main.c --- 5657 total
1 BUILD := build 2 3 # BOOTSTRAP_CC: the *host's* real C compiler, needed exactly once to 4 # build our own AS/CC/LD from source on a fresh checkout (see 5 # docs/toolchain.md's Bootstrap section). Everything below that point - 6 # every kernel/host-tool source file - is built exclusively by our own 7 # tools; BOOTSTRAP_CC is never invoked on OS code. 8 BOOTSTRAP_CC := gcc 9 10 AS := $(BUILD)/toolchain/as 11 CC := $(BUILD)/toolchain/cc 12 LD := $(BUILD)/toolchain/ld 13 HOSTCC := $(BUILD)/toolchain/cc --target=host 14 QEMU := qemu-system-i386 15 16 # -Itoolchain/include: CC has no built-in header search path at all (see 17 # docs/toolchain.md) - <stdint.h>/<stddef.h> only resolve because of this. 18 CFLAGS := -Ikernel -Itoolchain/include 19 HOSTCFLAGS := -Ikernel -Itools -Itoolchain/include 20 21 # Must match TINYFS_DISK_SECTORS / TINYFS_FS_START_LBA in kernel/fs/tinyfs.h 22 # and KERNEL_START_LBA / KERNEL_SECTORS in boot/stage2.S 23 DISK_SECTORS := 4096 24 KERNEL_START_SECTOR := 5 25 KERNEL_MAX_SECTORS := 512 26 27 BOOT_BIN := $(BUILD)/boot.bin 28 STAGE2_BIN := $(BUILD)/stage2.bin 29 DISK_IMG := $(BUILD)/disk.img 30 31 # kernel/builtins_shim.c is gone: CC intercepts every __builtin_* call 32 # site directly in codegen (see docs/toolchain.md) and never needs a 33 # linkable definition - the shim only existed for the old gcc build. 34 KERNEL_C_SRCS := kernel/kmain.c kernel/console.c kernel/serial.c kernel/gdt.c \ 35 kernel/idt.c kernel/string.c kernel/memory.c kernel/selftest.c \ 36 kernel/ata.c kernel/fs/tinyfs.c kernel/fs/tinyfs_disk.c \ 37 kernel/keyboard.c kernel/shell/shell.c \ 38 kernel/lang/lexer.c kernel/lang/script/ast.c \ 39 kernel/lang/script/parser.c kernel/lang/script/eval.c \ 40 kernel/lang/compiler/parser.c kernel/lang/compiler/codegen.c \ 41 kernel/lang/vm.c 42 KERNEL_ASM_SRCS := kernel/entry.S kernel/idt_stubs.S 43 KERNEL_OBJS := $(patsubst kernel/%.S,$(BUILD)/%.o,$(KERNEL_ASM_SRCS)) \ 44 $(patsubst kernel/%.c,$(BUILD)/%.o,$(KERNEL_C_SRCS)) 45 KERNEL_BIN := $(BUILD)/kernel.bin 46 47 MKFS_BIN := $(BUILD)/mkfs_tinyfs 48 # host_string.o/host_tinyfs.o: kernel/string.c and kernel/fs/tinyfs.c 49 # compiled a *second* time, natively - host tools are libc-free (see 50 # docs/toolchain.md), same as the kernel itself, and this build has its 51 # own build/fs/tinyfs.o etc for the freestanding kernel target, so these 52 # need the host_ prefix to not collide with those. 53 # tools/host_syscalls_shim.c is gone the same way builtins_shim.c is: 54 # CC intercepts __sys_* calls directly. 55 MKFS_OBJS := $(BUILD)/host_tinyfs.o $(BUILD)/host_string.o $(BUILD)/host_mkfs_tinyfs.o 56 # host-path:fs-name pairs seeded into every disk image; the host paths 57 # alone are listed separately so they can be used as make prerequisites 58 MKFS_SEED_FILES := tests/hello.tsh tests/hello.tc 59 MKFS_SEED_SPECS := tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc 60 61 .PHONY: all run run-gui clean 62 63 all: $(DISK_IMG) 64 65 BUILD_SUBDIRS := $(BUILD) $(BUILD)/toolchain $(BUILD)/fs $(BUILD)/shell \ 66 $(BUILD)/lang $(BUILD)/lang/script $(BUILD)/lang/compiler 67 68 $(BUILD_SUBDIRS): 69 mkdir -p $@ 70 71 # --- bootstrap: build our own AS/CC/LD from source, once, with the host's gcc --- 72 73 TOOLCHAIN_AS_SRCS := $(wildcard toolchain/as/*.c) 74 TOOLCHAIN_CC_SRCS := $(wildcard toolchain/cc/*.c) 75 TOOLCHAIN_LD_SRCS := $(wildcard toolchain/ld/*.c) 76 77 $(BUILD)/toolchain/as: $(TOOLCHAIN_AS_SRCS) | $(BUILD)/toolchain 78 $(BOOTSTRAP_CC) -Wall -Wextra -O2 -o $@ $(TOOLCHAIN_AS_SRCS) 79 80 $(BUILD)/toolchain/cc: $(TOOLCHAIN_CC_SRCS) | $(BUILD)/toolchain 81 $(BOOTSTRAP_CC) -Wall -Wextra -O2 -o $@ $(TOOLCHAIN_CC_SRCS) 82 83 $(BUILD)/toolchain/ld: $(TOOLCHAIN_LD_SRCS) | $(BUILD)/toolchain 84 $(BOOTSTRAP_CC) -Wall -Wextra -O2 -o $@ $(TOOLCHAIN_LD_SRCS) 85 86 # --- boot loader (hand-written assembly, flat binary) --- 87 88 $(BOOT_BIN): boot/boot.S | $(BUILD) $(AS) 89 $(AS) -f bin -o $@ $< 90 91 $(STAGE2_BIN): boot/stage2.S | $(BUILD) $(AS) 92 $(AS) -f bin -o $@ $< 93 94 # --- kernel --- 95 # Hand-written assembly (entry.S/idt_stubs.S) assembles directly. 96 # C sources go through CC first (.c -> .S, AS-syntax text) and then AS 97 # (.S -> .o, ELF32 relocatable) - CC has no built-in assembler, it always 98 # emits assembly text (see docs/toolchain.md), so this is always two 99 # steps where gcc's `-c` used to be one. 100 101 $(BUILD)/%.o: kernel/%.S | $(BUILD_SUBDIRS) $(AS) 102 $(AS) -f elf32 -o $@ $< 103 104 $(BUILD)/%.S: kernel/%.c | $(BUILD_SUBDIRS) $(CC) 105 $(CC) $(CFLAGS) -o $@ $< 106 107 $(BUILD)/%.o: $(BUILD)/%.S | $(BUILD_SUBDIRS) $(AS) 108 $(AS) -f elf32 -o $@ $< 109 110 # LD's link_flat mode writes the kernel image straight to a flat binary 111 # (--format bin) - no intermediate ELF, no OBJCOPY step (see 112 # docs/toolchain.md: LD already has the loadable bytes laid out in 113 # memory before an ELF wrapper would even be needed). 114 $(KERNEL_BIN): $(KERNEL_OBJS) kernel/link.ld | $(LD) 115 $(LD) -T kernel/link.ld --format bin -o $@ $(KERNEL_OBJS) 116 @size=$$(stat -c%s $@); budget=$$(( $(KERNEL_MAX_SECTORS) * 512 )); \ 117 if [ "$$size" -gt "$$budget" ]; then \ 118 echo "error: $@ is $$size bytes, exceeds the $$budget-byte ($(KERNEL_MAX_SECTORS)-sector) boot budget" >&2; \ 119 echo " bump KERNEL_SECTORS in boot/stage2.S, KERNEL_MAX_SECTORS here, and TINYFS_FS_START_LBA in kernel/fs/tinyfs.h together" >&2; \ 120 exit 1; \ 121 fi 122 123 # --- host-side mkfs tool (build-time infrastructure, not kernel code) --- 124 # Same two-step (.c -> .S -> .o) pipeline as the kernel, via HOSTCC 125 # (CC --target=host, which adds a _start that calls main - see 126 # docs/toolchain.md) instead of CC. 127 128 $(BUILD)/host_tinyfs.S: kernel/fs/tinyfs.c | $(BUILD) $(CC) 129 $(HOSTCC) $(HOSTCFLAGS) -o $@ $< 130 $(BUILD)/host_tinyfs.o: $(BUILD)/host_tinyfs.S | $(BUILD) $(AS) 131 $(AS) -f elf32 -o $@ $< 132 133 $(BUILD)/host_string.S: kernel/string.c | $(BUILD) $(CC) 134 $(HOSTCC) $(HOSTCFLAGS) -o $@ $< 135 $(BUILD)/host_string.o: $(BUILD)/host_string.S | $(BUILD) $(AS) 136 $(AS) -f elf32 -o $@ $< 137 138 $(BUILD)/host_mkfs_tinyfs.S: tools/mkfs_tinyfs.c | $(BUILD) $(CC) 139 $(HOSTCC) $(HOSTCFLAGS) -o $@ $< 140 $(BUILD)/host_mkfs_tinyfs.o: $(BUILD)/host_mkfs_tinyfs.S | $(BUILD) $(AS) 141 $(AS) -f elf32 -o $@ $< 142 143 # LD's default (link_static_exec) mode: a minimal static ELF32 executable, 144 # entry point taken from the linked _start symbol (see docs/toolchain.md). 145 $(MKFS_BIN): $(MKFS_OBJS) | $(LD) 146 $(LD) -o $@ $(MKFS_OBJS) 147 148 $(DISK_IMG): $(BOOT_BIN) $(STAGE2_BIN) $(KERNEL_BIN) $(MKFS_BIN) $(MKFS_SEED_FILES) 149 dd if=/dev/zero of=$@ bs=512 count=$(DISK_SECTORS) status=none 150 dd if=$(BOOT_BIN) of=$@ conv=notrunc status=none 151 dd if=$(STAGE2_BIN) of=$@ bs=512 seek=1 conv=notrunc status=none 152 dd if=$(KERNEL_BIN) of=$@ bs=512 seek=$(KERNEL_START_SECTOR) conv=notrunc status=none 153 $(MKFS_BIN) $@ $(MKFS_SEED_SPECS) 154 155 # Headless: no window at all, so there's no input surface for the emulated 156 # PS/2 keyboard - useful for a quick boot sanity check or scripting, but 157 # you cannot type into the shell this way. Use `make run-gui` to interact. 158 run: $(DISK_IMG) 159 $(QEMU) -drive file=$(DISK_IMG),format=raw -display none -serial stdio -no-reboot 160 161 # Opens a real window - click into it, then type. Serial output is also 162 # echoed to this terminal, but keystrokes only reach the guest via the 163 # window. 164 run-gui: $(DISK_IMG) 165 $(QEMU) -drive file=$(DISK_IMG),format=raw -serial stdio -no-reboot 166 167 clean: 168 rm -rf $(BUILD) 169
$ grep -l '#include' toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c | xargs grep -h '#include' | sort -u
* (no copy), and sibling #includes at the same depth
if (depth >= MAX_INCLUDE_DEPTH) { fprintf(stderr, "cc: #include nested too deep\n"); exit(1); }
if (is_quote && path_tok.kind != TK_STR) { fprintf(stderr, "cc: %s:%d: expected \"file\" after #include\n", filename, dir.line); exit(1); }
if (peekc(lx) != '<') { fprintf(stderr, "cc: %s:%d: expected <file> after #include\n", lx->filename, lx->line); exit(1); }
#include "as.h"
#include "cc.h"
#include "elf_format.h"
#include "ld.h"
#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>$ cd /home/martin/workspace/prototypes/boot-to-claude
echo "=== stdio calls ==="; grep -ohE '\b(fopen|fread|fwrite|fclose|fseek|ftell|fprintf|printf|fputc|fputs|fgets|vfprintf|vprintf|sprintf|snprintf|perror|stdin|stdout|stderr)\b' toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c | sort | uniq -c | sort -rn
echo "=== stdlib calls ==="; grep -ohE '\b(malloc|calloc|realloc|free|exit|atoi|atol|strtol|strtoll|strtoull|qsort|abort)\b' toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c | sort | uniq -c | sort -rn
echo "=== string calls ==="; grep -ohE '\b(strcmp|strncmp|strcpy|strncpy|strlen|strcat|strncat|strchr|strrchr|strstr|memcpy|memset|memmove|memcmp)\b' toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c | sort | uniq -c | sort -rn
echo "=== ctype calls ==="; grep -ohE '\b(isalpha|isdigit|isalnum|isspace|isupper|islower|toupper|tolower|isxdigit)\b' toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c | sort | uniq -c | sort -rn
echo "=== varargs ==="; grep -n 'va_list\|va_start\|va_arg\|va_end\|\.\.\.' toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c
echo "=== sys/stat ==="; grep -n 'sys/stat\|struct stat\|stat(' toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c=== stdio calls ===
47 stderr
47 fprintf
29 fwrite
18 fopen
11 fclose
8 perror
6 snprintf
6 fseek
5 ftell
5 fputc
4 fread
1 vfprintf
1 printf
=== stdlib calls ===
32 exit
12 calloc
4 malloc
2 strtoll
1 free
=== string calls ===
121 strcmp
22 memset
15 memcpy
7 strncmp
6 strlen
1 strrchr
1 strcpy
=== ctype calls ===
4 isalnum
3 tolower
2 isdigit
2 isalpha
=== varargs ===
toolchain/cc/main.c:30: * (-Wall, -O2, -std=c11, -m32, -ffreestanding, ...) rather
toolchain/cc/main.c:39: fprintf(stderr, "usage: cc [-I dir]... [--target=kernel|host] -o <out.S> <in.c>\n");
toolchain/cc/codegen.c:27:static void out(const char *fmt, ...) {
toolchain/cc/codegen.c:28: va_list ap;
toolchain/cc/codegen.c:29: va_start(ap, fmt);
toolchain/cc/codegen.c:31: va_end(ap);
toolchain/cc/codegen.c:208: * disk_block_read, ...)) decays to its own address - there's no
toolchain/cc/codegen.c:353: void (*emit_arg)(node_t *), void (*emitf)(const char *, ...));
toolchain/cc/codegen.c:695: * not a function symbol at all - e.g. tinyfs.c's `block_read(...)`
toolchain/cc/preprocessor.c:100: * TINYFS_INODE_BLOCK -> (TINYFS_BITMAP_BLOCK + ...)). `active` tracks
toolchain/cc/builtins.c:14: * `emitf(fmt, ...)` writes a line of assembly text. */
toolchain/cc/builtins.c:17:typedef void (*emitf_fn)(const char *, ...);
toolchain/ld/main.c:143: fprintf(stderr, "usage: ld [-T <script>] [--flat-base 0xADDR] [--format elf|bin] -o <out> <in.o>...\n");
toolchain/cc/parser.c:219: * `struct node { ... }`) needs to find its own tag while
toolchain/cc/parser.c:783:/* `{ v1, v2, ... }` or a single scalar expression. Brace lists are only
toolchain/cc/parser.c:818: /* Same `T name[] = { ... }` size-inference fix as parse_global_var -
toolchain/cc/parser.c:820: * {...};` (used throughout the kernel's scripting-language parser
toolchain/cc/parser.c:964: /* `T name[] = { ... };` - array_len was left at -1 by parse_declarator
toolchain/cc/parser.c:993: /* cdecl: EBP+8, +12, ... for params, in order - except when the
toolchain/cc/parser.c:1068: decl = node_new(ND_NOP); /* lone `struct Foo { ... };` with no variable */
=== sys/stat ===
toolchain/ld/link_static_exec.c:6:#include <sys/stat.h>1 #include "cc.h"
2
3 #include <stdio.h>
4 #include <string.h>
5
6 /* The fixed builtin list this compiler supports, in place of a general
7 * GCC-extended-inline-asm parser (see docs/toolchain.md for why - the
8 * kernel's actual inline-asm usage reduces to exactly these ~7
9 * operations). Each maps 1:1 to a short, fixed instruction sequence;
10 * arguments are evaluated left-to-right via the normal expression
11 * codegen, matching how a real function call would evaluate them,
12 * then moved into the fixed registers x86 port I/O and syscalls
13 * require. `emit_arg(node)` is codegen_expr (result in EAX);
14 * `emitf(fmt, ...)` writes a line of assembly text. */
15
16 typedef void (*emit_arg_fn)(node_t *);
17 typedef void (*emitf_fn)(const char *, ...);
18
19 static int g_builtin_label_id;
20
21 static node_t *nth(node_t *args, int i) {
22 for (; args && i > 0; args = args->next, i--) {}
23 return args;
24 }
25
26 int is_builtin_name(const char *name) {
27 static const char *names[] = {
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,
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,
40 emit_arg_fn emit_arg, emitf_fn emitf) {
41 (void)target;
42
43 /* --- kernel port I/O + privileged instructions --- */
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 */
48 emitf(" mov edx, eax\n"); /* edx = port (dx usable) */
49 emitf(" mov eax, ecx\n"); /* eax = value (al usable) */
50 emitf(" out dx, al\n");
51 return 1;
52 }
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;
57 }
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");
62 return 1;
63 }
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;
68 }
69 if (strcmp(name, "__builtin_hlt") == 0) { emitf(" hlt\n"); return 1; }
70 if (strcmp(name, "__builtin_lidt") == 0) {
71 emit_arg(nth(args, 0)); /* pointer to the idt descriptor */
72 emitf(" mov ecx, eax\n lidt [ecx]\n");
73 return 1;
74 }
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++);
86 emit_arg(nth(args, 0)); /* pointer to the gdt descriptor */
87 emitf(" mov ecx, eax\n lgdt [ecx]\n");
88 emitf(" mov ax, 0x10\n mov ds, ax\n mov es, ax\n mov fs, ax\n mov gs, ax\n mov ss, ax\n");
89 emitf(" jmp 0x08:%s\n", lbl);
90 emitf("%s:\n", lbl);
91 return 1;
92 }
93
94 /* --- raw Linux syscalls (host target only - see docs/toolchain.md) --- */
95 if (strcmp(name, "__sys_exit") == 0) {
96 emit_arg(nth(args, 0)); emitf(" mov ebx, eax\n mov eax, 1\n int 0x80\n");
97 return 1;
98 }
99 if (strcmp(name, "__sys_close") == 0) {
100 emit_arg(nth(args, 0)); emitf(" mov ebx, eax\n mov eax, 6\n int 0x80\n");
101 return 1;
102 }
103 if (strcmp(name, "__sys_open") == 0) {
104 emit_arg(nth(args, 0)); emitf(" push eax\n"); /* path */
105 emit_arg(nth(args, 1)); emitf(" push eax\n"); /* flags */
106 emitf(" mov edx, 420\n"); /* mode 0644, used only when O_CREAT is set */
107 emitf(" pop ecx\n pop ebx\n mov eax, 5\n int 0x80\n");
108 return 1;
109 }
110 if (strcmp(name, "__sys_read") == 0 || strcmp(name, "__sys_write") == 0) {
111 int nr = strcmp(name, "__sys_read") == 0 ? 3 : 4;
112 emit_arg(nth(args, 0)); emitf(" push eax\n"); /* fd */
113 emit_arg(nth(args, 1)); emitf(" push eax\n"); /* buf */
114 emit_arg(nth(args, 2)); emitf(" mov edx, eax\n"); /* count */
115 emitf(" pop ecx\n pop ebx\n mov eax, %d\n int 0x80\n", nr);
116 return 1;
117 }
118 if (strcmp(name, "__sys_lseek") == 0) {
119 emit_arg(nth(args, 0)); emitf(" push eax\n"); /* fd */
120 emit_arg(nth(args, 1)); emitf(" push eax\n"); /* offset */
121 emit_arg(nth(args, 2)); emitf(" mov edx, eax\n"); /* whence */
122 emitf(" pop ecx\n pop ebx\n mov eax, 19\n int 0x80\n");
123 return 1;
124 }
125
126 return 0;
127 }
128 {
"taskId": "16",
"status": "in_progress"
}Updated task #16 status
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'parse-only\|parse_only' toolchain/cc/main.c toolchain/cc/cc.h echo --- cat toolchain/cc/main.c
toolchain/cc/main.c:11: int parse_only = 0;
toolchain/cc/main.c:26: else if (strcmp(argv[i], "--parse-only") == 0) { parse_only = 1; }
toolchain/cc/main.c:49: if (parse_only) {
---
#include "cc.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv) {
const char *inpath = NULL;
const char *outpath = NULL;
cc_target_t target = TARGET_KERNEL;
int parse_only = 0;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) { outpath = argv[++i]; }
else if (strcmp(argv[i], "-I") == 0 && i + 1 < argc) {
if (g_n_include_dirs < MAX_INCLUDE_DIRS) g_include_dirs[g_n_include_dirs++] = argv[++i];
}
else if (strncmp(argv[i], "-I", 2) == 0 && argv[i][2] != '\0') {
/* gcc-compatible combined form (-Ikernel, no space) - the
* real Makefile's CFLAGS uses exactly this form. */
if (g_n_include_dirs < MAX_INCLUDE_DIRS) g_include_dirs[g_n_include_dirs++] = argv[i] + 2;
}
else if (strncmp(argv[i], "--target=", 9) == 0) {
target = strcmp(argv[i] + 9, "host") == 0 ? TARGET_HOST : TARGET_KERNEL;
}
else if (strcmp(argv[i], "--parse-only") == 0) { parse_only = 1; }
else if (strcmp(argv[i], "-c") == 0) { /* implied: one file in, one .S out - accepted, no-op */ }
else if (argv[i][0] == '-') {
/* Tolerate the rest of a real compiler invocation's flags
* (-Wall, -O2, -std=c11, -m32, -ffreestanding, ...) rather
* than erroring or (worse) mistaking one for the input
* file - the Makefile passes these unchanged either way,
* since CC is meant to be a drop-in gcc replacement for
* this project's exact build commands (see docs/toolchain.md). */
}
else { inpath = argv[i]; }
}
if (!inpath) {
fprintf(stderr, "usage: cc [-I dir]... [--target=kernel|host] -o <out.S> <in.c>\n");
return 1;
}
static token_t toks[MAX_TOKENS];
int n_toks = 0;
pp_process_file(inpath, toks, &n_toks, MAX_TOKENS);
node_t *prog = cc_parse(toks, n_toks);
if (parse_only) {
printf("cc: parsed %s OK\n", inpath);
return 0;
}
if (!outpath) {
fprintf(stderr, "cc: -o <out.S> required\n");
return 1;
}
FILE *out = fopen(outpath, "w");
if (!out) { perror("cc: fopen"); return 1; }
codegen_emit(prog, out, target);
fclose(out);
return 0;
}$ cd /home/martin/workspace/prototypes/boot-to-claude make build/toolchain/cc build/toolchain/as build/toolchain/ld 2>&1 | tail -20 ls -la build/toolchain/
make: 'build/toolchain/cc' is up to date. make: 'build/toolchain/as' is up to date. make: 'build/toolchain/ld' is up to date. total 180 drwxr-x--- 2 martin martin 4096 Jul 18 22:53 . drwxr-x--- 7 martin martin 4096 Jul 18 22:53 .. -rwxr-x--- 1 martin martin 51504 Jul 18 22:53 as -rwxr-x--- 1 martin martin 69832 Jul 18 22:53 cc -rwxr-x--- 1 martin martin 21864 Jul 18 22:53 ld
$ cd /home/martin/workspace/prototypes/boot-to-claude for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c; do echo "=== $f ===" ./build/toolchain/cc -Itoolchain/include --target=host --parse-only "$f" 2>&1 | head -5 done
=== toolchain/as/bin_writer.c === cc: toolchain/as/bin_writer.c:3: cannot find include file 'stdio.h' === toolchain/as/elf_writer.c === cc: toolchain/as/elf_writer.c:4: cannot find include file 'stdio.h' === toolchain/as/encode.c === cc: toolchain/as/encode.c:3: cannot find include file 'stdio.h' === toolchain/as/lexer.c === cc: toolchain/as/lexer.c:3: cannot find include file 'ctype.h' === toolchain/as/main.c === cc: toolchain/as/main.c:3: cannot find include file 'stdio.h' === toolchain/as/parser.c === cc: toolchain/as/parser.c:3: cannot find include file 'stdio.h' === toolchain/cc/builtins.c === cc: toolchain/cc/builtins.c:3: cannot find include file 'stdio.h' === toolchain/cc/codegen.c === cc: toolchain/cc/codegen.c:3: cannot find include file 'stdio.h' === toolchain/cc/lexer.c === cc: toolchain/cc/lexer.c:3: cannot find include file 'ctype.h' === toolchain/cc/main.c === cc: toolchain/cc/main.c:3: cannot find include file 'stdio.h' === toolchain/cc/parser.c === cc: toolchain/cc/parser.c:3: cannot find include file 'stdio.h' === toolchain/cc/preprocessor.c === cc: toolchain/cc/preprocessor.c:3: cannot find include file 'stdio.h' === toolchain/cc/types.c === cc: toolchain/cc/types.c:3: cannot find include file 'stdlib.h' === toolchain/ld/elf_reader.c === cc: toolchain/ld/elf_reader.c:4: cannot find include file 'stdio.h' === toolchain/ld/link_flat.c === cc: toolchain/ld/link_flat.c:4: cannot find include file 'stdio.h' === toolchain/ld/link_static_exec.c === cc: toolchain/ld/link_static_exec.c:4: cannot find include file 'stdio.h' === toolchain/ld/main.c === cc: toolchain/ld/main.c:4: cannot find include file 'stdio.h'
$ cd /home/martin/workspace/prototypes/boot-to-claude ls toolchain/include/ echo --- cat toolchain/include/stdint.h echo --- cat toolchain/include/stddef.h
stddef.h stdint.h --- #pragma once /* Hand-rolled freestanding stdint.h for CC - just the typedefs this * codebase actually uses (no 64-bit types, so CC's type-specifier * grammar never needs to support `long`). Real gcc bundles one of these * for free; CC needs its own since it doesn't ship a libc. */ typedef signed char int8_t; typedef unsigned char uint8_t; typedef signed short int16_t; typedef unsigned short uint16_t; typedef signed int int32_t; typedef unsigned int uint32_t; --- #pragma once typedef unsigned int size_t; #define NULL ((void *)0)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'parse_param_list\|TY_FUNC\|n_params\|nparams\|variadic\|ELLIPSIS\|"\.\.\."\|is_vararg' toolchain/cc/parser.c toolchain/cc/cc.h toolchain/cc/lexer.c | head -60
toolchain/cc/cc.h:86: TY_STRUCT, TY_UNION, TY_ENUM, TY_FUNC,
toolchain/cc/cc.h:102: type_t *base; /* pointee (TY_PTR), element (TY_ARRAY), return type (TY_FUNC) */
toolchain/cc/cc.h:107: type_t *param; /* TY_FUNC: linked list of param types via ->next */
toolchain/cc/parser.c:355:static type_t *parse_param_list(parser_t *p, char names[][MAX_NAME], int *n_names) {
toolchain/cc/parser.c:377: type_t *functy = type_new(TY_FUNC);
toolchain/cc/parser.c:379: functy->param = parse_param_list(p, NULL, NULL);
toolchain/cc/parser.c:411: char param_names[][MAX_NAME], int *n_params) {
toolchain/cc/parser.c:420: type_t *functy = type_new(TY_FUNC);
toolchain/cc/parser.c:422: functy->param = parse_param_list(p, param_names, n_params);
toolchain/cc/parser.c:440: type_t *functy = type_new(TY_FUNC);
toolchain/cc/parser.c:442: functy->param = parse_param_list(p, param_names, n_params);
toolchain/cc/parser.c:532: call->type = (fs && fs->type->kind == TY_FUNC) ? fs->type->base : g_int_type;
toolchain/cc/parser.c:533: if (!(fs && fs->type->kind == TY_FUNC)) {
toolchain/cc/parser.c:542: if (vs && vs->type->kind == TY_PTR && vs->type->base->kind == TY_FUNC) {
toolchain/cc/parser.c:978: char param_names[][MAX_NAME], int n_params, int is_static) {
toolchain/cc/parser.c:1003: for (int i = 0; i < n_params && pt; i++, pt = pt->next) {
toolchain/cc/parser.c:1071: int n_params = 0;
toolchain/cc/parser.c:1073: type_t *t = parse_declarator(&p, base, name, param_names, &n_params);
toolchain/cc/parser.c:1074: if (t->kind == TY_FUNC) {
toolchain/cc/parser.c:1075: decl = parse_func(&p, name, t, param_names, n_params, is_static);340 arr->array_len = n;
341 mt = arr;
342 }
343 member_t *m = calloc(1, sizeof(member_t));
344 safe_strcpy(m->name, name, MAX_NAME);
345 m->type = mt;
346 if (tail) tail->next = m; else head = m;
347 tail = m;
348 if (!match(p, TK_COMMA)) break;
349 }
350 expect(p, TK_SEMI);
351 }
352 return head;
353 }
354
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_LPAREN) && p->toks[p->pos + 1].kind == TK_STAR) {
366 /* function-pointer parameter: TYPE (*name)(PARAMS), e.g.
367 * parse_binop_level's `ast_node_t *(*next)(parser_t *)` -
368 * mirrors parse_declarator's identical form, duplicated here
369 * (rather than delegating to it) because plain parameters in
370 * this loop tolerate an unnamed parameter and parse_declarator
371 * doesn't. */
372 advance(p);
373 expect(p, TK_STAR);
374 if (check(p, TK_IDENT)) { safe_strcpy(nm, cur(p).text, MAX_NAME); advance(p); }
375 expect(p, TK_RPAREN);
376 expect(p, TK_LPAREN);
377 type_t *functy = type_new(TY_FUNC);
378 functy->base = pt;
379 functy->param = parse_param_list(p, NULL, NULL);
380 expect(p, TK_RPAREN);
381 pt = ptr_to(functy);
382 } else {
383 if (check(p, TK_IDENT)) { safe_strcpy(nm, cur(p).text, MAX_NAME); advance(p); }
384 while (match(p, TK_LBRACKET)) {
385 if (!check(p, TK_RBRACKET)) advance(p);
386 expect(p, TK_RBRACKET);
387 pt = ptr_to(pt); /* array param decays to pointer */
388 }
389 }
390 type_t *node = type_new(pt->kind);
391 *node = *pt;
392 node->next = NULL;
393 if (tail) tail->next = node; else head = node;
394 tail = node;
395 if (names && n_names) safe_strcpy(names[n], nm, MAX_NAME);
396 n++;
397 if (!match(p, TK_COMMA)) break;
398 }
399 if (n_names) *n_names = n;
400 return head;
401 }
402
403 /* Parses a declarator (the part after the base type specifier) and
404 * returns the full type, writing the declared name into name_out.
405 * Scoped to what this codebase's declarators actually need (see
406 * docs/toolchain.md): `*`-prefix pointers, `name`, `name[N]`/`name[]`,
407 * `name(params)` (function), and the one parenthesized case,
408 * `(*name)(params)` (function pointer) - not the fully general/
409 * arbitrarily-nested C declarator grammar. */
410 static type_t *parse_declarator(parser_t *p, type_t *base, char *name_out,
411 char param_names[][MAX_NAME], int *n_params) {
412 while (match(p, TK_STAR)) base = ptr_to(base);
413
414 if (match(p, TK_LPAREN)) {
415 expect(p, TK_STAR);
416 token_t id = expect(p, TK_IDENT);
417 safe_strcpy(name_out, id.text, MAX_NAME);
418 expect(p, TK_RPAREN);
419 expect(p, TK_LPAREN);
420 type_t *functy = type_new(TY_FUNC);
421 functy->base = base;
422 functy->param = parse_param_list(p, param_names, n_params);
423 expect(p, TK_RPAREN);
424 return ptr_to(functy);
425 }
426
427 token_t id = expect(p, TK_IDENT);
428 safe_strcpy(name_out, id.text, MAX_NAME);
429
430 if (match(p, TK_LBRACKET)) {
431 int n = -1;
432 if (!check(p, TK_RBRACKET)) n = (int)parse_const_expr(p);
433 expect(p, TK_RBRACKET);
434 type_t *arr = type_new(TY_ARRAY);
435 arr->base = base;
436 arr->array_len = n;
437 return arr;
438 }
439 if (match(p, TK_LPAREN)) {
440 type_t *functy = type_new(TY_FUNC);
441 functy->base = base;
442 functy->param = parse_param_list(p, param_names, n_params);
443 expect(p, TK_RPAREN);
444 return functy;
445 }
446 return base;
447 }
448
449 static type_t *parse_type_name(parser_t *p) {
450 type_t *base = parse_type_spec(p, NULL, NULL);
451 while (match(p, TK_STAR)) base = ptr_to(base);
452 if (match(p, TK_LBRACKET)) {
453 int n = -1;
454 if (!check(p, TK_RBRACKET)) n = (int)parse_const_expr(p);
455 expect(p, TK_RBRACKET);
456 type_t *arr = type_new(TY_ARRAY);
457 arr->base = base;
458 arr->array_len = n;
459 base = arr;960 n->is_extern_decl = is_extern;
961 n->is_file_static = is_static;
962 safe_strcpy(n->asm_label, name, MAX_NAME);
963 if (match(p, TK_ASSIGN)) n->init = parse_initializer(p);
964 /* `T name[] = { ... };` - array_len was left at -1 by parse_declarator
965 * (no size between the brackets), and type_size() treats a non-
966 * positive array_len as size 0 - silently making sizeof(name) come
967 * out as 0 instead of the real, initializer-inferred length. Fix it
968 * up now that the initializer list is available to count. */
969 if (t->kind == TY_ARRAY && t->array_len < 0 && n->init && n->init->kind == ND_BLOCK) {
970 int count = 0;
971 for (node_t *v = n->init->list; v; v = v->next) count++;
972 t->array_len = count;
973 }
974 return n;
975 }
976
977 static node_t *parse_func(parser_t *p, const char *name, type_t *functy,
978 char param_names[][MAX_NAME], int n_params, int is_static) {
979 sym_t *fs = add_global(p, name, functy);
980
981 if (match(p, TK_SEMI)) return NULL; /* prototype only */
982
983 p->locals = NULL;
984 p->frame_size = 0;
985 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));
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 /* Struct/union parameters are passed by value as their real
1011 * (4-byte-rounded) size, not a flat 4 bytes like every scalar/
1012 * pointer param - the caller copies the whole struct onto the
1013 * stack (see codegen_call_or_builtin), so subsequent params'
1014 * offsets need to account for its actual width. */
1015 if (pt->kind == TY_STRUCT || pt->kind == TY_UNION) {
1016 int sz = (type_size(pt) + 3) & ~3;
1017 off += sz < 4 ? 4 : sz;
1018 } else {
1019 off += 4;
1020 }
1021
1022 node_t *pn = node_new(ND_VAR_DECL);
1023 safe_strcpy(pn->name, param_names[i], MAX_NAME);
1024 pn->type = pt;
1025 pn->frame_offset = s->frame_offset;
1026 if (!n->list) n->list = pn; else ptail->next = pn;
1027 ptail = pn;
1028 }
1029
1030 n->b = parse_block(p);
1031 n->num = p->frame_size;
1032 fs->type = functy;
1033 return n;
1034 }
1035
1036 static node_t *parse_typedef(parser_t *p) {
1037 advance(p); /* 'typedef' */
1038 type_t *base = parse_type_spec(p, NULL, NULL);
1039 char name[MAX_NAME];$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'codegen_call_or_builtin' toolchain/cc/codegen.c | head -5
42:static void codegen_call_or_builtin(node_t *n);
313: codegen_call_or_builtin(n);
355:static void codegen_call_or_builtin(node_t *n) {
500: * codegen_call_or_builtin): copy the result into the355 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 int total_bytes = 0;
362 for (int i = argc - 1; i >= 0; i--) {
363 codegen_expr(args[i]);
364 if (args[i]->type->kind == TY_STRUCT || args[i]->type->kind == TY_UNION) {
365 /* Struct/union by value: codegen_expr already yields its
366 * address (see load_value), not its bytes - copy the whole
367 * thing onto the stack rather than pushing a 4-byte pointer,
368 * matching parse_func's real-size parameter offsets. */
369 int sz = type_size(args[i]->type);
370 sz = (sz + 3) & ~3;
371 if (sz < 4) sz = 4;
372 out(" mov ecx, eax\n"); /* save src - sub esp doesn't touch ecx */
373 out(" sub esp, %d\n", sz);
374 out(" mov eax, ecx\n"); /* src */
375 out(" mov ecx, esp\n"); /* dst = the space just reserved */
376 codegen_struct_copy(args[i]->type);
377 total_bytes += sz;
378 } else {
379 out(" push eax\n");
380 total_bytes += 4;
381 }
382 }
383 /* Struct/union return: pass the hidden temp slot's address (see
384 * parse_primary's ND_CALL construction) as an implicit extra
385 * argument, pushed last so it lands first from the callee's side
386 * ([ebp+8] - matches parse_func's `returns_struct` offset shift). */
387 int is_struct_return = n->type->kind == TY_STRUCT || n->type->kind == TY_UNION;
388 if (is_struct_return) {
389 out(" lea eax, [ebp%+lld]\n", (long long)n->num);
390 out(" push eax\n");
391 total_bytes += 4;
392 }
393 if (n->is_indirect_call) {
394 /* Callee is a function-pointer variable (e.g. tinyfs_init's
395 * read_fn/write_fn callbacks), not a directly callable symbol -
396 * load its stored address (always a plain 4-byte pointer, so no
397 * load_value/is_byte_type dance needed) and call through it.
398 * Done last, after all args are already pushed, so this doesn't
399 * need to protect eax from being clobbered by arg evaluation. */
400 codegen_var_addr(n);
401 out(" mov eax, [eax]\n");
402 out(" call eax\n");
403 } else {
404 out(" call %s\n", n->name);
405 }
406 if (total_bytes) out(" add esp, %d\n", total_bytes);
407 /* eax already holds the hidden slot's address on return (ND_RETURN
408 * leaves it there) - exactly the "address of a struct-typed rvalue"
409 * convention load_value/codegen_expr already use elsewhere, so
410 * nothing further to do here for is_struct_return. */
411 }
412
413 /* ---------------- statements ---------------- */
414
415 static void codegen_stmt(node_t *n) {
416 switch (n->kind) {
417 case ND_BLOCK:
418 for (node_t *s = n->list; s; s = s->next) codegen_stmt(s);
419 return;
420 case ND_EXPR_STMT:
421 codegen_expr(n->a);
422 return;
423 case ND_VAR_DECL:
424 if (n->is_static_local) return; /* emitted into .data/.bss separately */
425 /* codegen_init_local handles both brace-init lists (e.g.
426 * eval.c's `exec_result_t r = { EXEC_NORMAL, VAL_ZERO };`)
427 * and plain scalar initializers - a bare `int x = 1;` is
428 * just its one-element base case. */
429 if (n->init) codegen_init_local(n->frame_offset, n->type, n->init);
430 return;
431 case ND_IF: {
432 char lelse[MAX_NAME], lend[MAX_NAME];
433 new_label(lelse); new_label(lend);
434 codegen_expr(n->a);
435 out(" cmp eax, 0\n je %s\n", lelse);
436 codegen_stmt(n->b);
437 out(" jmp %s\n", lend);
438 out("%s:\n", lelse);
439 if (n->c) codegen_stmt(n->c);
440 out("%s:\n", lend);
441 return;
442 }
443 case ND_WHILE: {
444 char ltop[MAX_NAME], lend[MAX_NAME];
445 new_label(ltop); new_label(lend);
446 loop_ctx_t lc = { "", "", g_loop };
447 safe_strcpy(lc.break_label, lend, MAX_NAME);
448 safe_strcpy(lc.continue_label, ltop, MAX_NAME);
449 g_loop = &lc;
450 out("%s:\n", ltop);
451 codegen_expr(n->a);
452 out(" cmp eax, 0\n je %s\n", lend);
453 codegen_stmt(n->b);
454 out(" jmp %s\n%s:\n", ltop, lend);
455 g_loop = lc.parent;
456 return;
457 }
458 case ND_DOWHILE: {
459 char ltop[MAX_NAME], lend[MAX_NAME], lcont[MAX_NAME];
460 new_label(ltop); new_label(lend); new_label(lcont);
461 loop_ctx_t lc = { "", "", g_loop };
462 safe_strcpy(lc.break_label, lend, MAX_NAME);
463 safe_strcpy(lc.continue_label, lcont, MAX_NAME);
464 g_loop = &lc;
465 out("%s:\n", ltop);
466 codegen_stmt(n->b);
467 out("%s:\n", lcont);
468 codegen_expr(n->a);
469 out(" cmp eax, 0\n jne %s\n", ltop);
470 out("%s:\n", lend);
471 g_loop = lc.parent;
472 return;
473 }
474 case ND_FOR: {
475 char ltop[MAX_NAME], lend[MAX_NAME], lcont[MAX_NAME];
476 new_label(ltop); new_label(lend); new_label(lcont);
477 if (n->a) {
478 if (n->a->kind == ND_VAR_DECL) {
479 for (node_t *d = n->a; d; d = d->next) codegen_stmt(d);
480 } else {
481 codegen_expr(n->a);
482 }
483 }
484 loop_ctx_t lc = { "", "", g_loop };
485 safe_strcpy(lc.break_label, lend, MAX_NAME);
486 safe_strcpy(lc.continue_label, lcont, MAX_NAME);
487 g_loop = &lc;
488 out("%s:\n", ltop);
489 if (n->b) { codegen_expr(n->b); out(" cmp eax, 0\n je %s\n", lend); }
490 codegen_stmt(n->d);
491 out("%s:\n", lcont);
492 if (n->c) codegen_expr(n->c);
493 out(" jmp %s\n%s:\n", ltop, lend);
494 g_loop = lc.parent;495 static node_t *make_str(parser_t *p, const char *text) {
496 node_t *n = node_new(ND_STR);
497 safe_strcpy(n->str, text, sizeof(n->str));
498 snprintf(n->asm_label, MAX_NAME, "__str_%d", p->next_str_id++);
499 type_t *arr = type_new(TY_ARRAY);
500 arr->base = g_char_type;
501 arr->array_len = (int)strlen(text) + 1;
502 n->type = arr;
503 return n;
504 }
505
506 static member_t *find_member(type_t *st, const char *name) {
507 for (member_t *m = st->members; m; m = m->next) if (strcmp(m->name, name) == 0) return m;
508 return NULL;
509 }
510
511 static node_t *parse_primary(parser_t *p) {
512 if (check(p, TK_NUM)) { node_t *n = make_num(advance(p).num); return n; }
513 if (check(p, TK_CHAR)) { node_t *n = make_num(advance(p).num); n->type = g_char_type; return n; }
514 if (check(p, TK_STR)) { return make_str(p, advance(p).text); }
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 /* Struct/union-returning call: needs a hidden temp slot in
552 * *this* function's frame to hold the result (the callee
553 * writes into it via a hidden pointer argument - see
554 * parse_func/codegen.c's ND_RETURN). Stashed in call->num
555 * (unused by ND_CALL otherwise) rather than
556 * call->frame_offset, which is_indirect_call above may
557 * already be using for the callee variable's own location. */
558 if (call->type->kind == TY_STRUCT || call->type->kind == TY_UNION) {
559 p->frame_size += type_size(call->type);
560 p->frame_size = (p->frame_size + 3) & ~3;
561 call->num = -p->frame_size;
562 }
563 return call;
564 }$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'sizeof\|is_kw(p' toolchain/cc/parser.c | head -30
63:static int is_kw(parser_t *p, const char *kw) {
67: if (is_kw(p, kw)) { advance(p); return 1; }
93: sym_t *s = calloc(1, sizeof(sym_t));
104: sym_t *s = calloc(1, sizeof(sym_t));
126:static type_t *parse_type_name(parser_t *p); /* for casts/sizeof(type) */
200: if (is_kw(p, "struct") || is_kw(p, "union")) {
201: int is_union = is_kw(p, "union");
222: tag_t *tg = calloc(1, sizeof(tag_t));
230: if (is_kw(p, "__attribute__")) {
232: if (is_kw(p, "packed")) { advance(p); st->is_packed = 1; }
254: tag_t *tg = calloc(1, sizeof(tag_t));
266: if (is_kw(p, "enum")) {
279: sym_t *s = calloc(1, sizeof(sym_t));
291: tag_t *tg = calloc(1, sizeof(tag_t));
343: member_t *m = calloc(1, sizeof(member_t));
359: if (is_kw(p, "void") && p->toks[p->pos + 1].kind == TK_RPAREN) { advance(p); return NULL; }
497: safe_strcpy(n->str, text, sizeof(n->str));
640: if (is_kw(p, "sizeof")) {
643: /* could be sizeof(type) or sizeof(expr) - peek for a type start */
822: * silently means sizeof(name) == 0. */
966: * positive array_len as size 0 - silently making sizeof(name) come
1004: sym_t *s = calloc(1, sizeof(sym_t));
1042: sym_t *s = calloc(1, sizeof(sym_t));600 n = mem;
601 } else if (match(p, TK_ARROW)) {
602 token_t id = expect(p, TK_IDENT);
603 member_t *m = find_member(n->type->base, id.text);
604 if (!m) { perr(p, "no such member"); return n; }
605 node_t *mem = node_new(ND_ARROW);
606 mem->a = n;
607 safe_strcpy(mem->name, id.text, MAX_NAME);
608 mem->type = m->type;
609 mem->num = m->offset;
610 n = mem;
611 } else if (match(p, TK_INC)) {
612 node_t *pi = node_new(ND_POSTINC); pi->a = n; pi->type = n->type; n = pi;
613 } else if (match(p, TK_DEC)) {
614 node_t *pd = node_new(ND_POSTDEC); pd->a = n; pd->type = n->type; n = pd;
615 } else break;
616 }
617 return n;
618 }
619
620 static int looks_like_type_name(parser_t *p) {
621 /* Used after '(' inside unary() to disambiguate a cast from a
622 * parenthesized expression. */
623 return is_type_start(p);
624 }
625
626 static node_t *parse_unary(parser_t *p) {
627 if (match(p, TK_PLUS)) return parse_cast(p);
628 if (match(p, TK_MINUS)) { node_t *n = node_new(ND_UNARY_MINUS); n->a = parse_cast(p); n->type = n->a->type; return n; }
629 if (match(p, TK_NOT)) { node_t *n = node_new(ND_NOT); n->a = parse_cast(p); n->type = g_int_type; return n; }
630 if (match(p, TK_TILDE)) { node_t *n = node_new(ND_BITNOT); n->a = parse_cast(p); n->type = n->a->type; return n; }
631 if (match(p, TK_AMP)) { node_t *n = node_new(ND_ADDR); n->a = parse_cast(p); n->type = ptr_to(n->a->type); return n; }
632 if (match(p, TK_STAR)) {
633 node_t *n = node_new(ND_DEREF);
634 n->a = parse_cast(p);
635 n->type = decay(n->a->type)->base;
636 return n;
637 }
638 if (match(p, TK_INC)) { node_t *n = node_new(ND_PREINC); n->a = parse_unary(p); n->type = n->a->type; return n; }
639 if (match(p, TK_DEC)) { node_t *n = node_new(ND_PREDEC); n->a = parse_unary(p); n->type = n->a->type; return n; }
640 if (is_kw(p, "sizeof")) {
641 advance(p);
642 if (check(p, TK_LPAREN) && (p->toks[p->pos + 1].kind == TK_IDENT) ) {
643 /* could be sizeof(type) or sizeof(expr) - peek for a type start */
644 int save = p->pos;
645 advance(p);
646 if (looks_like_type_name(p)) {
647 type_t *t = parse_type_name(p);
648 expect(p, TK_RPAREN);
649 return make_num(type_size(t));
650 }
651 p->pos = save;
652 }
653 node_t *operand = parse_unary(p);
654 return make_num(type_size(operand->type));
655 }
656 return parse_postfix(p);
657 }
658
659 static node_t *parse_cast(parser_t *p) {
660 if (check(p, TK_LPAREN)) {
661 int save = p->pos;
662 advance(p);
663 if (looks_like_type_name(p)) {
664 type_t *t = parse_type_name(p);
665 expect(p, TK_RPAREN);
666 node_t *n = node_new(ND_CAST);
667 n->a = parse_cast(p);
668 n->type = t;
669 return n;
670 }
671 p->pos = save;
672 }
673 return parse_unary(p);
674 }
675
676 static int is_ptr(type_t *t) { return t->kind == TY_PTR || t->kind == TY_ARRAY; }
677
678 static node_t *make_binop(tok_kind_t op, node_t *a, node_t *b) {
679 node_t *n = node_new(ND_BINOP);564 }
565 return resolve_ident(p, name, ident_tok);
566 }
567 if (match(p, TK_LPAREN)) {
568 node_t *n = parse_expr(p);
569 expect(p, TK_RPAREN);
570 return n;
571 }
572 perr(p, "expected expression");
573 return make_num(0);
574 }
575
576 static node_t *parse_postfix(parser_t *p) {
577 node_t *n = parse_primary(p);
578 for (;;) {
579 if (match(p, TK_LBRACKET)) {
580 node_t *idx = parse_expr(p);
581 expect(p, TK_RBRACKET);
582 node_t *add = node_new(ND_BINOP);
583 add->op = TK_PLUS;
584 add->a = n;
585 add->b = idx;
586 add->type = decay(n->type);
587 node_t *deref = node_new(ND_DEREF);
588 deref->a = add;
589 deref->type = add->type->base;
590 n = deref;
591 } else if (match(p, TK_DOT)) {
592 token_t id = expect(p, TK_IDENT);
593 member_t *m = find_member(n->type, id.text);
594 if (!m) { perr(p, "no such member"); return n; }
595 node_t *mem = node_new(ND_MEMBER);
596 mem->a = n;
597 safe_strcpy(mem->name, id.text, MAX_NAME);
598 mem->type = m->type;
599 mem->num = m->offset;
600 n = mem;
601 } else if (match(p, TK_ARROW)) {
602 token_t id = expect(p, TK_IDENT);
603 member_t *m = find_member(n->type->base, id.text);$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'TK_PLUSEQ\|ND_ASSIGN\|parse_assign\|resolve_ident' toolchain/cc/parser.c | head -40
123:static node_t *parse_assign(parser_t *p);
163: return eval_const_node(p, parse_assign(p));
481:static node_t *resolve_ident(parser_t *p, const char *name, token_t at) {
525: node_t *arg = parse_assign(p);
539: * reference would (see resolve_ident/codegen_addr) so
565: return resolve_ident(p, name, ident_tok);
733: t->c = parse_assign(p);
741: return k == TK_ASSIGN || k == TK_PLUSEQ || k == TK_MINUSEQ || k == TK_STAREQ || k == TK_SLASHEQ ||
745:static node_t *parse_assign(parser_t *p) {
749: node_t *rhs = parse_assign(p);
751: node_t *n = node_new(ND_ASSIGN);
755: node_t *n = node_new(ND_COMPOUND_ASSIGN);
766: * elements) - those call parse_assign directly for each element, so
769: node_t *n = parse_assign(p);
771: node_t *rhs = parse_assign(p);
802: return parse_assign(p);476 n->num = v;
477 n->type = g_int_type;
478 return n;
479 }
480
481 static node_t *resolve_ident(parser_t *p, const char *name, token_t at) {
482 sym_t *s = sym_find(p, name);
483 if (!s) { perr_at(p, at, "undeclared identifier"); return make_num(0); }
484 if (s->is_enum_const) return make_num(s->enum_value);
485 node_t *n = node_new(ND_VAR);
486 safe_strcpy(n->name, name, MAX_NAME);
487 n->type = s->type;
488 n->is_global = s->is_global;
489 n->is_static_local = s->is_static_local;
490 n->frame_offset = s->frame_offset;
491 safe_strcpy(n->asm_label, s->asm_label, MAX_NAME);
492 return n;
493 }
494
495 static node_t *make_str(parser_t *p, const char *text) {
496 node_t *n = node_new(ND_STR);
497 safe_strcpy(n->str, text, sizeof(n->str));
498 snprintf(n->asm_label, MAX_NAME, "__str_%d", p->next_str_id++);
499 type_t *arr = type_new(TY_ARRAY);
500 arr->base = g_char_type;
501 arr->array_len = (int)strlen(text) + 1;
502 n->type = arr;
503 return n;
504 }
505
506 static member_t *find_member(type_t *st, const char *name) {
507 for (member_t *m = st->members; m; m = m->next) if (strcmp(m->name, name) == 0) return m;
508 return NULL;
509 }
510
511 static node_t *parse_primary(parser_t *p) {676 static int is_ptr(type_t *t) { return t->kind == TY_PTR || t->kind == TY_ARRAY; }
677
678 static node_t *make_binop(tok_kind_t op, node_t *a, node_t *b) {
679 node_t *n = node_new(ND_BINOP);
680 n->op = op;
681 n->a = a;
682 n->b = b;
683 type_t *at = decay(a->type), *bt = decay(b->type);
684 if ((op == TK_PLUS || op == TK_MINUS) && is_ptr(at)) n->type = at;
685 else if (op == TK_PLUS && is_ptr(bt)) n->type = bt;
686 else n->type = g_int_type;
687 return n;
688 }
689
690 static node_t *parse_binop_level(parser_t *p, node_t *(*next)(parser_t *), const tok_kind_t *ops, int nops) {
691 node_t *n = next(p);
692 for (;;) {
693 int matched = 0;
694 for (int i = 0; i < nops; i++) {
695 if (check(p, ops[i])) {
696 tok_kind_t op = advance(p).kind;
697 n = make_binop(op, n, next(p));
698 matched = 1;
699 break;
700 }
701 }
702 if (!matched) return n;
703 }
704 }
705
706 static node_t *parse_mul(parser_t *p) { static const tok_kind_t o[] = {TK_STAR, TK_SLASH, TK_PERCENT}; return parse_binop_level(p, parse_cast, o, 3); }
707 static node_t *parse_add(parser_t *p) { static const tok_kind_t o[] = {TK_PLUS, TK_MINUS}; return parse_binop_level(p, parse_mul, o, 2); }
708 static node_t *parse_shift(parser_t *p) { static const tok_kind_t o[] = {TK_SHL, TK_SHR}; return parse_binop_level(p, parse_add, o, 2); }
709 static node_t *parse_rel(parser_t *p) { static const tok_kind_t o[] = {TK_LT, TK_LE, TK_GT, TK_GE}; return parse_binop_level(p, parse_shift, o, 4); }
710 static node_t *parse_eq(parser_t *p) { static const tok_kind_t o[] = {TK_EQ, TK_NEQ}; return parse_binop_level(p, parse_rel, o, 2); }
711 static node_t *parse_bitand(parser_t *p) { static const tok_kind_t o[] = {TK_AMP}; return parse_binop_level(p, parse_eq, o, 1); }
712 static node_t *parse_bitxor(parser_t *p) { static const tok_kind_t o[] = {TK_CARET}; return parse_binop_level(p, parse_bitand, o, 1); }
713 static node_t *parse_bitor(parser_t *p) { static const tok_kind_t o[] = {TK_PIPE}; return parse_binop_level(p, parse_bitxor, o, 1); }
714
715 static node_t *parse_logand(parser_t *p) {
716 node_t *n = parse_bitor(p);
717 while (match(p, TK_AND)) { node_t *r = parse_bitor(p); node_t *a = node_new(ND_LOGAND); a->a = n; a->b = r; a->type = g_int_type; n = a; }
718 return n;
719 }
720 static node_t *parse_logor(parser_t *p) {
721 node_t *n = parse_logand(p);
722 while (match(p, TK_OR)) { node_t *r = parse_logand(p); node_t *o = node_new(ND_LOGOR); o->a = n; o->b = r; o->type = g_int_type; n = o; }
723 return n;
724 }
725
726 static node_t *parse_ternary(parser_t *p) {
727 node_t *cond = parse_logor(p);
728 if (match(p, TK_QUESTION)) {
729 node_t *t = node_new(ND_TERNARY);
730 t->a = cond;
731 t->b = parse_expr(p);
732 expect(p, TK_COLON);
733 t->c = parse_assign(p);
734 t->type = t->b->type;
735 return t;
736 }
737 return cond;
738 }
739
740 static int is_assign_op(tok_kind_t k) {
741 return k == TK_ASSIGN || k == TK_PLUSEQ || k == TK_MINUSEQ || k == TK_STAREQ || k == TK_SLASHEQ ||
742 k == TK_PERCENTEQ || k == TK_ANDEQ || k == TK_OREQ || k == TK_XOREQ || k == TK_SHLEQ || k == TK_SHREQ;
743 }
744
745 static node_t *parse_assign(parser_t *p) {
746 node_t *lhs = parse_ternary(p);
747 if (is_assign_op(cur(p).kind)) {
748 tok_kind_t op = advance(p).kind;
749 node_t *rhs = parse_assign(p);
750 if (op == TK_ASSIGN) {
751 node_t *n = node_new(ND_ASSIGN);
752 n->a = lhs; n->b = rhs; n->type = lhs->type;
753 return n;
754 }
755 node_t *n = node_new(ND_COMPOUND_ASSIGN);
756 n->op = op;
757 n->a = lhs; n->b = rhs; n->type = lhs->type;
758 return n;
759 }
760 return lhs;
761 }
762
763 /* The comma *operator* (evaluate left, discard, evaluate right - e.g. a
764 * for-loop increment clause like `pn = pn->next, i++`), distinct from
765 * comma as a plain separator (call args, declarator lists, initializer$ cd /home/martin/workspace/prototypes/boot-to-claude sed -n '1,40p' toolchain/cc/codegen.c
#include "cc.h"
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
/* Naive, non-optimizing codegen: every intermediate expression value
* lives in EAX; binary operators spill the left operand to the real
* machine stack (push/pop) while the right operand is computed - no
* register allocation. Emits AS-syntax assembly text (mirrors
* `gcc -S | as`). See docs/toolchain.md for the calling convention and
* builtin mechanism. */
static FILE *g_out;
static cc_target_t g_target;
static int g_label_id;
static char g_cur_func[MAX_NAME];
typedef struct loop_ctx {
char break_label[MAX_NAME];
char continue_label[MAX_NAME];
struct loop_ctx *parent;
} loop_ctx_t;
static loop_ctx_t *g_loop;
static void out(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vfprintf(g_out, fmt, ap);
va_end(ap);
}
static void new_label(char *buf) {
snprintf(buf, MAX_NAME, ".L%d", g_label_id++);
}
static void codegen_expr(node_t *n);
static void codegen_addr(node_t *n);
static void codegen_stmt(node_t *n);$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n '"long"\|"short"\|"unsigned"\|"signed"\|"char"\|"int"\|is_type_start\|parse_type_spec' toolchain/cc/parser.c | head -40
170:static int is_type_start(parser_t *p) {
172: const char *kws[] = { "void", "char", "short", "int", "long", "signed", "unsigned",
180:static type_t *parse_type_spec(parser_t *p, int *is_static, int *is_extern) {
191: if (match_kw(p, "char")) { saw_char = 1; continue; }
192: if (match_kw(p, "short")) { saw_short = 1; continue; }
193: if (match_kw(p, "int")) { saw_int = 1; continue; }
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; }
327: type_t *base = parse_type_spec(p, NULL, NULL);
361: type_t *base = parse_type_spec(p, NULL, NULL);
450: type_t *base = parse_type_spec(p, NULL, NULL);
623: return is_type_start(p);
833: type_t *base = parse_type_spec(p, &is_static, &is_extern);
879: n->a = is_type_start(p) ? parse_local_decl_stmt(p) : NULL;
930: if (is_type_start(p)) return parse_local_decl_stmt(p);
1038: type_t *base = parse_type_spec(p, NULL, NULL);
1066: type_t *base = parse_type_spec(&p, &is_static, &is_extern);180 static type_t *parse_type_spec(parser_t *p, int *is_static, int *is_extern) {
181 int saw_void = 0, saw_char = 0, saw_short = 0, saw_int = 0, saw_long = 0, saw_unsigned = 0, saw_signed = 0;
182 type_t *named = NULL;
183 if (is_static) *is_static = 0;
184 if (is_extern) *is_extern = 0;
185
186 for (;;) {
187 if (match_kw(p, "static")) { if (is_static) *is_static = 1; continue; }
188 if (match_kw(p, "extern")) { if (is_extern) *is_extern = 1; continue; }
189 if (match_kw(p, "const") || match_kw(p, "volatile") || match_kw(p, "inline")) continue;
190 if (match_kw(p, "void")) { saw_void = 1; continue; }
191 if (match_kw(p, "char")) { saw_char = 1; continue; }
192 if (match_kw(p, "short")) { saw_short = 1; continue; }
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 /* Reuse the existing (possibly still-incomplete, from an
210 * earlier forward declaration) type_t instance rather than
211 * allocating a new one, so any typedef/pointer that
212 * already captured its address sees the real members once
213 * this definition completes it - see the forward-decl
214 * branch below. */
215 st = existing ? existing->type : type_new(is_union ? TY_UNION : TY_STRUCT);
216 safe_strcpy(st->tag, tagname, MAX_NAME);
217 /* Register the tag *before* parsing the body, not after -
218 * a self-referential member (`struct node *next;` inside
219 * `struct node { ... }`) needs to find its own tag while
220 * still inside its own member list. */
221 if (tagname[0] && !existing) {
222 tag_t *tg = calloc(1, sizeof(tag_t));
223 safe_strcpy(tg->name, tagname, MAX_NAME);
224 tg->type = st;$ cd /home/martin/workspace/prototypes/boot-to-claude grep -ohE '%[-+ 0#]*[0-9]*l?l?[a-zA-Z%]' toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c | sort | uniq -c | sort -rn
97 %s
36 %d
6 %lld
4 %+d
2 % n
1 %u
1 %c
1 %+lld
1 % r$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n '% n\|% r' toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c
toolchain/cc/parser.c:148: case TK_PERCENT: return r != 0 ? l % r : 0; toolchain/as/parser.c:438: uint32_t pad = (n - (cur % n)) % n;
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -ohE '%[-+ 0#]*[0-9]*(\.[0-9]+)?l?l?[a-zA-Z%]' toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c | sort -u echo "--- x/hex specifiers ---" grep -n '%x\|%X\|%02x\|%08x\|%p' toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c
% n % r %+d %+lld %c %d %lld %s %u --- x/hex specifiers ---
$ make --version | head -1
GNU Make 4.4.1
$ cd /home/martin/workspace/prototypes/boot-to-claude
grep -ohE '\b(feof|ferror|rewind|ungetc|getc|fgetc|clearerr|setvbuf|fflush|SEEK_SET|SEEK_CUR|SEEK_END|remove|unlink)\b' toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c | sort | uniq -c
echo "--- fopen modes ---"
grep -ohE 'fopen\([^)]*\)' toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c | sort -u
echo "--- fwrite/fread signatures sample ---"
grep -n 'fwrite(\|fread(' toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c | head -10
echo "--- struct stat usage ---"
grep -n 'struct stat\|stat(\|st_size\|st_mode' toolchain/ld/*.c 3 SEEK_END
3 SEEK_SET
--- fopen modes ---
fopen(cand, "rb")
fopen(outpath, "w")
fopen(path, "rb")
fopen(path, "wb")
--- fwrite/fread signatures sample ---
toolchain/as/bin_writer.c:10: size_t n = fwrite(ctx->sec_bytes[SEC_TEXT], 1, ctx->sec_len[SEC_TEXT], f);
toolchain/as/main.c:14: if (fread(buf, 1, (size_t)n, f) != (size_t)n) { perror("as: fread"); exit(1); }
toolchain/as/elf_writer.c:159: fwrite(&eh, sizeof(eh), 1, f);
toolchain/as/elf_writer.c:163: if (!SEC_IS_BSS[s]) fwrite(ctx->sec_bytes[s], 1, ctx->sec_len[s], f);
toolchain/as/elf_writer.c:167: if (reloc_for[s]) fwrite(rels[s], sizeof(Elf32_Rel), (size_t)nrels[s], f);
toolchain/as/elf_writer.c:169: fwrite(symtab, sizeof(Elf32_Sym), (size_t)nsym, f);
toolchain/as/elf_writer.c:170: fwrite(strtab.buf, 1, strtab.len, f);
toolchain/as/elf_writer.c:171: fwrite(shstrtab.buf, 1, shstrtab.len, f);
toolchain/as/elf_writer.c:177: fwrite(&sh, sizeof(sh), 1, f); /* NULL section */
toolchain/as/elf_writer.c:188: fwrite(&sh, sizeof(sh), 1, f);
--- struct stat usage ---$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'sys/stat\|S_IX\|S_IR\|S_IW\|chmod\|mkdir\|umask' toolchain/ld/*.c
toolchain/ld/link_static_exec.c:6:#include <sys/stat.h> toolchain/ld/link_static_exec.c:90: chmod(path, 0755);
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'strtoll' toolchain/*/*.c echo --- cat kernel/string.c 2>/dev/null | head -60 echo "--- string.h ---" cat kernel/string.h 2>/dev/null
toolchain/cc/lexer.c:106: t.num = strtoll(p + 2, NULL, 16);
toolchain/cc/lexer.c:108: t.num = strtoll(p, NULL, 10);
---
#include "string.h"
#include <stdint.h>
void *memcpy(void *dst, const void *src, size_t n) {
uint8_t *d = dst;
const uint8_t *s = src;
for (size_t i = 0; i < n; i++) {
d[i] = s[i];
}
return dst;
}
void *memset(void *dst, int val, size_t n) {
uint8_t *d = dst;
for (size_t i = 0; i < n; i++) {
d[i] = (uint8_t)val;
}
return dst;
}
size_t strlen(const char *s) {
size_t n = 0;
while (s[n]) {
n++;
}
return n;
}
int strcmp(const char *a, const char *b) {
while (*a && (*a == *b)) {
a++;
b++;
}
return (unsigned char)*a - (unsigned char)*b;
}
char *strchr(const char *s, int c) {
while (*s) {
if (*s == (char)c) {
return (char *)s;
}
s++;
}
return c == '\0' ? (char *)s : NULL;
}
char *itoa(int value, char *buf, int base) {
char tmp[34];
int i = 0;
int negative = (base == 10 && value < 0);
unsigned int uval = negative ? (unsigned int)(-value) : (unsigned int)value;
if (uval == 0) {
tmp[i++] = '0';
}
while (uval) {
int digit = (int)(uval % (unsigned int)base);
tmp[i++] = (char)(digit < 10 ? '0' + digit : 'a' + (digit - 10));
uval /= (unsigned int)base;
--- string.h ---
#pragma once
#include <stddef.h>
void *memcpy(void *dst, const void *src, size_t n);
void *memset(void *dst, int val, size_t n);
size_t strlen(const char *s);
int strcmp(const char *a, const char *b);
char *strchr(const char *s, int c);
/* Writes the decimal (base 10) or hex (base 16) representation of value
* into buf (caller-provided, must be large enough) and returns buf. */
char *itoa(int value, char *buf, int base);$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'idiv\|TK_SLASH\|TK_PERCENT\|is_unsigned\|TY_UINT\|unsigned' toolchain/cc/codegen.c toolchain/cc/cc.h | head -40
toolchain/cc/codegen.c:175: case TK_SLASH: out(" cdq\n idiv ecx\n"); return;
toolchain/cc/codegen.c:176: case TK_PERCENT: out(" cdq\n idiv ecx\n mov eax, edx\n"); return;
toolchain/cc/codegen.c:223: /* eax/ecx get clobbered (idiv even clobbers edx) while
toolchain/cc/codegen.c:226: * register copy of it wouldn't survive an idiv/cdq. */
toolchain/cc/codegen.c:241: case TK_SLASHEQ: out(" cdq\n idiv ecx\n"); break;
toolchain/cc/codegen.c:242: case TK_PERCENTEQ: out(" cdq\n idiv ecx\n mov eax, edx\n"); break;
toolchain/cc/codegen.c:807: out(" db %d\n", (int)(unsigned char)*p);
toolchain/cc/cc.h:28: TK_PLUS, TK_MINUS, TK_STAR, TK_SLASH, TK_PERCENT,
toolchain/cc/cc.h:29: TK_ASSIGN, TK_PLUSEQ, TK_MINUSEQ, TK_STAREQ, TK_SLASHEQ, TK_PERCENTEQ,
toolchain/cc/cc.h:101: int is_unsigned;$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'TK_DOT\|tok_kind_t' toolchain/cc/cc.h | head -10
27: TK_SEMI, TK_COMMA, TK_COLON, TK_QUESTION, TK_DOT, TK_ARROW, 36:} tok_kind_t; 39: tok_kind_t kind; 136: tok_kind_t op; /* ND_BINOP / ND_COMPOUND_ASSIGN */
1 #pragma once
2 /* CC: the self-hosted C compiler. See docs/toolchain.md for the "tinyC"
3 * dialect this supports and why. Emits AS-syntax assembly text (mirrors
4 * `gcc -S | as`), for two targets: --target=kernel (freestanding, no
5 * runtime) and --target=host (adds a _start that calls main then exits
6 * via a raw syscall - see builtins.c). Native host tool, not OS code. */
7
8 #include <stdint.h>
9 #include <stddef.h>
10
11 static inline void safe_strcpy(char *dst, const char *src, size_t dstsize) {
12 size_t i = 0;
13 for (; i + 1 < dstsize && src[i]; i++) dst[i] = src[i];
14 dst[i] = '\0';
15 }
16
17 #define MAX_NAME 64
18 #define MAX_TOKENS 65536
19 #define MAX_INCLUDE_DEPTH 16
20
21 /* ---------------- lexer ---------------- */
22
23 typedef enum {
24 TK_EOF, TK_IDENT, TK_NUM, TK_STR, TK_CHAR,
25 /* punctuation/operators */
26 TK_LPAREN, TK_RPAREN, TK_LBRACE, TK_RBRACE, TK_LBRACKET, TK_RBRACKET,
27 TK_SEMI, TK_COMMA, TK_COLON, TK_QUESTION, TK_DOT, TK_ARROW,
28 TK_PLUS, TK_MINUS, TK_STAR, TK_SLASH, TK_PERCENT,
29 TK_ASSIGN, TK_PLUSEQ, TK_MINUSEQ, TK_STAREQ, TK_SLASHEQ, TK_PERCENTEQ,
30 TK_ANDEQ, TK_OREQ, TK_XOREQ, TK_SHLEQ, TK_SHREQ,
31 TK_EQ, TK_NEQ, TK_LT, TK_LE, TK_GT, TK_GE,
32 TK_AND, TK_OR, TK_NOT, TK_AMP, TK_PIPE, TK_CARET, TK_TILDE,
33 TK_SHL, TK_SHR, TK_INC, TK_DEC,
34 /* preprocessor-only (consumed by the preprocessor, never reach the parser) */
35 TK_HASH, TK_NEWLINE,
36 } tok_kind_t;
37
38 typedef struct {
39 tok_kind_t kind;
40 char text[256]; /* TK_IDENT / TK_STR (decoded) */- TK_SEMI, TK_COMMA, TK_COLON, TK_QUESTION, TK_DOT, TK_ARROW, + TK_SEMI, TK_COMMA, TK_COLON, TK_QUESTION, TK_DOT, TK_ARROW, TK_ELLIPSIS,
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 "TWO('-', '>', TK_ARROW)\|case '\\.':" toolchain/cc/lexer.c144: TWO('-', '>', TK_ARROW) TWO('+', '=', TK_PLUSEQ) TWO('-', '=', TK_MINUSEQ)
170: case '.': k = TK_DOT; break;140
141 #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; }
142 TWO('=', '=', TK_EQ) TWO('!', '=', TK_NEQ) TWO('<', '=', TK_LE) TWO('>', '=', TK_GE)
143 TWO('&', '&', TK_AND) TWO('|', '|', TK_OR) TWO('+', '+', TK_INC) TWO('-', '-', TK_DEC)
144 TWO('-', '>', TK_ARROW) TWO('+', '=', TK_PLUSEQ) TWO('-', '=', TK_MINUSEQ)
145 TWO('*', '=', TK_STAREQ) TWO('/', '=', TK_SLASHEQ) TWO('%', '=', TK_PERCENTEQ)
146 TWO('&', '=', TK_ANDEQ) TWO('|', '=', TK_OREQ) TWO('^', '=', TK_XOREQ)
147 #undef TWO
148 if (c == '<' && peekc2(lx) == '<') {
149 if (lx->pos + 2 < lx->len && lx->src[lx->pos + 2] == '=') { lx->pos += 3; token_t t = mk(lx, TK_SHLEQ); t.bol = was_bol; return t; }
150 lx->pos += 2; token_t t = mk(lx, TK_SHL); t.bol = was_bol; return t;
151 }
152 if (c == '>' && peekc2(lx) == '>') {
153 if (lx->pos + 2 < lx->len && lx->src[lx->pos + 2] == '=') { lx->pos += 3; token_t t = mk(lx, TK_SHREQ); t.bol = was_bol; return t; }
154 lx->pos += 2; token_t t = mk(lx, TK_SHR); t.bol = was_bol; return t;
155 }
156
157 lx->pos++;
158 tok_kind_t k;
159 switch (c) {- if (c == '>' && peekc2(lx) == '>') {
- if (lx->pos + 2 < lx->len && lx->src[lx->pos + 2] == '=') { lx->pos += 3; token_t t = mk(lx, TK_SHREQ); t.bol = was_bol; return t; }
- lx->pos += 2; token_t t = mk(lx, TK_SHR); t.bol = was_bol; return t;
- }
+ if (c == '>' && peekc2(lx) == '>') {
+ if (lx->pos + 2 < lx->len && lx->src[lx->pos + 2] == '=') { lx->pos += 3; token_t t = mk(lx, TK_SHREQ); t.bol = was_bol; return t; }
+ lx->pos += 2; token_t t = mk(lx, TK_SHR); t.bol = was_bol; return t;
+ }
+ if (c == '.' && peekc2(lx) == '.' && lx->pos + 2 < lx->len && lx->src[lx->pos + 2] == '.') {
+ lx->pos += 3; token_t t = mk(lx, TK_ELLIPSIS); t.bol = was_bol; return t;
+ }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)
- if (check(p, TK_RPAREN)) return NULL;
- if (is_kw(p, "void") && p->toks[p->pos + 1].kind == TK_RPAREN) { advance(p); return NULL; }
- for (;;) {
- type_t *base = parse_type_spec(p, NULL, NULL);
+ if (check(p, TK_RPAREN)) return NULL;
+ if (is_kw(p, "void") && p->toks[p->pos + 1].kind == TK_RPAREN) { advance(p); return NULL; }
+ for (;;) {
+ /* `...` (variadic tail, e.g. `fprintf`'s or codegen.c's own
+ * `out(const char *fmt, ...)`): stops named-parameter parsing
+ * here with no further storage of its own - call sites don't
+ * check argument counts against a prototype (each call arg is
+ * parsed/pushed independently, see codegen_call_or_builtin), so
+ * nothing downstream needs to know this function is variadic
+ * beyond "stop expecting more named params". */
+ if (check(p, TK_ELLIPSIS)) { advance(p); break; }
+ type_t *base = parse_type_spec(p, NULL, 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)
- node_t *operand = parse_unary(p);
- return make_num(type_size(operand->type));
- }
- return parse_postfix(p);
- }
+ node_t *operand = parse_unary(p);
+ return make_num(type_size(operand->type));
+ }
+ /* va_start/va_arg/va_end: not real functions (va_arg's second
+ * argument is a type, and CC's preprocessor has no function-like
+ * macros to hide any of this behind - see toolchain/include/stdarg.h),
+ * so these are parsed directly, like sizeof above. va_list is just
+ * `char *`; a cdecl frame's named params sit below the varargs in
+ * ascending address order, so:
+ * va_start(ap, last) desugars to ap = (char*)&last + K
+ * va_arg(ap, T) desugars to *(T*)((ap += K) - K)
+ * (the compound-assign-then-subtract reads the *old* ap while still
+ * advancing it - the only way to get pre-increment-of-a-pointer
+ * semantics as a single expression without a temporary), where K is
+ * the (4-byte-rounded) size of the type being skipped/read. */
+ if (is_kw(p, "va_start")) {
+ advance(p);
+ expect(p, TK_LPAREN);
+ node_t *ap = parse_assign(p);
+ expect(p, TK_COMMA);
+ token_t id = expect(p, TK_IDENT);
+ node_t *last = resolve_ident(p, id.text, id);
+ expect(p, TK_RPAREN);
+ int k = (type_size(last->type) + 3) & ~3;
+ if (k < 4) k = 4;
+ node_t *addr = node_new(ND_ADDR);
+ addr->a = last;
+ addr->type = ptr_to(last->type);
+ node_t *cast = node_new(ND_CAST);
+ cast->a = addr;
+ cast->type = ptr_to(g_char_type);
+ node_t *sum = make_binop(TK_PLUS, cast, make_num(k));
+ node_t *assign = node_new(ND_ASSIGN);
+ assign->a = ap;
+ assign->b = sum;
+ assign->type = ap->type;
+ return assign;
+ }
+ if (is_kw(p, "va_arg")) {
+ advance(p);
+ expect(p, TK_LPAREN);
+ node_t *ap = parse_assign(p);
+ expect(p, TK_COMMA);
+ type_t *t = parse_type_name(p);
+ expect(p, TK_RPAREN);
+ int k = (type_size(t) + 3) & ~3;
+ if (k < 4) k = 4;
+ node_t *advance_ap = node_new(ND_COMPOUND_ASSIGN);
+ advance_ap->op = TK_PLUSEQ;
+ advance_ap->a = ap;
+ advance_ap->b = make_num(k);
+ advance_ap->type = ap->type;
+ node_t *old = make_binop(TK_MINUS, advance_ap, make_num(k));
+ node_t *cast = node_new(ND_CAST);
+ cast->a = old;
+ cast->type = ptr_to(t);
+ node_t *deref = node_new(ND_DEREF);
+ deref->a = cast;
+ deref->type = t;
+ return deref;
+ }
+ if (is_kw(p, "va_end")) {
+ advance(p);
+ expect(p, TK_LPAREN);
+ parse_assign(p); /* side-effect-free (a bare va_list variable) - discarded */
+ expect(p, TK_RPAREN);
+ return make_num(0);
+ }
+ return parse_postfix(p);
+ }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)
55 static token_t cur(parser_t *p) { return p->toks[p->pos]; }
56 static token_t advance(parser_t *p) { token_t t = p->toks[p->pos]; if (p->toks[p->pos].kind != TK_EOF) p->pos++; return t; }
57 static int check(parser_t *p, tok_kind_t k) { return cur(p).kind == k; }
58 static int match(parser_t *p, tok_kind_t k) { if (check(p, k)) { advance(p); return 1; } return 0; }
59 static token_t expect(parser_t *p, tok_kind_t k) {
60 if (!check(p, k)) perr(p, "unexpected token");
61 return advance(p);
62 }
63 static int is_kw(parser_t *p, const char *kw) {
64 return check(p, TK_IDENT) && strcmp(cur(p).text, kw) == 0;
65 }
66 static int match_kw(parser_t *p, const char *kw) {
67 if (is_kw(p, kw)) { advance(p); return 1; }
68 return 0;
69 }$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n '^static node_t \*make_binop\|^static node_t \*make_num\|make_binop(parser_t\|static node_t \*parse_assign(parser_t \*p);\|static type_t \*parse_type_name(parser_t \*p);' toolchain/cc/parser.c | head -20
123:static node_t *parse_assign(parser_t *p);
126:static type_t *parse_type_name(parser_t *p); /* for casts/sizeof(type) */
482:static node_t *make_num(int64_t v) {
751:static node_t *make_binop(tok_kind_t op, node_t *a, node_t *b) {118 } 119 120 /* ---------------- expressions (forward decls) ---------------- */ 121 122 static node_t *parse_expr(parser_t *p); 123 static node_t *parse_assign(parser_t *p); 124 static node_t *parse_cast(parser_t *p); 125 static node_t *parse_unary(parser_t *p); 126 static type_t *parse_type_name(parser_t *p); /* for casts/sizeof(type) */ 127 static node_t *parse_stmt(parser_t *p); 128 static node_t *parse_block(parser_t *p); 129
- static type_t *parse_type_name(parser_t *p); /* for casts/sizeof(type) */ - static node_t *parse_stmt(parser_t *p); - static node_t *parse_block(parser_t *p); + static type_t *parse_type_name(parser_t *p); /* for casts/sizeof(type) */ + static node_t *parse_stmt(parser_t *p); + static node_t *parse_block(parser_t *p); + static node_t *make_binop(tok_kind_t op, node_t *a, node_t *b); /* for va_start/va_arg desugaring */
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)
$ mkdir -p /home/martin/workspace/prototypes/boot-to-claude/toolchain/include/sys mkdir -p /home/martin/workspace/prototypes/boot-to-claude/toolchain/hostlib ls /home/martin/workspace/prototypes/boot-to-claude/toolchain/include
stddef.h stdint.h sys
#pragma once /* va_list is just a byte pointer into the caller's cdecl argument area - * see toolchain/cc/parser.c for how va_start/va_arg/va_end are actually * implemented (as parser-level desugaring, not macros - CC's * preprocessor has no function-like macros, and va_arg's second * argument is a type name anyway, which no ordinary function/macro call * could accept). This header exists only so `va_list ap;` has a type to * name; va_start/va_arg/va_end themselves need no declaration here. */ typedef char *va_list;
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/toolchain/include/stdarg.h (file state is current in your context — no need to Read it back)
#pragma once
#include <stddef.h>
/* CC's own freestanding string.h - just what toolchain/{as,cc,ld} and
* their hostlib actually call (see docs/toolchain.md, Phase T8). Not
* shared with kernel/string.h: that one's a smaller, kernel-only subset
* and the two are compiled for different targets. */
void *memcpy(void *dst, const void *src, size_t n);
void *memset(void *dst, int val, size_t n);
size_t strlen(const char *s);
int strcmp(const char *a, const char *b);
int strncmp(const char *a, const char *b, size_t n);
char *strcpy(char *dst, const char *src);
char *strchr(const char *s, int c);
char *strrchr(const char *s, int c);
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/toolchain/include/string.h (file state is current in your context — no need to Read it back)
#pragma once int isalpha(int c); int isdigit(int c); int isalnum(int c); int tolower(int c);
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/toolchain/include/ctype.h (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'int64_t\|num;' toolchain/cc/cc.h | head -20
41: int64_t num; /* TK_NUM / TK_CHAR */ 138: int64_t num;
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -rn 'int64_t\|uint64_t' toolchain/as/*.c toolchain/as/*.h toolchain/cc/*.c toolchain/cc/*.h toolchain/ld/*.c toolchain/ld/*.h
toolchain/as/lexer.c:33:static int64_t parse_number(const char *text, int len) {
toolchain/as/lexer.c:35: int64_t v = 0;
toolchain/as/lexer.c:51: int64_t v = 0;
toolchain/as/lexer.c:58: int64_t v = 0;
toolchain/as/parser.c:106: int64_t val; /* constant, or addend when has_sym */
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:141: int64_t diff = (int64_t)sa->offset - (int64_t)sb->offset;
toolchain/as/parser.c:177: return val_const((int64_t)ps->offsets[ps->cur_section]);
toolchain/as/as.h:43: int64_t equ_value; /* valid if is_equ */
toolchain/as/as.h:72: int64_t disp; /* constant part; for OPND_IMM with no symbol, this is the whole value */
toolchain/as/as.h:74: int64_t far_seg; /* OPND_FAR only: segment selector constant part */
toolchain/as/as.h:130: int64_t number;
toolchain/cc/cc.h:41: int64_t num; /* TK_NUM / TK_CHAR */
toolchain/cc/cc.h:138: int64_t num;
toolchain/cc/parser.c:18: int64_t enum_value;
toolchain/cc/parser.c:136:static int64_t eval_const_node(parser_t *p, node_t *n) {
toolchain/cc/parser.c:143: int64_t l = eval_const_node(p, n->a), r = eval_const_node(p, n->b);
toolchain/cc/parser.c:163:static int64_t parse_const_expr(parser_t *p) {
toolchain/cc/parser.c:276: int64_t val = 0;
toolchain/cc/parser.c:483:static node_t *make_num(int64_t v) {
toolchain/as/encode.c:71:static int64_t resolve_abs(asm_ctx_t *ctx, int is_bin, const char *sym, int64_t disp,
toolchain/as/encode.c:81: return (int64_t)ctx->org + (int64_t)s->offset + disp;
toolchain/as/encode.c:103:static int64_t resolve_pcrel(asm_ctx_t *ctx, int is_bin, const char *sym, int64_t disp,
toolchain/as/encode.c:104: section_kind_t cur_sec, int64_t next_ip_rel,
toolchain/as/encode.c:111: int64_t next_ip = is_bin ? (int64_t)ctx->org + next_ip_rel : next_ip_rel;
toolchain/as/encode.c:120: int64_t target = (int64_t)ctx->org + (int64_t)s->offset + disp;
toolchain/as/encode.c:121: int64_t next_ip = (int64_t)ctx->org + next_ip_rel;
toolchain/as/encode.c:125: return ((int64_t)s->offset + disp) - next_ip_rel;
toolchain/as/encode.c:187: int64_t addr = resolve_abs(ctx, is_bin, rm->sym, rm->disp, patch_sec, patch_off, strict);
toolchain/as/encode.c:218: int64_t v = a->has_sym
toolchain/as/encode.c:228: for (int64_t i = 0; i < a->disp; i++) e8(&e, 0);
toolchain/as/encode.c:233: for (int64_t i = 0; i < a->disp; i++) e8(&e, (uint8_t)b->disp);
toolchain/as/encode.c:257: int64_t v = b->has_sym
toolchain/as/encode.c:338: int64_t v = b->has_sym ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + 1, strict) : b->disp;
toolchain/as/encode.c:344: int64_t v = b->has_sym ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + 1, strict) : b->disp;
toolchain/as/encode.c:351: int64_t v = b->has_sym ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + 2, strict) : b->disp;
toolchain/as/encode.c:357: int64_t v = b->has_sym
toolchain/as/encode.c:416: int64_t v = b->has_sym ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + (uint32_t)e.len + 2, strict) : b->disp;
toolchain/as/encode.c:424: int64_t v = b->has_sym ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + (uint32_t)e.len + 4, strict) : b->disp;
toolchain/as/encode.c:468: int64_t v = b->has_sym
toolchain/as/encode.c:495: int64_t v = c->has_sym
toolchain/as/encode.c:584: int64_t rel = resolve_pcrel(ctx, is_bin, a->sym, a->disp, st->section,
toolchain/as/encode.c:585: (int64_t)st->offset + 4, st->section, st->offset + 2, strict, 2);
toolchain/as/encode.c:589: int64_t rel = resolve_pcrel(ctx, is_bin, a->sym, a->disp, st->section,
toolchain/as/encode.c:590: (int64_t)st->offset + 6, st->section, st->offset + 2, strict, 4);
toolchain/as/encode.c:599: int64_t rel = resolve_pcrel(ctx, is_bin, a->sym, a->disp, st->section,
toolchain/as/encode.c:600: (int64_t)st->offset + 6, st->section, st->offset + 2, strict, 4);
toolchain/as/encode.c:619: int64_t off = resolve_abs(ctx, is_bin, a->sym, a->disp, st->section, off_patch_off, strict);
toolchain/as/encode.c:621: int64_t seg = a->far_seg_has_sym
toolchain/as/encode.c:629: int64_t rel = resolve_pcrel(ctx, is_bin, a->sym, a->disp, st->section,
toolchain/as/encode.c:630: (int64_t)st->offset + 3, st->section, st->offset + 1, strict, 2);
toolchain/as/encode.c:634: int64_t rel = resolve_pcrel(ctx, is_bin, a->sym, a->disp, st->section,
toolchain/as/encode.c:635: (int64_t)st->offset + 5, st->section, st->offset + 1, strict, 4);
toolchain/as/encode.c:652: int64_t rel = resolve_pcrel(ctx, is_bin, a->sym, a->disp, st->section,
toolchain/as/encode.c:653: (int64_t)st->offset + 3, st->section, st->offset + 1, strict, 2);
toolchain/as/encode.c:657: int64_t rel = resolve_pcrel(ctx, is_bin, a->sym, a->disp, st->section,
toolchain/as/encode.c:658: (int64_t)st->offset + 5, st->section, st->offset + 1, strict, 4);
toolchain/as/encode.c:667: int64_t v = a->has_sym ? resolve_abs(ctx, is_bin, a->sym, a->disp, st->section, st->offset + 1, strict) : a->disp;1 #pragma once 2 /* Hand-rolled freestanding stdint.h for CC - just the typedefs this 3 * codebase actually uses (no 64-bit types, so CC's type-specifier 4 * grammar never needs to support `long`). Real gcc bundles one of these 5 * for free; CC needs its own since it doesn't ship a libc. */ 6 7 typedef signed char int8_t; 8 typedef unsigned char uint8_t; 9 typedef signed short int16_t; 10 typedef unsigned short uint16_t; 11 typedef signed int int32_t; 12 typedef unsigned int uint32_t; 13
- #pragma once - /* Hand-rolled freestanding stdint.h for CC - just the typedefs this - * codebase actually uses (no 64-bit types, so CC's type-specifier - * grammar never needs to support `long`). Real gcc bundles one of these - * for free; CC needs its own since it doesn't ship a libc. */ - - typedef signed char int8_t; - typedef unsigned char uint8_t; - typedef signed short int16_t; - typedef unsigned short uint16_t; - typedef signed int int32_t; - typedef unsigned int uint32_t; + #pragma once + /* Hand-rolled freestanding stdint.h for CC - just the typedefs this + * codebase actually uses (no 64-bit types, so CC's type-specifier + * grammar never needs to support `long`). Real gcc bundles one of these + * for free; CC needs its own since it doesn't ship a libc. */ + + typedef signed char int8_t; + typedef unsigned char uint8_t; + typedef signed short int16_t; + typedef unsigned short uint16_t; + typedef signed int int32_t; + typedef unsigned int uint32_t; + + /* Same-size alias, not a real 64-bit type - CC's own source (as/cc/ld, + * Phase T8's self-hosting target) declares plenty of int64_t-typed + * fields (relocation offsets, token values), but every value ever + * stored in them fits comfortably in 32 bits for this project's tiny + * disk images and source files. Exactly the same reasoning as `long` + * aliasing to a 4-byte int in parser.c's parse_type_spec - no real + * 64-bit arithmetic exists anywhere in this codebase. */ + typedef int32_t int64_t; + typedef uint32_t uint64_t;
The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/include/stdint.h has been updated successfully. (file state is current in your context — no need to Read it back)
#pragma once #include <stddef.h> void *malloc(size_t size); void *calloc(size_t n, size_t size); void free(void *p); void exit(int code); /* Only base 10/16 are ever passed (see toolchain/cc/lexer.c) and every * call site passes NULL for endptr - both hostlib/stdlib.c and the * type this returns reflect that (see stdint.h: int64_t is a 32-bit * alias here, so this never actually returns a true 64-bit value). */ int64_t strtoll(const char *s, char **endptr, int base);
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/toolchain/include/stdlib.h (file state is current in your context — no need to Read it back)
- #pragma once - #include <stddef.h> + #pragma once + #include <stddef.h> + #include <stdint.h>
The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/include/stdlib.h has been updated successfully. (file state is current in your context — no need to Read it back)
#pragma once
#include <stddef.h>
#include <stdarg.h>
/* Minimal buffered-in-name-only FILE - every read/write is a direct
* __sys_read/__sys_write, see toolchain/hostlib/stdio.c. Just enough of
* real stdio for toolchain/{as,cc,ld}'s own source (file I/O + a small
* printf-format subset: %s %d %u %c, the '+' flag, and 'l'/'ll' length
* modifiers that are parsed and ignored - see hostlib/stdio.c). */
typedef struct FILE {
int fd;
int eof;
int err;
} FILE;
extern FILE g_stdout_file;
extern FILE g_stderr_file;
#define stdout (&g_stdout_file)
#define stderr (&g_stderr_file)
#define SEEK_SET 0
#define SEEK_CUR 1
#define SEEK_END 2
#define EOF (-1)
FILE *fopen(const char *path, const char *mode);
size_t fread(void *ptr, size_t size, size_t n, FILE *f);
size_t fwrite(const void *ptr, size_t size, size_t n, FILE *f);
int fclose(FILE *f);
int fseek(FILE *f, long offset, int whence);
long ftell(FILE *f);
int fputc(int c, FILE *f);
int fprintf(FILE *f, const char *fmt, ...);
int vfprintf(FILE *f, const char *fmt, va_list ap);
int printf(const char *fmt, ...);
int snprintf(char *buf, size_t n, const char *fmt, ...);
void perror(const char *msg);
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/toolchain/include/stdio.h (file state is current in your context — no need to Read it back)
#pragma once /* Only chmod (see toolchain/ld/link_static_exec.c, marking its output * executable) - no struct stat anywhere in this codebase. */ int chmod(const char *path, int mode);
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/toolchain/include/sys/stat.h (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'fopen\|snprintf\|include_dir\|resolve_include\|search' toolchain/cc/preprocessor.c | head -30
7:const char *g_include_dirs[MAX_INCLUDE_DIRS];
8:int g_n_include_dirs = 0;
47: FILE *f = fopen(path, "rb");
76:static int resolve_include(const char *cur_dir, const char *name, int is_quote, char *out, size_t outsz) {
80: FILE *f = fopen(cand, "rb");
83: for (int i = 0; i < g_n_include_dirs; i++) {
84: join_path(cand, sizeof(cand), g_include_dirs[i], name);
85: FILE *f = fopen(cand, "rb");
141: if (!resolve_include(cur_dir, path_tok.text, is_quote, resolved, sizeof(resolved))) {- "__sys_open", "__sys_read", "__sys_write", "__sys_lseek", "__sys_close", "__sys_exit", - NULL, + "__sys_open", "__sys_read", "__sys_write", "__sys_lseek", "__sys_close", "__sys_exit", + "__sys_brk", "__sys_chmod", + NULL,
The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/builtins.c has been updated successfully. (file state is current in your context — no need to Read it back)
- if (strcmp(name, "__sys_lseek") == 0) {
- emit_arg(nth(args, 0)); emitf(" push eax\n"); /* fd */
- emit_arg(nth(args, 1)); emitf(" push eax\n"); /* offset */
- emit_arg(nth(args, 2)); emitf(" mov edx, eax\n"); /* whence */
- emitf(" pop ecx\n pop ebx\n mov eax, 19\n int 0x80\n");
- return 1;
- }
-
- return 0;
+ if (strcmp(name, "__sys_lseek") == 0) {
+ emit_arg(nth(args, 0)); emitf(" push eax\n"); /* fd */
+ emit_arg(nth(args, 1)); emitf(" push eax\n"); /* offset */
+ emit_arg(nth(args, 2)); emitf(" mov edx, eax\n"); /* whence */
+ emitf(" pop ecx\n pop ebx\n mov eax, 19\n int 0x80\n");
+ return 1;
+ }
+ if (strcmp(name, "__sys_brk") == 0) {
+ /* hostlib.c's malloc: pass 0 to query the current break, or a
+ * target address to extend it - both are the raw brk(2) ABI. */
+ emit_arg(nth(args, 0)); emitf(" mov ebx, eax\n mov eax, 45\n int 0x80\n");
+ return 1;
+ }
+ if (strcmp(name, "__sys_chmod") == 0) {
+ emit_arg(nth(args, 0)); emitf(" push eax\n"); /* path */
+ emit_arg(nth(args, 1)); emitf(" mov ecx, eax\n"); /* mode */
+ emitf(" pop ebx\n mov eax, 15\n int 0x80\n");
+ return 1;
+ }
+
+ return 0;The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/cc/builtins.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 -rn '__sys_\|__builtin_' tools/mkfs_tinyfs.c kernel/fs/tinyfs.c 2>/dev/null | head -20 echo --- grep -rln '__sys_open\|__sys_read' --include=*.h .
tools/mkfs_tinyfs.c:7: * Libc-free: uses only the __sys_* raw-syscall names CC's builtins.c
tools/mkfs_tinyfs.c:11: * current interim GCC-based build, __sys_open/read/write/lseek/close/
tools/mkfs_tinyfs.c:26: __sys_write(2, msg, strlen(msg));
tools/mkfs_tinyfs.c:27: __sys_exit(1);
tools/mkfs_tinyfs.c:34: if (__sys_lseek(g_disk_fd, (int)offset, SEEK_SET) < 0 ||
tools/mkfs_tinyfs.c:35: __sys_read(g_disk_fd, buf, TINYFS_BLOCK_SIZE) != TINYFS_BLOCK_SIZE) {
tools/mkfs_tinyfs.c:42: if (__sys_lseek(g_disk_fd, (int)offset, SEEK_SET) < 0 ||
tools/mkfs_tinyfs.c:43: __sys_write(g_disk_fd, buf, TINYFS_BLOCK_SIZE) != TINYFS_BLOCK_SIZE) {
tools/mkfs_tinyfs.c:57: int fd = __sys_open(host_path, O_RDONLY);
tools/mkfs_tinyfs.c:62: int n = __sys_read(fd, buf, sizeof(buf));
tools/mkfs_tinyfs.c:63: __sys_close(fd);
tools/mkfs_tinyfs.c:77: g_disk_fd = __sys_open(argv[1], O_RDWR);
tools/mkfs_tinyfs.c:103: __sys_close(g_disk_fd);
tools/mkfs_tinyfs.c:104: __sys_write(1, "mkfs_tinyfs: done\n", 19);
tools/mkfs_tinyfs.c:105: __sys_exit(0);
---
toolchain/include/stdio.h
tools/host_syscalls.h1 #pragma once 2 /* Declares the raw Linux x86-32 syscalls host tools use in place of 3 * libc file I/O (see docs/toolchain.md and tools/mkfs_tinyfs.c). Under 4 * CC (--target=host), calls to these exact names are recognized in 5 * codegen and compiled directly to `int 0x80` - no function body is 6 * ever compiled or linked for them, matching kernel/builtins.h's 7 * __builtin_* mechanism. Under the current interim GCC-based build, 8 * real definitions live in tools/host_syscalls_shim.c. */ 9 10 int __sys_open(const char *path, int flags); 11 int __sys_read(int fd, void *buf, unsigned int count); 12 int __sys_write(int fd, const void *buf, unsigned int count); 13 int __sys_lseek(int fd, int offset, int whence); 14 int __sys_close(int fd); 15 void __sys_exit(int code); 16
#pragma once
/* Raw Linux x86-32 syscalls (see tools/host_syscalls.h for the original
* of this pattern) - CC's builtins.c recognizes calls to these exact
* names and compiles them directly to `int 0x80`, no function body ever
* linked. Only toolchain/hostlib/*.c calls these directly; every other
* toolchain/{as,cc,ld} source file goes through the ordinary stdio/
* stdlib API instead (see docs/toolchain.md, Phase T8). */
int __sys_open(const char *path, int flags);
int __sys_read(int fd, void *buf, unsigned int count);
int __sys_write(int fd, const void *buf, unsigned int count);
int __sys_lseek(int fd, int offset, int whence);
int __sys_close(int fd);
void __sys_exit(int code);
void *__sys_brk(void *addr);
int __sys_chmod(const char *path, int mode);
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/toolchain/include/hostsys.h (file state is current in your context — no need to Read it back)
#include <string.h>
/* toolchain/{as,cc,ld}'s own string.h implementation, for --target=host
* builds (see docs/toolchain.md, Phase T8). Deliberately not shared with
* kernel/string.c: same simple algorithms, different target/header. */
void *memcpy(void *dst, const void *src, size_t n) {
uint8_t *d = dst;
const uint8_t *s = src;
for (size_t i = 0; i < n; i++) d[i] = s[i];
return dst;
}
void *memset(void *dst, int val, size_t n) {
uint8_t *d = dst;
for (size_t i = 0; i < n; i++) d[i] = (uint8_t)val;
return dst;
}
size_t strlen(const char *s) {
size_t n = 0;
while (s[n]) n++;
return n;
}
int strcmp(const char *a, const char *b) {
while (*a && (*a == *b)) { a++; b++; }
return (unsigned char)*a - (unsigned char)*b;
}
int strncmp(const char *a, const char *b, size_t n) {
for (size_t i = 0; i < n; i++) {
if (a[i] != b[i] || a[i] == '\0') return (unsigned char)a[i] - (unsigned char)b[i];
}
return 0;
}
char *strcpy(char *dst, const char *src) {
size_t i = 0;
while (src[i]) { dst[i] = src[i]; i++; }
dst[i] = '\0';
return dst;
}
char *strchr(const char *s, int c) {
while (*s) {
if (*s == (char)c) return (char *)s;
s++;
}
return c == '\0' ? (char *)s : NULL;
}
char *strrchr(const char *s, int c) {
const char *last = NULL;
while (*s) {
if (*s == (char)c) last = s;
s++;
}
if (c == '\0') return (char *)s;
return (char *)last;
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/toolchain/hostlib/string.c (file state is current in your context — no need to Read it back)
- #include <string.h> + #include <string.h> + #include <stdint.h>
The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/hostlib/string.c has been updated successfully. (file state is current in your context — no need to Read it back)
#include <ctype.h>
int isalpha(int c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); }
int isdigit(int c) { return c >= '0' && c <= '9'; }
int isalnum(int c) { return isalpha(c) || isdigit(c); }
int tolower(int c) { return (c >= 'A' && c <= 'Z') ? c - 'A' + 'a' : c; }
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/toolchain/hostlib/ctype.c (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'ND_BINOP\|type_size(n->a->type\|elem_size\|scale' toolchain/cc/codegen.c | head -20
155: /* Pointer arithmetic: scale whichever side is the integer operand. 162: /* eax=int, ecx=pointer - scale eax then swap via the stack 164: * the pointer and ecx the scaled offset, matching every 253: case ND_BINOP: 302: int step = (n->a->type->kind == TY_PTR) ? type_size(n->a->type->base) : 1;
150 int a_is_ptr = n->a->type->kind == TY_PTR || n->a->type->kind == TY_ARRAY;
151 codegen_expr(n->b);
152 out(" push eax\n");
153 codegen_expr(n->a);
154 out(" pop ecx\n");
155 /* Pointer arithmetic: scale whichever side is the integer operand.
156 * eax=a, ecx=b at this point (per the evaluation order above). */
157 if ((n->op == TK_PLUS || n->op == TK_MINUS) && (n->type->kind == TY_PTR || n->type->kind == TY_ARRAY)) {
158 int sz = type_size(n->type->base);
159 if (a_is_ptr) {
160 if (sz > 1) out(" imul ecx, ecx, %d\n", sz);
161 } else {
162 /* eax=int, ecx=pointer - scale eax then swap via the stack
163 * (no xchg instruction in this ISA) so eax ends up holding
164 * the pointer and ecx the scaled offset, matching every
165 * other case's eax-is-left-operand convention below. */
166 if (sz > 1) out(" imul eax, eax, %d\n", sz);
167 out(" push eax\n mov eax, ecx\n pop ecx\n");
168 }
169 }
170
171 switch (n->op) {
172 case TK_PLUS: out(" add eax, ecx\n"); return;
173 case TK_MINUS: out(" sub eax, ecx\n"); return;
174 case TK_STAR: out(" imul eax, ecx\n"); return;
175 case TK_SLASH: out(" cdq\n idiv ecx\n"); return;
176 case TK_PERCENT: out(" cdq\n idiv ecx\n mov eax, edx\n"); return;
177 case TK_AMP: out(" and eax, ecx\n"); return;
178 case TK_PIPE: out(" or eax, ecx\n"); return;
179 case TK_CARET: out(" xor eax, ecx\n"); return;#include <stdlib.h>
#include <string.h>
#include <hostsys.h>
/* No real free (a bump allocator via brk(2) - see docs/toolchain.md,
* Phase T8): as/cc/ld are short-lived, single-pass build tools that
* never run long enough for that to matter, same spirit as the
* non-optimizing single-pass codegen elsewhere in CC. */
static char *g_heap_cur;
static char *g_heap_end;
void *malloc(size_t size) {
size_t sz = (size + 15) & ~(size_t)15;
if (!g_heap_cur) {
g_heap_cur = (char *)__sys_brk((void *)0);
g_heap_end = g_heap_cur;
}
if (g_heap_cur + sz > g_heap_end) {
size_t grow = sz > 65536 ? sz : 65536;
g_heap_end = (char *)__sys_brk((void *)(g_heap_end + grow));
}
void *p = g_heap_cur;
g_heap_cur += sz;
return p;
}
void *calloc(size_t n, size_t size) {
size_t total = n * size;
void *p = malloc(total);
memset(p, 0, total);
return p;
}
void free(void *p) {
(void)p;
}
void exit(int code) {
__sys_exit(code);
}
int64_t strtoll(const char *s, char **endptr, int base) {
int64_t v = 0;
int neg = 0;
if (*s == '-') { neg = 1; s++; }
else if (*s == '+') { s++; }
while (*s) {
char c = *s;
int d;
if (c >= '0' && c <= '9') d = c - '0';
else if (c >= 'a' && c <= 'f') d = c - 'a' + 10;
else if (c >= 'A' && c <= 'F') d = c - 'A' + 10;
else break;
if (d >= base) break;
v = v * base + d;
s++;
}
if (endptr) *endptr = (char *)s;
return neg ? -v : v;
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/toolchain/hostlib/stdlib.c (file state is current in your context — no need to Read it back)
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <hostsys.h>
/* toolchain/{as,cc,ld}'s own stdio.h implementation, for --target=host
* builds (see docs/toolchain.md, Phase T8). Every FILE op is a direct
* raw syscall - no buffering, since none of these build tools are
* performance-sensitive enough to need it.
*
* fmt_core implements exactly the printf-format subset toolchain/*.c
* actually uses (surveyed before writing this): %s %d %u %c, the '+'
* flag, and 'l'/'ll' length modifiers - which are parsed and silently
* ignored, always reading a plain 4-byte value regardless. That matches
* stdint.h's int64_t/long being 4-byte aliases here (see its comment):
* a caller casting a value to `long long` for a `%lld` conversion is
* still only ever pushing 4 real bytes, so reading 4 bytes back here is
* the only self-consistent choice, not a rounding-down of something
* larger. */
FILE g_stdout_file = { 1, 0, 0 };
FILE g_stderr_file = { 2, 0, 0 };
static int fmt_core(char *buf, size_t bufsize, const char *fmt, va_list ap) {
size_t o = 0;
for (const char *p = fmt; *p; p++) {
if (*p != '%') {
if (o + 1 < bufsize) buf[o] = *p;
o++;
continue;
}
p++;
int plus = 0;
if (*p == '+') { plus = 1; p++; }
while (*p == 'l') p++;
if (*p == 's') {
const char *s = va_arg(ap, const char *);
while (*s) {
if (o + 1 < bufsize) buf[o] = *s;
o++;
s++;
}
} else if (*p == 'c') {
int c = va_arg(ap, int);
if (o + 1 < bufsize) buf[o] = (char)c;
o++;
} else if (*p == 'd') {
int v = va_arg(ap, int);
int neg = v < 0;
unsigned int uv = neg ? (unsigned int)(-v) : (unsigned int)v;
char tmp[16];
int ti = 0;
if (uv == 0) tmp[ti++] = '0';
while (uv) { tmp[ti++] = (char)('0' + uv % 10); uv /= 10; }
if (neg) { if (o + 1 < bufsize) buf[o] = '-'; o++; }
else if (plus) { if (o + 1 < bufsize) buf[o] = '+'; o++; }
while (ti > 0) { ti--; if (o + 1 < bufsize) buf[o] = tmp[ti]; o++; }
} else if (*p == 'u') {
unsigned int v = va_arg(ap, unsigned int);
char tmp[16];
int ti = 0;
if (v == 0) tmp[ti++] = '0';
while (v) { tmp[ti++] = (char)('0' + v % 10); v /= 10; }
while (ti > 0) { ti--; if (o + 1 < bufsize) buf[o] = tmp[ti]; o++; }
} else if (*p == '%') {
if (o + 1 < bufsize) buf[o] = '%';
o++;
} else {
if (o + 1 < bufsize) buf[o] = '%';
o++;
if (*p) { if (o + 1 < bufsize) buf[o] = *p; o++; }
}
}
if (bufsize > 0) buf[o < bufsize ? o : bufsize - 1] = '\0';
return (int)o;
}
FILE *fopen(const char *path, const char *mode) {
/* Linux x86 open(2) flag values - O_WRONLY=1, O_CREAT=64, O_TRUNC=512
* (decimal, not octal: CC's lexer has no octal-literal support, see
* lexer.c's numeric-literal reader). Only "r"/"rb" and "w"/"wb" are
* ever passed (surveyed before writing this). */
int flags = (mode[0] == 'r') ? 0 : (1 | 64 | 512);
int fd = __sys_open(path, flags);
if (fd < 0) return NULL;
FILE *f = malloc(sizeof(FILE));
f->fd = fd;
f->eof = 0;
f->err = 0;
return f;
}
size_t fread(void *ptr, size_t size, size_t n, FILE *f) {
if (size == 0 || n == 0) return 0;
int want = (int)(size * n);
int got = __sys_read(f->fd, ptr, (unsigned int)want);
if (got < 0) { f->err = 1; return 0; }
if (got < want) f->eof = 1;
return (size_t)(got / (int)size);
}
size_t fwrite(const void *ptr, size_t size, size_t n, FILE *f) {
if (size == 0 || n == 0) return 0;
int want = (int)(size * n);
int got = __sys_write(f->fd, ptr, (unsigned int)want);
if (got < 0) { f->err = 1; return 0; }
return (size_t)(got / (int)size);
}
int fclose(FILE *f) {
__sys_close(f->fd);
free(f);
return 0;
}
int fseek(FILE *f, long offset, int whence) {
__sys_lseek(f->fd, (int)offset, whence);
return 0;
}
long ftell(FILE *f) {
return (long)__sys_lseek(f->fd, 0, SEEK_CUR);
}
int fputc(int c, FILE *f) {
char ch = (char)c;
__sys_write(f->fd, &ch, 1);
return c;
}
int vfprintf(FILE *f, const char *fmt, va_list ap) {
char buf[4096];
int n = fmt_core(buf, sizeof(buf), fmt, ap);
int wn = n < (int)sizeof(buf) - 1 ? n : (int)sizeof(buf) - 1;
__sys_write(f->fd, buf, (unsigned int)wn);
return n;
}
int fprintf(FILE *f, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int n = vfprintf(f, fmt, ap);
va_end(ap);
return n;
}
int printf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int n = vfprintf(stdout, fmt, ap);
va_end(ap);
return n;
}
int snprintf(char *buf, size_t n, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int r = fmt_core(buf, n, fmt, ap);
va_end(ap);
return r;
}
void perror(const char *msg) {
fprintf(stderr, "%s\n", msg);
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/toolchain/hostlib/stdio.c (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude rm -f build/toolchain/cc build/toolchain/as build/toolchain/ld make build/toolchain/cc build/toolchain/as build/toolchain/ld 2>&1 | tail -40
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 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 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
$ cd /home/martin/workspace/prototypes/boot-to-claude
fail=0
for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c; do
out=$(./build/toolchain/cc -Itoolchain/include --target=host --parse-only "$f" 2>&1)
if [ $? -ne 0 ]; then
echo "=== $f ==="
echo "$out" | head -3
fail=1
fi
done
echo "done fail=$fail"=== toolchain/as/elf_writer.c === cc: toolchain/as/elf_writer.c:82: expected expression (near '?') === toolchain/as/parser.c === cc: toolchain/as/parser.c:36: expected expression (near '?') === toolchain/cc/codegen.c === cc: toolchain/cc/codegen.c:655: unexpected token (near '?') === toolchain/cc/lexer.c === cc: toolchain/cc/lexer.c:147: unsupported preprocessor directive #undef === toolchain/cc/parser.c === cc: toolchain/cc/parser.c:1144: unexpected token (near '?') === toolchain/cc/preprocessor.c === cc: toolchain/cc/preprocessor.c:23: unexpected token (near '?') === toolchain/ld/elf_reader.c === cc: toolchain/ld/elf_format.h:106: undeclared identifier (near 'info') === toolchain/ld/link_flat.c === cc: toolchain/ld/link_flat.c:105: expected expression (near '?') done fail=1
$ cd /home/martin/workspace/prototypes/boot-to-claude
grep -n '^#define.*(' toolchain/as/*.c toolchain/as/*.h toolchain/cc/*.c toolchain/cc/*.h toolchain/ld/*.c toolchain/ld/*.htoolchain/as/elf_format.h:102:#define ELF32_ST_INFO(bind, type) (((bind) << 4) + ((type) & 0xf))
toolchain/as/elf_format.h:106:#define ELF32_R_SYM(info) ((info) >> 8)
toolchain/as/elf_format.h:107:#define ELF32_R_TYPE(info) ((unsigned char)(info))
toolchain/as/elf_format.h:108:#define ELF32_R_INFO(sym, type) (((sym) << 8) + (unsigned char)(type))
toolchain/cc/lexer.c:141:#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; }
toolchain/ld/elf_format.h:102:#define ELF32_ST_INFO(bind, type) (((bind) << 4) + ((type) & 0xf))
toolchain/ld/elf_format.h:106:#define ELF32_R_SYM(info) ((info) >> 8)
toolchain/ld/elf_format.h:107:#define ELF32_R_TYPE(info) ((unsigned char)(info))
toolchain/ld/elf_format.h:108:#define ELF32_R_INFO(sym, type) (((sym) << 8) + (unsigned char)(type))$ cd /home/martin/workspace/prototypes/boot-to-claude diff toolchain/as/elf_format.h toolchain/ld/elf_format.h && echo IDENTICAL sed -n '95,112p' toolchain/as/elf_format.h
IDENTICAL #define SHF_EXECINSTR 0x4 #define STB_LOCAL 0 #define STB_GLOBAL 1 #define STT_NOTYPE 0 #define STT_FUNC 2 #define STT_SECTION 3 #define ELF32_ST_INFO(bind, type) (((bind) << 4) + ((type) & 0xf)) #define R_386_32 1 #define R_386_PC32 2 #define ELF32_R_SYM(info) ((info) >> 8) #define ELF32_R_TYPE(info) ((unsigned char)(info)) #define ELF32_R_INFO(sym, type) (((sym) << 8) + (unsigned char)(type)) #define PT_LOAD 1 #define PF_X 1 #define PF_W 2
$ cd /home/martin/workspace/prototypes/boot-to-claude
for f in toolchain/as/elf_format.h toolchain/ld/elf_format.h; do
python3 - "$f" <<'PYEOF'
import sys
path = sys.argv[1]
with open(path) as fh:
content = fh.read()
old = """#define ELF32_ST_INFO(bind, type) (((bind) << 4) + ((type) & 0xf))
#define R_386_32 1
#define R_386_PC32 2
#define ELF32_R_SYM(info) ((info) >> 8)
#define ELF32_R_TYPE(info) ((unsigned char)(info))
#define ELF32_R_INFO(sym, type) (((sym) << 8) + (unsigned char)(type))
"""
new = """/* Plain functions, not function-like macros: CC's preprocessor only
* supports object-like #define (see docs/toolchain.md, Phase T2) - these
* four need real parameters. */
static inline unsigned char elf32_st_info(int bind, int type) { return (unsigned char)((bind << 4) + (type & 0xf)); }
#define R_386_32 1
#define R_386_PC32 2
static inline unsigned int elf32_r_sym(unsigned int info) { return info >> 8; }
static inline unsigned char elf32_r_type(unsigned int info) { return (unsigned char)info; }
static inline unsigned int elf32_r_info(unsigned int sym, unsigned int type) { return (sym << 8) + (unsigned char)type; }
"""
assert old in content, f"pattern not found in {path}"
content = content.replace(old, new)
with open(path, "w") as fh:
fh.write(content)
print("patched", path)
PYEOF
donepatched toolchain/as/elf_format.h patched toolchain/ld/elf_format.h
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -rn 'ELF32_ST_INFO\|ELF32_R_SYM\|ELF32_R_TYPE\|ELF32_R_INFO' toolchain/
toolchain/ld/link_flat.c:110: sym.st_info = (unsigned char)ELF32_ST_INFO(STB_GLOBAL, STT_NOTYPE); toolchain/as/elf_writer.c:94: sym.st_info = (unsigned char)ELF32_ST_INFO(is_glob ? STB_GLOBAL : STB_LOCAL, STT_NOTYPE); toolchain/as/elf_writer.c:121: rels[sec][nrels[sec]].r_info = ELF32_R_INFO(symidx, type); toolchain/ld/elf_reader.c:102: ir->symbol_idx = (int)ELF32_R_SYM(rel.r_info); toolchain/ld/elf_reader.c:103: ir->type = (int)ELF32_R_TYPE(rel.r_info);
$ cd /home/martin/workspace/prototypes/boot-to-claude sed -i 's/ELF32_ST_INFO(/elf32_st_info(/g; s/ELF32_R_SYM(/elf32_r_sym(/g; s/ELF32_R_TYPE(/elf32_r_type(/g; s/ELF32_R_INFO(/elf32_r_info(/g' \ toolchain/ld/link_flat.c toolchain/as/elf_writer.c toolchain/ld/elf_reader.c grep -rn 'elf32_st_info\|elf32_r_sym\|elf32_r_type\|elf32_r_info' toolchain/ld/link_flat.c toolchain/as/elf_writer.c toolchain/ld/elf_reader.c
toolchain/ld/link_flat.c:110: sym.st_info = (unsigned char)elf32_st_info(STB_GLOBAL, STT_NOTYPE); toolchain/ld/elf_reader.c:102: ir->symbol_idx = (int)elf32_r_sym(rel.r_info); toolchain/ld/elf_reader.c:103: ir->type = (int)elf32_r_type(rel.r_info); toolchain/as/elf_writer.c:94: sym.st_info = (unsigned char)elf32_st_info(is_glob ? STB_GLOBAL : STB_LOCAL, STT_NOTYPE); toolchain/as/elf_writer.c:121: rels[sec][nrels[sec]].r_info = elf32_r_info(symidx, type);
138 return t;
139 }
140
141 #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; }
142 TWO('=', '=', TK_EQ) TWO('!', '=', TK_NEQ) TWO('<', '=', TK_LE) TWO('>', '=', TK_GE)
143 TWO('&', '&', TK_AND) TWO('|', '|', TK_OR) TWO('+', '+', TK_INC) TWO('-', '-', TK_DEC)
144 TWO('-', '>', TK_ARROW) TWO('+', '=', TK_PLUSEQ) TWO('-', '=', TK_MINUSEQ)
145 TWO('*', '=', TK_STAREQ) TWO('/', '=', TK_SLASHEQ) TWO('%', '=', TK_PERCENTEQ)
146 TWO('&', '=', TK_ANDEQ) TWO('|', '=', TK_OREQ) TWO('^', '=', TK_XOREQ)
147 #undef TWO
148 if (c == '<' && peekc2(lx) == '<') {
149 if (lx->pos + 2 < lx->len && lx->src[lx->pos + 2] == '=') { lx->pos += 3; token_t t = mk(lx, TK_SHLEQ); t.bol = was_bol; return t; }
150 lx->pos += 2; token_t t = mk(lx, TK_SHL); t.bol = was_bol; return t;
151 }
152 if (c == '>' && peekc2(lx) == '>') {- static token_t mk(lexer_t *lx, tok_kind_t k) {
- token_t t;
- memset(&t, 0, sizeof(t));
- t.kind = k;
- t.line = lx->line;
- t.file = lx->filename;
- t.bol = lx->bol;
- return t;
- }
+ static token_t mk(lexer_t *lx, tok_kind_t k) {
+ token_t t;
+ memset(&t, 0, sizeof(t));
+ t.kind = k;
+ t.line = lx->line;
+ t.file = lx->filename;
+ t.bol = lx->bol;
+ return t;
+ }
+
+ /* Not a function-like macro (CC's preprocessor only supports object-like
+ * #define - see docs/toolchain.md, Phase T2): a plain helper covering
+ * every fixed two-character operator below. */
+ static int try_two(lexer_t *lx, int c, int was_bol, int c1, int c2, tok_kind_t kind, token_t *out) {
+ if (c == c1 && peekc2(lx) == c2) {
+ lx->pos += 2;
+ *out = mk(lx, kind);
+ out->bol = was_bol;
+ return 1;
+ }
+ return 0;
+ }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)
- #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; }
- TWO('=', '=', TK_EQ) TWO('!', '=', TK_NEQ) TWO('<', '=', TK_LE) TWO('>', '=', TK_GE)
- TWO('&', '&', TK_AND) TWO('|', '|', TK_OR) TWO('+', '+', TK_INC) TWO('-', '-', TK_DEC)
- TWO('-', '>', TK_ARROW) TWO('+', '=', TK_PLUSEQ) TWO('-', '=', TK_MINUSEQ)
- TWO('*', '=', TK_STAREQ) TWO('/', '=', TK_SLASHEQ) TWO('%', '=', TK_PERCENTEQ)
- TWO('&', '=', TK_ANDEQ) TWO('|', '=', TK_OREQ) TWO('^', '=', TK_XOREQ)
- #undef TWO
+ {
+ token_t t;
+ if (try_two(lx, c, was_bol, '=', '=', TK_EQ, &t)) return t;
+ if (try_two(lx, c, was_bol, '!', '=', TK_NEQ, &t)) return t;
+ if (try_two(lx, c, was_bol, '<', '=', TK_LE, &t)) return t;
+ if (try_two(lx, c, was_bol, '>', '=', TK_GE, &t)) return t;
+ if (try_two(lx, c, was_bol, '&', '&', TK_AND, &t)) return t;
+ if (try_two(lx, c, was_bol, '|', '|', TK_OR, &t)) return t;
+ if (try_two(lx, c, was_bol, '+', '+', TK_INC, &t)) return t;
+ if (try_two(lx, c, was_bol, '-', '-', TK_DEC, &t)) return t;
+ if (try_two(lx, c, was_bol, '-', '>', TK_ARROW, &t)) return t;
+ if (try_two(lx, c, was_bol, '+', '=', TK_PLUSEQ, &t)) return t;
+ if (try_two(lx, c, was_bol, '-', '=', TK_MINUSEQ, &t)) return t;
+ if (try_two(lx, c, was_bol, '*', '=', TK_STAREQ, &t)) return t;
+ if (try_two(lx, c, was_bol, '/', '=', TK_SLASHEQ, &t)) return t;
+ if (try_two(lx, c, was_bol, '%', '=', TK_PERCENTEQ, &t)) return t;
+ if (try_two(lx, c, was_bol, '&', '=', TK_ANDEQ, &t)) return t;
+ if (try_two(lx, c, was_bol, '|', '=', TK_OREQ, &t)) return t;
+ if (try_two(lx, c, was_bol, '^', '=', TK_XOREQ, &t)) return t;
+ }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)
$ cd /home/martin/workspace/prototypes/boot-to-claude echo "=== as/elf_writer.c:82 ==="; sed -n '78,86p' toolchain/as/elf_writer.c echo "=== as/parser.c:36 ==="; sed -n '32,40p' toolchain/as/parser.c echo "=== cc/codegen.c:655 ==="; sed -n '650,660p' toolchain/cc/codegen.c echo "=== cc/parser.c:1144 ==="; sed -n '1138,1148p' toolchain/cc/parser.c echo "=== cc/preprocessor.c:23 ==="; sed -n '18,28p' toolchain/cc/preprocessor.c echo "=== ld/link_flat.c:105 ==="; sed -n '100,110p' toolchain/ld/link_flat.c
=== as/elf_writer.c:82 ===
static strtab_builder_t strtab;
memset(&strtab, 0, sizeof(strtab));
static int sym_elf_index[MAX_SYMS];
symtab[nsym++] = (Elf32_Sym){0}; /* index 0: null symbol */
for (int pass = 0; pass < 2; pass++) { /* pass 0 = locals, pass 1 = globals */
for (int i = 0; i < ctx->nsyms; i++) {
symbol_t *s = &ctx->syms[i];
=== as/parser.c:36 ===
} line_t;
static token_t line_peek(line_t *ln) {
if (ln->pos < ln->n) return ln->toks[ln->pos];
return (token_t){ .kind = TK_EOF };
}
static token_t line_next(line_t *ln) {
token_t t = line_peek(ln);
if (ln->pos < ln->n) ln->pos++;
=== cc/codegen.c:655 ===
}
extern int is_builtin_name(const char *name);
#define MAX_EXTERNS 64
static char g_externs[MAX_EXTERNS][MAX_NAME];
static int g_n_externs;
static int already_extern(const char *name) {
for (int i = 0; i < g_n_externs; i++) if (strcmp(g_externs[i], name) == 0) return 1;
return 0;
=== cc/parser.c:1144 ===
} else {
int is_static = 0, is_extern = 0;
type_t *base = parse_type_spec(&p, &is_static, &is_extern);
if (match(&p, TK_SEMI)) {
decl = node_new(ND_NOP); /* lone `struct Foo { ... };` with no variable */
} else {
char param_names[16][MAX_NAME];
int n_params = 0;
char name[MAX_NAME];
type_t *t = parse_declarator(&p, base, name, param_names, &n_params);
if (t->kind == TY_FUNC) {
=== cc/preprocessor.c:23 ===
} macro_t;
static macro_t g_macros[MAX_MACROS];
static int g_n_macros = 0;
static char g_pragma_once_files[MAX_PRAGMA_ONCE][512];
static int g_n_pragma_once = 0;
static macro_t *macro_find(const char *name) {
for (int i = 0; i < g_n_macros; i++) {
if (strcmp(g_macros[i].name, name) == 0) return &g_macros[i];
=== ld/link_flat.c:105 ===
int shidx_shstrtab = nsh++;
uint32_t name_shstrtab = strtab_add(&shstrtab, ".shstrtab");
static Elf32_Sym symtab[MAX_OUT_SYMS + 1];
int nsym = 0;
symtab[nsym++] = (Elf32_Sym){0};
for (int i = 0; i < ctx->n_out_syms; i++) {
out_symbol_t *s = &ctx->out_syms[i];
Elf32_Sym sym = {0};
sym.st_name = strtab_add(&strtab, s->name);
sym.st_info = (unsigned char)elf32_st_info(STB_GLOBAL, STT_NOTYPE);$ cd /home/martin/workspace/prototypes/boot-to-claude
grep -rnE '\([A-Za-z_][A-Za-z0-9_]*_t\)\s*\{|\(struct [A-Za-z_]+\)\s*\{' toolchain/as toolchain/cc toolchain/ld toolchain/hostlibtoolchain/as/parser.c:36: return (token_t){ .kind = TK_EOF };$ cd /home/martin/workspace/prototypes/boot-to-claude
grep -rnE '\([A-Za-z_][A-Za-z0-9_]*\)\s*\{' toolchain/as toolchain/cc toolchain/ld toolchain/hostlibtoolchain/cc/main.c:49: if (parse_only) {
toolchain/as/elf_writer.c:82: symtab[nsym++] = (Elf32_Sym){0}; /* index 0: null symbol */
toolchain/cc/codegen.c:159: if (a_is_ptr) {
toolchain/cc/codegen.c:388: if (is_struct_return) {
toolchain/cc/codegen.c:796: if (g_n_strings) {
toolchain/cc/codegen.c:827: if (any_data) {
toolchain/cc/codegen.c:850: if (any_bss) {
toolchain/as/parser.c:36: return (token_t){ .kind = TK_EOF };
toolchain/as/parser.c:137: if (negate_b) {
toolchain/as/parser.c:248: if (r) { o.reg = r->reg; line_next(ln); }
toolchain/as/parser.c:292: if (r) {
toolchain/as/lexer.c:50: if (all01) {
toolchain/cc/preprocessor.c:78: if (is_quote) {
toolchain/cc/preprocessor.c:81: if (f) { fclose(f); safe_strcpy(out, cand, outsz); return 1; }
toolchain/cc/preprocessor.c:86: if (f) { fclose(f); safe_strcpy(out, cand, outsz); return 1; }
toolchain/cc/preprocessor.c:202: if (m) {
toolchain/hostlib/stdio.c:54: while (uv) { tmp[ti++] = (char)('0' + uv % 10); uv /= 10; }
toolchain/hostlib/stdio.c:55: if (neg) { if (o + 1 < bufsize) buf[o] = '-'; o++; }
toolchain/hostlib/stdio.c:56: else if (plus) { if (o + 1 < bufsize) buf[o] = '+'; o++; }
toolchain/hostlib/stdio.c:63: while (v) { tmp[ti++] = (char)('0' + v % 10); v /= 10; }
toolchain/cc/parser.c:107: if (is_static) {
toolchain/cc/parser.c:242: } else if (existing) {
toolchain/cc/parser.c:1162: if (decl) {
toolchain/cc/lexer.c:57: switch (c) {
toolchain/cc/lexer.c:188: switch (c) {
toolchain/ld/main.c:156: if (flat_mode) {
toolchain/ld/main.c:166: if (flat_mode) {
toolchain/as/encode.c:8: switch (r) {
toolchain/as/encode.c:76: if (is_bin) {
toolchain/as/encode.c:78: if (strict) { fprintf(stderr, "as: undefined symbol '%s'\n", sym); exit(1); }
toolchain/as/encode.c:89: if (strict) {
toolchain/as/encode.c:115: if (is_bin) {
toolchain/as/encode.c:117: if (strict) { fprintf(stderr, "as: undefined symbol '%s'\n", sym); exit(1); }
toolchain/as/encode.c:132: if (strict) {
toolchain/ld/link_flat.c:105: symtab[nsym++] = (Elf32_Sym){0};$ cd /home/martin/workspace/prototypes/boot-to-claude
grep -rnE '\([A-Za-z_][A-Za-z0-9_]*\)\{|\([A-Za-z_][A-Za-z0-9_]*\) *\{ *[.0]' toolchain/as toolchain/cc toolchain/ld toolchain/hostlibtoolchain/as/elf_writer.c:82: symtab[nsym++] = (Elf32_Sym){0}; /* index 0: null symbol */
toolchain/as/parser.c:36: return (token_t){ .kind = TK_EOF };
toolchain/ld/link_flat.c:105: symtab[nsym++] = (Elf32_Sym){0};1 #include "as.h"
2
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <ctype.h>
7
8 symbol_t *sym_find(asm_ctx_t *ctx, const char *name) {
9 for (int i = 0; i < ctx->nsyms; i++) {
10 if (strcmp(ctx->syms[i].name, name) == 0) return &ctx->syms[i];
11 }
12 return NULL;
13 }
14
15 symbol_t *sym_get_or_create(asm_ctx_t *ctx, const char *name) {
16 symbol_t *s = sym_find(ctx, name);
17 if (s) return s;
18 if (ctx->nsyms >= MAX_SYMS) { fprintf(stderr, "as: too many symbols\n"); exit(1); }
19 s = &ctx->syms[ctx->nsyms++];
20 memset(s, 0, sizeof(*s));
21 safe_strcpy(s->name, name, MAX_NAME);
22 return s;
23 }
24
25 /* --- per-line token buffer --- */
26
27 #define MAX_LINE_TOKS 32
28 typedef struct {
29 token_t toks[MAX_LINE_TOKS];
30 int n;
31 int pos;
32 } line_t;
33
34 static token_t line_peek(line_t *ln) {
35 if (ln->pos < ln->n) return ln->toks[ln->pos];
36 return (token_t){ .kind = TK_EOF };
37 }
38 static token_t line_next(line_t *ln) {
39 token_t t = line_peek(ln);
40 if (ln->pos < ln->n) ln->pos++;
41 return t;
42 }- static token_t line_peek(line_t *ln) {
- if (ln->pos < ln->n) return ln->toks[ln->pos];
- return (token_t){ .kind = TK_EOF };
- }
+ static token_t line_peek(line_t *ln) {
+ if (ln->pos < ln->n) return ln->toks[ln->pos];
+ /* Not a compound literal (CC doesn't support them - see
+ * docs/toolchain.md, Phase T3/T8): memset then set the one field
+ * that matters, same zeroed-rest semantics as `{ .kind = TK_EOF }`. */
+ token_t t;
+ memset(&t, 0, sizeof(t));
+ t.kind = TK_EOF;
+ return t;
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/parser.c has been updated successfully. (file state is current in your context — no need to Read it back)
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;- symtab[nsym++] = (Elf32_Sym){0}; /* index 0: null symbol */
+ /* Not a compound literal (CC doesn't support them - see
+ * docs/toolchain.md, Phase T3/T8): symtab is `static`, but a prior
+ * call in the same process could have left index 0 non-zero, so
+ * this can't just rely on static zero-init either. */
+ memset(&symtab[nsym], 0, sizeof(symtab[nsym]));
+ nsym++; /* index 0: null symbol */The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/elf_writer.c has been updated successfully. (file state is current in your context — no need to Read it back)
98 int shidx_strtab = nsh++;
99 uint32_t name_strtab = strtab_add(&shstrtab, ".strtab");
100 int shidx_shstrtab = nsh++;
101 uint32_t name_shstrtab = strtab_add(&shstrtab, ".shstrtab");
102
103 static Elf32_Sym symtab[MAX_OUT_SYMS + 1];
104 int nsym = 0;
105 symtab[nsym++] = (Elf32_Sym){0};
106 for (int i = 0; i < ctx->n_out_syms; i++) {
107 out_symbol_t *s = &ctx->out_syms[i];
108 Elf32_Sym sym = {0};
109 sym.st_name = strtab_add(&strtab, s->name);- static Elf32_Sym symtab[MAX_OUT_SYMS + 1];
- int nsym = 0;
- symtab[nsym++] = (Elf32_Sym){0};
+ static Elf32_Sym symtab[MAX_OUT_SYMS + 1];
+ int nsym = 0;
+ /* Not a compound literal (CC doesn't support them - see
+ * docs/toolchain.md, Phase T3/T8): symtab is `static`, but a prior
+ * call in the same process could have left index 0 non-zero. */
+ memset(&symtab[nsym], 0, sizeof(symtab[nsym]));
+ nsym++;The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/ld/link_flat.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 head -10 toolchain/ld/link_flat.c echo --- head -10 toolchain/as/elf_writer.c
#include "ld.h"
#include "elf_format.h"
#include <stdio.h>
#include <string.h>
static uint32_t align4(uint32_t x) { return (x + 3) & ~3u; }
/* Hardcodes exactly kernel/link.ld's layout - base address, section
* order, and the _bss_start/_bss_end symbols entry.S relies on to zero
---
#include "as.h"
#include "elf_format.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static const char *SEC_NAMES[MAX_SECTIONS] = { ".text", ".data", ".bss", ".rodata" };
static const int SEC_IS_BSS[MAX_SECTIONS] = { 0, 0, 1, 0 };
static const int SEC_IS_EXEC[MAX_SECTIONS] = { 1, 0, 0, 0 };$ cd /home/martin/workspace/prototypes/boot-to-claude
make build/toolchain/cc build/toolchain/as build/toolchain/ld 2>&1 | tail -20
echo "--- rebuilt, now parse-only sweep ---"
fail=0
for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c; do
out=$(./build/toolchain/cc -Itoolchain/include --target=host --parse-only "$f" 2>&1)
if [ $? -ne 0 ]; then
echo "=== $f ==="
echo "$out" | head -3
fail=1
fi
done
echo "done fail=$fail"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 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 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 --- rebuilt, now parse-only sweep --- === toolchain/as/elf_writer.c === cc: toolchain/as/elf_writer.c:116: unexpected token (near '?') === toolchain/cc/codegen.c === cc: toolchain/cc/codegen.c:655: unexpected token (near '?') === toolchain/cc/parser.c === cc: toolchain/cc/parser.c:1144: unexpected token (near '?') === toolchain/cc/preprocessor.c === cc: toolchain/cc/preprocessor.c:23: unexpected token (near '?') done fail=1
$ cd /home/martin/workspace/prototypes/boot-to-claude echo "=== elf_writer.c ==="; grep -n '?' toolchain/as/elf_writer.c echo "=== codegen.c ==="; grep -n '?' toolchain/cc/codegen.c | head -40
=== elf_writer.c ===
34: int reloc_for[MAX_SECTIONS]; /* does section s have any relocations? */
99: sym.st_info = (unsigned char)elf32_st_info(is_glob ? STB_GLOBAL : STB_LOCAL, STT_NOTYPE);
123: int type = r->kind == RELOC_PC32 ? R_386_PC32 : R_386_32;
188: sh.sh_type = SEC_IS_BSS[s] ? SHT_NOBITS : SHT_PROGBITS;
189: sh.sh_flags = SHF_ALLOC | (SEC_IS_EXEC[s] ? SHF_EXECINSTR : 0) | (s == SEC_TEXT ? 0 : SHF_WRITE);
=== codegen.c ===
302: int step = (n->a->type->kind == TY_PTR) ? type_size(n->a->type->base) : 1;
307: out(" %s eax, %d\n", is_dec ? "sub" : "add", step);
543: out(" jmp %s\n", default_label[0] ? default_label : lend);
550: safe_strcpy(lc.continue_label, g_loop ? g_loop->continue_label : "", MAX_NAME);$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'line\b' toolchain/cc/preprocessor.c | head -40
132: if (dir.kind != TK_IDENT) { fprintf(stderr, "cc: %s:%d: expected preprocessor directive\n", filename, dir.line); exit(1); }
139: if (is_quote && path_tok.kind != TK_STR) { fprintf(stderr, "cc: %s:%d: expected \"file\" after #include\n", filename, dir.line); exit(1); }
142: fprintf(stderr, "cc: %s:%d: cannot find include file '%s'\n", filename, dir.line, path_tok.text);
169: fprintf(stderr, "cc: %s:%d: unsupported #pragma\n", filename, dir.line);
176: if (name.kind != TK_IDENT) { fprintf(stderr, "cc: %s:%d: expected macro name\n", filename, dir.line); exit(1); }
184: /* First token of the next line (or EOF) - this
185: * lexer has no newline tokens, so `bol` marks
196: fprintf(stderr, "cc: %s:%d: unsupported preprocessor directive #%s\n", filename, dir.line, dir.text);108 }
109 }
110 int first_global = 1;
111 for (int i = 1; i < nsym; i++) {
112 if ((symtab[i].st_info >> 4) == STB_LOCAL) first_global = i + 1;
113 }
114
115 /* --- relocation table entries, grouped by target section --- */
116 static Elf32_Rel rels[MAX_SECTIONS][MAX_RELOCS];
117 int nrels[MAX_SECTIONS] = {0};
118 for (int i = 0; i < ctx->nrelocs; i++) {
119 reloc_t *r = &ctx->relocs[i];
120 symbol_t *s = sym_find(ctx, r->sym);
121 if (!s) { fprintf(stderr, "as: relocation against unknown symbol '%s'\n", r->sym); return -1; }
122 int symidx = sym_elf_index[s - ctx->syms];$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'perr\|token_str\|near' toolchain/cc/parser.c | head -20
44:static void perr_at(parser_t *p, token_t t, const char *msg) {
45: fprintf(stderr, "cc: %s:%d: %s (near '%s')\n", t.file ? t.file : "?", t.line, msg,
51:static void perr(parser_t *p, const char *msg) {
52: perr_at(p, p->toks[p->pos], msg);
60: if (!check(p, k)) perr(p, "unexpected token");
155: default: perr(p, "unsupported operator in constant expression"); return 0;
159: perr(p, "expected a constant expression");
261: perr(p, "anonymous struct/union with no body");
300: if (!existing) { perr(p, "unknown enum tag"); return NULL; }
492: if (!s) { perr_at(p, at, "undeclared identifier"); return make_num(0); }
581: perr(p, "expected expression");
603: if (!m) { perr(p, "no such member"); return n; }
613: if (!m) { perr(p, "no such member"); return n; }
942: if (!match_kw(p, "while")) perr(p, "expected 'while'");44 static void perr_at(parser_t *p, token_t t, const char *msg) {
45 fprintf(stderr, "cc: %s:%d: %s (near '%s')\n", t.file ? t.file : "?", t.line, msg,
46 t.kind == TK_IDENT || t.kind == TK_STR ? t.text : "?");
47 p->had_error = 1;
48 exit(1); /* tinyC has no error recovery - not worth it for this codebase's size */
49 }
50
51 static void perr(parser_t *p, const char *msg) {
52 perr_at(p, p->toks[p->pos], msg);
53 }$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'type_size' toolchain/cc/types.c
53:int type_size(type_t *t) {
60: case TY_ARRAY: t->size = type_size(t->base) * (t->array_len > 0 ? t->array_len : 0); break;
64: int msz = type_size(m->type);
78: off += type_size(m->type);$ cd /home/martin/workspace/prototypes/boot-to-claude grep -rnE '\[[A-Za-z_0-9]+\]\s*\[[A-Za-z_0-9]+\]' toolchain/as toolchain/cc toolchain/ld toolchain/hostlib | grep -v '\.md'
toolchain/as/as.h:110: uint8_t sec_bytes[MAX_SECTIONS][65536];
toolchain/cc/main.c:18: else if (strncmp(argv[i], "-I", 2) == 0 && argv[i][2] != '\0') {
toolchain/cc/main.c:28: else if (argv[i][0] == '-') {
toolchain/as/elf_writer.c:116: static Elf32_Rel rels[MAX_SECTIONS][MAX_RELOCS];
toolchain/cc/preprocessor.c:23:static char g_pragma_once_files[MAX_PRAGMA_ONCE][512];
toolchain/cc/codegen.c:655:static char g_externs[MAX_EXTERNS][MAX_NAME];
toolchain/ld/main.c:139: else if (argv[i][0] == '-') { fprintf(stderr, "ld: unrecognized option '%s'\n", argv[i]); return 1; }
toolchain/cc/parser.c:1144: char param_names[16][MAX_NAME];427 expect(p, TK_RPAREN);
428 expect(p, TK_LPAREN);
429 type_t *functy = type_new(TY_FUNC);
430 functy->base = base;
431 functy->param = parse_param_list(p, param_names, n_params);
432 expect(p, TK_RPAREN);
433 return ptr_to(functy);
434 }
435
436 token_t id = expect(p, TK_IDENT);
437 safe_strcpy(name_out, id.text, MAX_NAME);
438
439 if (match(p, TK_LBRACKET)) {
440 int n = -1;
441 if (!check(p, TK_RBRACKET)) n = (int)parse_const_expr(p);
442 expect(p, TK_RBRACKET);
443 type_t *arr = type_new(TY_ARRAY);
444 arr->base = base;
445 arr->array_len = n;
446 return arr;
447 }
448 if (match(p, TK_LPAREN)) {
449 type_t *functy = type_new(TY_FUNC);
450 functy->base = base;
451 functy->param = parse_param_list(p, param_names, n_params);
452 expect(p, TK_RPAREN);
453 return functy;
454 }
455 return base;
456 }- /* Parses a declarator (the part after the base type specifier) and
+ /* Parses zero or more trailing `[N]` groups (e.g. `rels[MAX_SECTIONS]
+ * [MAX_RELOCS]`, needed for Phase T8's self-hosting - see
+ * docs/toolchain.md) and nests them the way C actually does: the
+ * *first* bracket is the outermost array, so it must wrap a type built
+ * from the *later* brackets inward, not the other way around - collect
+ * the sizes first, then build from the last one back to the first. */
+ static type_t *parse_array_dims(parser_t *p, type_t *base) {
+ int dims[8];
+ int ndims = 0;
+ while (match(p, TK_LBRACKET)) {
+ int n = -1;
+ if (!check(p, TK_RBRACKET)) n = (int)parse_const_expr(p);
+ expect(p, TK_RBRACKET);
+ dims[ndims++] = n;
+ }
+ type_t *t = base;
+ for (int i = ndims - 1; i >= 0; i--) {
+ type_t *arr = type_new(TY_ARRAY);
+ arr->base = t;
+ arr->array_len = dims[i];
+ t = arr;
+ }
+ return t;
+ }
+
+ /* Parses a declarator (the part after the base type specifier) andThe 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)
- token_t id = expect(p, TK_IDENT);
- safe_strcpy(name_out, id.text, MAX_NAME);
-
- if (match(p, TK_LBRACKET)) {
- int n = -1;
- if (!check(p, TK_RBRACKET)) n = (int)parse_const_expr(p);
- expect(p, TK_RBRACKET);
- type_t *arr = type_new(TY_ARRAY);
- arr->base = base;
- arr->array_len = n;
- return arr;
- }
- if (match(p, TK_LPAREN)) {
+ token_t id = expect(p, TK_IDENT);
+ safe_strcpy(name_out, id.text, MAX_NAME);
+
+ if (check(p, TK_LBRACKET)) return parse_array_dims(p, base);
+ if (match(p, TK_LPAREN)) {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)
460
461 token_t id = expect(p, TK_IDENT);
462 safe_strcpy(name_out, id.text, MAX_NAME);
463
464 if (check(p, TK_LBRACKET)) return parse_array_dims(p, base);
465 if (match(p, TK_LPAREN)) {
466 type_t *functy = type_new(TY_FUNC);
467 functy->base = base;
468 functy->param = parse_param_list(p, param_names, n_params);
469 expect(p, TK_RPAREN);
470 return functy;
471 }
472 return base;
473 }
474 474
475 static type_t *parse_type_name(parser_t *p) {
476 type_t *base = parse_type_spec(p, NULL, NULL);
477 while (match(p, TK_STAR)) base = ptr_to(base);
478 if (match(p, TK_LBRACKET)) {
479 int n = -1;
480 if (!check(p, TK_RBRACKET)) n = (int)parse_const_expr(p);
481 expect(p, TK_RBRACKET);
482 type_t *arr = type_new(TY_ARRAY);
483 arr->base = base;
484 arr->array_len = n;
485 base = arr;
486 }
487 return base;
488 }- static type_t *parse_type_name(parser_t *p) {
- type_t *base = parse_type_spec(p, NULL, NULL);
- while (match(p, TK_STAR)) base = ptr_to(base);
- if (match(p, TK_LBRACKET)) {
- int n = -1;
- if (!check(p, TK_RBRACKET)) n = (int)parse_const_expr(p);
- expect(p, TK_RBRACKET);
- type_t *arr = type_new(TY_ARRAY);
- arr->base = base;
- arr->array_len = n;
- base = arr;
- }
- return base;
- }
+ static type_t *parse_type_name(parser_t *p) {
+ type_t *base = parse_type_spec(p, NULL, NULL);
+ while (match(p, TK_STAR)) base = ptr_to(base);
+ if (check(p, TK_LBRACKET)) base = parse_array_dims(p, base);
+ return base;
+ }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
make build/toolchain/cc build/toolchain/as build/toolchain/ld 2>&1 | tail -20
echo "--- rebuilt, now parse-only sweep ---"
fail=0
for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c; do
out=$(./build/toolchain/cc -Itoolchain/include --target=host --parse-only "$f" 2>&1)
if [ $? -ne 0 ]; then
echo "=== $f ==="
echo "$out" | head -5
fail=1
fi
done
echo "done fail=$fail"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 make: 'build/toolchain/as' is up to date. make: 'build/toolchain/ld' is up to date. --- rebuilt, now parse-only sweep --- done fail=0
$ cd /home/martin/workspace/prototypes/boot-to-claude
mkdir -p build/stage2
set -x
for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c; do
base=$(echo "$f" | tr '/' '_')
./build/toolchain/cc -Itoolchain/include --target=host -o "build/stage2/${base%.c}.S" "$f" || { echo "CODEGEN FAIL: $f"; break; }
done 2>&1 | tail -80++ tail -80 ++ for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c +++ echo toolchain/as/bin_writer.c +++ tr / _ ++ base=toolchain_as_bin_writer.c ++ ./build/toolchain/cc -Itoolchain/include --target=host -o build/stage2/toolchain_as_bin_writer.S toolchain/as/bin_writer.c ++ for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c +++ echo toolchain/as/elf_writer.c +++ tr / _ ++ base=toolchain_as_elf_writer.c ++ ./build/toolchain/cc -Itoolchain/include --target=host -o build/stage2/toolchain_as_elf_writer.S toolchain/as/elf_writer.c ++ for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c +++ echo toolchain/as/encode.c +++ tr / _ ++ base=toolchain_as_encode.c ++ ./build/toolchain/cc -Itoolchain/include --target=host -o build/stage2/toolchain_as_encode.S toolchain/as/encode.c ++ for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c +++ echo toolchain/as/lexer.c +++ tr / _ ++ base=toolchain_as_lexer.c ++ ./build/toolchain/cc -Itoolchain/include --target=host -o build/stage2/toolchain_as_lexer.S toolchain/as/lexer.c ++ for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c +++ echo toolchain/as/main.c +++ tr / _ ++ base=toolchain_as_main.c ++ ./build/toolchain/cc -Itoolchain/include --target=host -o build/stage2/toolchain_as_main.S toolchain/as/main.c ++ for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c +++ echo toolchain/as/parser.c +++ tr / _ ++ base=toolchain_as_parser.c ++ ./build/toolchain/cc -Itoolchain/include --target=host -o build/stage2/toolchain_as_parser.S toolchain/as/parser.c cc: internal error: not an lvalue (kind 17) ++ echo 'CODEGEN FAIL: toolchain/as/parser.c' CODEGEN FAIL: toolchain/as/parser.c ++ break + pwd -P
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'not an lvalue' toolchain/cc/codegen.c echo --- grep -n 'ND_' toolchain/cc/cc.h | grep -oE 'ND_[A-Z_]+' | nl -ba | head -25
142: fprintf(stderr, "cc: internal error: not an lvalue (kind %d)\n", n->kind);
---
1 ND_NUM
2 ND_STR
3 ND_VAR
4 ND_ASSIGN
5 ND_COMPOUND_ASSIGN
6 ND_BINOP
7 ND_LOGAND
8 ND_LOGOR
9 ND_UNARY_MINUS
10 ND_NOT
11 ND_BITNOT
12 ND_ADDR
13 ND_DEREF
14 ND_PREINC
15 ND_PREDEC
16 ND_POSTINC
17 ND_POSTDEC
18 ND_CALL
19 ND_INDEX
20 ND_MEMBER
21 ND_ARROW
22 ND_CAST
23 ND_SIZEOF
24 ND_TERNARY
25 ND_COMMA$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n -- '--' toolchain/as/parser.c
25:/* --- per-line token buffer --- */ 61:/* --- state carried across the whole parse --- */ 82:/* --- register table --- */ 103:/* --- expression evaluation --- 238:/* --- operand parsing --- */ 313:/* --- statement/label emission --- */ 351:/* --- data directive value lists (db/dw/dd/dq), split one scalar per stmt --- */ 380:/* --- one line --- */ 525:/* --- driver --- */
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -nE '[A-Za-z0-9_]--|--[A-Za-z0-9_]' toolchain/as/parser.c
(Bash completed with no output)
$ cd /home/martin/workspace/prototypes/boot-to-claude
sed -n '130,145p' toolchain/cc/codegen.c
echo ---
grep -n 'struct node {' -A 40 toolchain/cc/cc.h | head -45 case ND_MEMBER:
codegen_addr(n->a);
if (n->num) out(" add eax, %lld\n", (long long)n->num);
return;
case ND_ARROW:
codegen_expr(n->a);
if (n->num) out(" add eax, %lld\n", (long long)n->num);
return;
case ND_DEREF:
codegen_expr(n->a);
return;
default:
fprintf(stderr, "cc: internal error: not an lvalue (kind %d)\n", n->kind);
exit(1);
}
}
---
133:struct node {
134- node_kind_t kind;
135- type_t *type; /* result type, filled in during parsing/type-checking */
136- tok_kind_t op; /* ND_BINOP / ND_COMPOUND_ASSIGN */
137-
138- int64_t num;
139- char str[512]; /* ND_STR: decoded bytes; also used for the emitted label once assigned */
140- char name[MAX_NAME]; /* ND_VAR / ND_CALL / ND_MEMBER / ND_ARROW / func/var decl name */
141-
142- node_t *a, *b, *c, *d; /* kind-specific children */
143- node_t *list; /* block statements / call args / decl init-list, via ->next */
144- node_t *next;
145-
146- /* variable/function storage location, resolved during parsing */
147- int is_global;
148- int is_static_local; /* function-local `static` - lives in .data/.bss, own label */
149- int is_file_static; /* file-scope `static T name;` - still needs storage (is_global
150- * stays 1 for that purpose) but must not get a `global` directive:
151- * two files each with their own file-private `static int foo;`
152- * would otherwise collide as duplicate global symbols at link time. */
153- int frame_offset; /* local/param: EBP-relative byte offset */
154- char asm_label[MAX_NAME]; /* global / static-local: the label CC emits for it */
155- int is_indirect_call; /* ND_CALL: callee is a function-pointer variable
156- * (the fields above locate it), not a directly
157- * callable function symbol - `call eax` instead
158- * of `call name` */
159-
160- node_t *init; /* ND_VAR_DECL: initializer expression or {..} list (via ->list), or NULL */
161- int is_extern_decl;
162-};
163-
164-node_t *node_new(node_kind_t kind);
165-
166-/* ---------------- parser ---------------- */
167-
168-/* Parses the whole (already-preprocessed) token stream into a list of
169- * top-level ND_FUNC_DEF / ND_VAR_DECL nodes (via ->next). */
170-node_t *cc_parse(token_t *toks, int n_toks);
171-
172-/* ---------------- codegen ---------------- */
173-95 out("%s:\n", lend);
96 out(" add esp, 8\n");
97 }
98
99 /* Stores EAX's value into the address in ECX. */
100 static void store_value(type_t *t) {
101 if (t->kind == TY_STRUCT || t->kind == TY_UNION) {
102 codegen_struct_copy(t);
103 } else if (is_byte_type(t)) {
104 out(" mov [ecx], al\n");
105 } else if (is_short_type(t)) {
106 out(" mov [ecx], ax\n");
107 } else {
108 out(" mov [ecx], eax\n");
109 }
110 }
111
112 /* ---------------- lvalue addresses ---------------- */
113
114 /* Address of a variable-like storage location, given the is_global/
115 * is_static_local/frame_offset/asm_label fields ND_VAR and an indirect
116 * ND_CALL's callee both carry (see cc.h). */
117 static void codegen_var_addr(node_t *n) {
118 if (n->is_global || n->is_static_local) {
119 out(" lea eax, [%s]\n", n->asm_label);
120 } else {
121 out(" lea eax, [ebp%+d]\n", n->frame_offset);
122 }
123 }
124
125 static void codegen_addr(node_t *n) {
126 switch (n->kind) {
127 case ND_VAR:
128 codegen_var_addr(n);
129 return;
130 case ND_MEMBER:
131 codegen_addr(n->a);
132 if (n->num) out(" add eax, %lld\n", (long long)n->num);
133 return;
134 case ND_ARROW:
135 codegen_expr(n->a);
136 if (n->num) out(" add eax, %lld\n", (long long)n->num);
137 return;
138 case ND_DEREF:
139 codegen_expr(n->a);$ cd /home/martin/workspace/prototypes/boot-to-claude
grep -n 'codegen_addr(' toolchain/cc/codegen.c39:static void codegen_addr(node_t *n);
125:static void codegen_addr(node_t *n) {
131: codegen_addr(n->a);
206: codegen_addr(n);
215: codegen_addr(n->a);
227: codegen_addr(n->a);
291: codegen_addr(n->a);
303: codegen_addr(n->a);
334: codegen_addr(n);195 }
196
197 static void codegen_expr(node_t *n) {
198 switch (n->kind) {
199 case ND_NUM:
200 out(" mov eax, %lld\n", (long long)n->num);
201 return;
202 case ND_STR:
203 out(" lea eax, [%s]\n", n->asm_label);
204 return;
205 case ND_VAR:
206 codegen_addr(n);
207 /* A bare function name (e.g. passed as a callback: tinyfs_init(
208 * disk_block_read, ...)) decays to its own address - there's no
209 * separate "function value" stored anywhere to load, unlike an
210 * ordinary global whose address holds its value. Loading here
211 * would dereference the function's own machine code bytes. */
212 if (n->type->kind != TY_FUNC) load_value(n->type);
213 return;
214 case ND_ASSIGN: {
215 codegen_addr(n->a);
216 out(" push eax\n");
217 codegen_expr(n->b);
218 out(" pop ecx\n");
219 store_value(n->a->type);
220 return;
221 }
222 case ND_COMPOUND_ASSIGN: {
223 /* eax/ecx get clobbered (idiv even clobbers edx) while
224 * computing the new value, so &a is kept on the real stack
225 * throughout and only popped fresh at the very end - a
226 * register copy of it wouldn't survive an idiv/cdq. */
227 codegen_addr(n->a);
228 out(" push eax\n"); /* [&a] */
229 codegen_expr(n->b);
230 out(" push eax\n"); /* [&a, b_value] */
231 out(" pop ecx\n"); /* ecx = b_value */
232 out(" mov edx, [esp]\n"); /* edx = &a (peek, stack still [&a]) */
233 out(" mov eax, [edx]\n"); /* eax = current value of a */
234 switch (n->op) {
235 case TK_PLUSEQ: out(" add eax, ecx\n"); break;
236 case TK_MINUSEQ: out(" sub eax, ecx\n"); break;
237 case TK_ANDEQ: out(" and eax, ecx\n"); break;
238 case TK_OREQ: out(" or eax, ecx\n"); break;
239 case TK_XOREQ: out(" xor eax, ecx\n"); break;
240 case TK_STAREQ: out(" imul eax, ecx\n"); break;
241 case TK_SLASHEQ: out(" cdq\n idiv ecx\n"); break;
242 case TK_PERCENTEQ: out(" cdq\n idiv ecx\n mov eax, edx\n"); break;
243 /* ecx already holds b_value (the shift count) in CL, no
244 * register shuffling needed - same as codegen_binop. */
245 case TK_SHLEQ: out(" shl eax, cl\n"); break;
246 case TK_SHREQ: out(" shr eax, cl\n"); break;
247 default: fprintf(stderr, "cc: internal error: unsupported compound-assign op\n"); exit(1);
248 }
249 out(" pop ecx\n"); /* ecx = &a, fresh from the stack (not the possibly-clobbered edx copy) */
250 store_value(n->a->type);
251 return;
252 }
253 case ND_BINOP:
254 codegen_binop(n);
255 return;
256 case ND_LOGAND: {
257 char lfalse[MAX_NAME], lend[MAX_NAME];
258 new_label(lfalse); new_label(lend);
259 codegen_expr(n->a);
260 out(" cmp eax, 0\n je %s\n", lfalse);
261 codegen_expr(n->b);
262 out(" cmp eax, 0\n je %s\n", lfalse);
263 out(" mov eax, 1\n jmp %s\n", lend);
264 out("%s:\n mov eax, 0\n%s:\n", lfalse, lend);
265 return;
266 }
267 case ND_LOGOR: {
268 char ltrue[MAX_NAME], lend[MAX_NAME];
269 new_label(ltrue); new_label(lend);
270 codegen_expr(n->a);
271 out(" cmp eax, 0\n jne %s\n", ltrue);
272 codegen_expr(n->b);
273 out(" cmp eax, 0\n jne %s\n", ltrue);
274 out(" mov eax, 0\n jmp %s\n", lend);
275 out("%s:\n mov eax, 1\n%s:\n", ltrue, lend);
276 return;
277 }
278 case ND_UNARY_MINUS:
279 codegen_expr(n->a);
280 out(" neg eax\n");
281 return;
282 case ND_NOT:
283 codegen_expr(n->a);
284 out(" cmp eax, 0\n sete al\n movzx eax, al\n");
285 return;
286 case ND_BITNOT:
287 codegen_expr(n->a);
288 out(" not eax\n");
289 return;
290 case ND_ADDR:
291 codegen_addr(n->a);
292 return;
293 case ND_DEREF:
294 codegen_expr(n->a);
295 load_value(n->type);
296 return;
297 case ND_PREINC: case ND_PREDEC: case ND_POSTINC: case ND_POSTDEC: {
298 /* int/pointer only (4-byte) - ++/-- on a char never appears
299 * in this codebase, see docs/toolchain.md. */
300 int is_dec = (n->kind == ND_PREDEC || n->kind == ND_POSTDEC);
301 int is_post = (n->kind == ND_POSTINC || n->kind == ND_POSTDEC);
302 int step = (n->a->type->kind == TY_PTR) ? type_size(n->a->type->base) : 1;
303 codegen_addr(n->a);
304 out(" mov ecx, eax\n"); /* ecx = address */
305 out(" mov eax, [ecx]\n"); /* eax = old value */
306 if (is_post) out(" mov edx, eax\n");
307 out(" %s eax, %d\n", is_dec ? "sub" : "add", step);
308 out(" mov [ecx], eax\n");
309 if (is_post) out(" mov eax, edx\n");
310 return;
311 }
312 case ND_CALL:
313 codegen_call_or_builtin(n);
314 return;- default: - fprintf(stderr, "cc: internal error: not an lvalue (kind %d)\n", n->kind); - exit(1); - } - } + default: + fprintf(stderr, "cc: internal error: not an lvalue (kind %d) name='%s' a_kind=%d a_name='%s' g_cur_func='%s'\n", + n->kind, n->name, n->a ? n->a->kind : -1, n->a ? n->a->name : "", g_cur_func); + exit(1); + } + }
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 -Wall -Wextra -O2 -o build/toolchain/cc toolchain/cc/*.c 2>&1 | tail -20 ./build/toolchain/cc -Itoolchain/include --target=host -o /tmp/x.S toolchain/as/parser.c
Exit code 1
toolchain/cc/codegen.c: In function ‘codegen_addr’:
toolchain/cc/codegen.c:143:59: warning: operand of ‘?:’ changes signedness from ‘int’ to ‘node_kind_t’ due to unsignedness of other operand [-Wsign-compare]
143 | n->kind, n->name, n->a ? n->a->kind : -1, n->a ? n->a->name : "", g_cur_func);
| ^~
cc: internal error: not an lvalue (kind 17) name='line_peek' a_kind=-1 a_name='' g_cur_func='eval_primary'$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'node_new\|MAX_NODES\|g_nodes' toolchain/cc/parser.c toolchain/cc/cc.h | head -20
toolchain/cc/cc.h:164:node_t *node_new(node_kind_t kind);
toolchain/cc/parser.c:493: node_t *n = node_new(ND_NUM);
toolchain/cc/parser.c:503: node_t *n = node_new(ND_VAR);
toolchain/cc/parser.c:514: node_t *n = node_new(ND_STR);
toolchain/cc/parser.c:539: node_t *call = node_new(ND_CALL);
toolchain/cc/parser.c:600: node_t *add = node_new(ND_BINOP);
toolchain/cc/parser.c:605: node_t *deref = node_new(ND_DEREF);
toolchain/cc/parser.c:613: node_t *mem = node_new(ND_MEMBER);
toolchain/cc/parser.c:623: node_t *mem = node_new(ND_ARROW);
toolchain/cc/parser.c:630: node_t *pi = node_new(ND_POSTINC); pi->a = n; pi->type = n->type; n = pi;
toolchain/cc/parser.c:632: node_t *pd = node_new(ND_POSTDEC); pd->a = n; pd->type = n->type; n = pd;
toolchain/cc/parser.c:646: if (match(p, TK_MINUS)) { node_t *n = node_new(ND_UNARY_MINUS); n->a = parse_cast(p); n->type = n->a->type; return n; }
toolchain/cc/parser.c:647: if (match(p, TK_NOT)) { node_t *n = node_new(ND_NOT); n->a = parse_cast(p); n->type = g_int_type; return n; }
toolchain/cc/parser.c:648: if (match(p, TK_TILDE)) { node_t *n = node_new(ND_BITNOT); n->a = parse_cast(p); n->type = n->a->type; return n; }
toolchain/cc/parser.c:649: if (match(p, TK_AMP)) { node_t *n = node_new(ND_ADDR); n->a = parse_cast(p); n->type = ptr_to(n->a->type); return n; }
toolchain/cc/parser.c:651: node_t *n = node_new(ND_DEREF);
toolchain/cc/parser.c:656: if (match(p, TK_INC)) { node_t *n = node_new(ND_PREINC); n->a = parse_unary(p); n->type = n->a->type; return n; }
toolchain/cc/parser.c:657: if (match(p, TK_DEC)) { node_t *n = node_new(ND_PREDEC); n->a = parse_unary(p); n->type = n->a->type; return n; }
toolchain/cc/parser.c:696: node_t *addr = node_new(ND_ADDR);
toolchain/cc/parser.c:699: node_t *cast = node_new(ND_CAST);$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'node_new' toolchain/cc/types.c sed -n '1,30p' toolchain/cc/types.c
12:node_t *node_new(node_kind_t kind) {
#include "cc.h"
#include <stdlib.h>
#include <string.h>
type_t *type_new(type_kind_t kind) {
type_t *t = calloc(1, sizeof(type_t));
t->kind = kind;
return t;
}
node_t *node_new(node_kind_t kind) {
node_t *n = calloc(1, sizeof(node_t));
n->kind = kind;
return n;
}
type_t *ptr_to(type_t *base) {
type_t *t = type_new(TY_PTR);
t->base = base;
t->size = 4;
t->align = 4;
return t;
}
int type_is_int(type_t *t) {
return t->kind == TY_CHAR || t->kind == TY_SHORT || t->kind == TY_INT || t->kind == TY_ENUM;
}
static int round_up(int n, int align) { return (n + align - 1) / align * align; }$ cd /home/martin/workspace/prototypes/boot-to-claude
grep -n 'typedef enum {' -A 20 toolchain/cc/cc.h | grep -B1 -A 20 'ND_NUM'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_SWITCH, ND_CASE, ND_DEFAULT,
128- ND_BLOCK, ND_EXPR_STMT, ND_NOP,
129- ND_FUNC_DEF, ND_VAR_DECL,
130-} node_kind_t;
131-
132-typedef struct node node_t;
133-struct node {
134- node_kind_t kind;
135- type_t *type; /* result type, filled in during parsing/type-checking */
136- tok_kind_t op; /* ND_BINOP / ND_COMPOUND_ASSIGN */
137-
138- int64_t num;
139- char str[512]; /* ND_STR: decoded bytes; also used for the emitted label once assigned */
140- char name[MAX_NAME]; /* ND_VAR / ND_CALL / ND_MEMBER / ND_ARROW / func/var decl name */
141-
--$ cd /home/martin/workspace/prototypes/boot-to-claude
grep -n 'line_peek(' toolchain/as/parser.c34:static token_t line_peek(line_t *ln) {
45: token_t t = line_peek(ln);
57: token_t t = line_peek(ln);
167: token_t t = line_peek(ln);
178: if (line_peek(ln).kind == TK_RPAREN) line_next(ln); else perr(ps, "expected )");
210: token_t t = line_peek(ln);
230: token_t t = line_peek(ln);
251: token_t t = line_peek(ln);
260: } else if (line_peek(ln).kind == TK_PLUS || line_peek(ln).kind == TK_MINUS) {
261: int neg = line_peek(ln).kind == TK_MINUS;
266: if (line_peek(ln).kind == TK_RBRACK) line_next(ln); else perr(ps, "expected ]");
273: if (line_peek(ln).kind == TK_IDENT && size_keyword(line_peek(ln).text, &sz)) {
275: if (line_peek(ln).kind == TK_LBRACK) {
291: if (line_peek(ln).kind == TK_LBRACK) {
296: if (line_peek(ln).kind == TK_IDENT) {
297: const regdef_t *r = reg_lookup(line_peek(ln).text);
366: if (line_peek(ln).kind == TK_STRING) {
375: if (line_peek(ln).kind == TK_COMMA) { line_next(ln); continue; }
385: if (line_peek(ln).kind == TK_EOF) return;
388: if (line_peek(ln).kind == TK_IDENT) {
391: if (line_peek(ln).kind == TK_COLON) {
413: if (line_peek(ln).kind == TK_EOF || line_peek(ln).kind == TK_NEWLINE) return;
414: if (line_peek(ln).kind != TK_IDENT) { perr(ps, "expected directive/instruction"); return; }
486: if (line_peek(ln).kind != TK_NEWLINE && line_peek(ln).kind != TK_EOF) {
492: if (ieq(st.mnemonic, "jmp") && line_peek(ln).kind == TK_COLON) {
505: } else if (line_peek(ln).kind == TK_COMMA) {
515: if (line_peek(ln).kind == TK_COMMA) {125 static void codegen_addr(node_t *n) {
126 switch (n->kind) {
127 case ND_VAR:
128 codegen_var_addr(n);
129 return;
130 case ND_MEMBER:
131 codegen_addr(n->a);
132 if (n->num) out(" add eax, %lld\n", (long long)n->num);
133 return;
134 case ND_ARROW:
135 codegen_expr(n->a);
136 if (n->num) out(" add eax, %lld\n", (long long)n->num);
137 return;
138 case ND_DEREF:
139 codegen_expr(n->a);
140 return;
141 default:- case ND_DEREF: - codegen_expr(n->a); - return; - default: - fprintf(stderr, "cc: internal error: not an lvalue (kind %d) name='%s' a_kind=%d a_name='%s' g_cur_func='%s'\n", - n->kind, n->name, n->a ? n->a->kind : -1, n->a ? n->a->name : "", g_cur_func); - exit(1); - } - } + case ND_DEREF: + codegen_expr(n->a); + return; + case ND_CALL: + /* A struct/union-returning call used directly as an lvalue + * (e.g. as/parser.c's `line_peek(ln).kind` - a very common + * idiom, needed for Phase T8's self-hosting - see + * docs/toolchain.md). codegen_call_or_builtin already leaves + * the hidden result slot's address in eax for exactly this + * case (see its own comment) - the "address of a struct- + * typed rvalue" convention load_value/codegen_expr already + * use everywhere else, so this is just an ordinary call. */ + codegen_expr(n); + return; + default: + fprintf(stderr, "cc: internal error: not an lvalue (kind %d)\n", n->kind); + exit(1); + } + }
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 -Wall -Wextra -O2 -o build/toolchain/cc toolchain/cc/*.c 2>&1 | tail -20 ./build/toolchain/cc -Itoolchain/include --target=host -o /tmp/x.S toolchain/as/parser.c && echo OK
OK
$ cd /home/martin/workspace/prototypes/boot-to-claude
rm -rf build/stage2 && mkdir -p build/stage2
fail=0
for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c; do
base=$(echo "$f" | tr '/' '_')
out=$(./build/toolchain/cc -Itoolchain/include --target=host -o "build/stage2/${base%.c}.S" "$f" 2>&1)
if [ $? -ne 0 ]; then
echo "=== $f ==="
echo "$out"
fail=1
fi
done
echo "done fail=$fail"done fail=0
$ cd /home/martin/workspace/prototypes/boot-to-claude
fail=0
for s in build/stage2/*.S; do
o="${s%.S}.o"
out=$(./build/toolchain/as -f elf32 -o "$o" "$s" 2>&1)
if [ $? -ne 0 ]; then echo "=== AS FAIL: $s ==="; echo "$out"; fail=1; fi
done
echo "assemble done fail=$fail"=== AS FAIL: build/stage2/toolchain_as_elf_writer.S === === AS FAIL: build/stage2/toolchain_as_main.S === === AS FAIL: build/stage2/toolchain_cc_main.S === === AS FAIL: build/stage2/toolchain_cc_preprocessor.S === === AS FAIL: build/stage2/toolchain_ld_main.S === assemble done fail=1
$ cd /home/martin/workspace/prototypes/boot-to-claude ./build/toolchain/as -f elf32 -o /tmp/y.o build/stage2/toolchain_as_elf_writer.S; echo "exit=$?"
/bin/bash: line 3: 3714457 Segmentation fault (core dumped) ./build/toolchain/as -f elf32 -o /tmp/y.o build/stage2/toolchain_as_elf_writer.S exit=139
$ cd /home/martin/workspace/prototypes/boot-to-claude wc -l build/stage2/toolchain_as_elf_writer.S build/stage2/toolchain_as_main.S build/stage2/toolchain_cc_main.S build/stage2/toolchain_cc_preprocessor.S build/stage2/toolchain_ld_main.S echo --- head -30 build/stage2/toolchain_cc_main.S
2651 build/stage2/toolchain_as_elf_writer.S
866 build/stage2/toolchain_as_main.S
878 build/stage2/toolchain_cc_main.S
2752 build/stage2/toolchain_cc_preprocessor.S
2302 build/stage2/toolchain_ld_main.S
9449 total
---
BITS 32
extern strcmp
extern strncmp
extern fprintf
extern pp_process_file
extern cc_parse
extern printf
extern fopen
extern perror
extern codegen_emit
extern fclose
extern g_include_dirs
extern g_n_include_dirs
extern g_stdout_file
extern g_stderr_file
section .text
safe_strcpy:
push ebp
mov ebp, esp
sub esp, 4
lea eax, [ebp-4]
push eax
mov eax, 0
pop ecx
mov [ecx], eax
.L0:
lea eax, [ebp+16]
mov eax, [eax]
push eax
mov eax, 1$ cd /home/martin/workspace/prototypes/boot-to-claude which gdb valgrind 2>&1 gdb -q -batch -ex run -ex bt --args ./build/toolchain/as -f elf32 -o /tmp/y.o build/stage2/toolchain_cc_main.S 2>&1 | tail -30
/bin/gdb /bin/valgrind [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Program received signal SIGSEGV, Segmentation fault. 0x0000555555556e80 in stmt_encode () #0 0x0000555555556e80 in stmt_encode () #1 0x0000555555558f45 in as_encode_pass2 () #2 0x000055555555545b in main ()
$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -g -O0 -o /tmp/as_debug toolchain/as/*.c gdb -q -batch -ex run -ex bt --args /tmp/as_debug -f elf32 -o /tmp/y.o build/stage2/toolchain_cc_main.S 2>&1 | tail -30
[Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Program received signal SIGSEGV, Segmentation fault. 0x000055555555696e in e8 (e=0x7fffffffd690, b=0 '\000') at toolchain/as/encode.c:41 41 if (e->out) e->out[e->len] = b; #0 0x000055555555696e in e8 (e=0x7fffffffd690, b=0 '\000') at toolchain/as/encode.c:41 #1 0x00005555555573b0 in stmt_encode (ctx=0x5555557b80e0 <ctx>, st=0x555555886db0 <ctx+847056>, out=0x555556a480f0 <ctx+19464208> "", strict=1) at toolchain/as/encode.c:228 #2 0x000055555555b30e in as_encode_pass2 (ctx=0x5555557b80e0 <ctx>) at toolchain/as/encode.c:738 #3 0x000055555555c35d in main (argc=6, argv=0x7fffffffd888) at toolchain/as/main.c:44
195 static const struct { const char *name; int base, ext; } table[] = {
196 { "add", 0x00, 0 }, { "or", 0x08, 1 }, { "and", 0x20, 4 },
197 { "sub", 0x28, 5 }, { "xor", 0x30, 6 }, { "cmp", 0x38, 7 },
198 };
199 for (size_t i = 0; i < sizeof(table) / sizeof(table[0]); i++) {
200 if (strcmp(m, table[i].name) == 0) { *base = table[i].base; *ext = table[i].ext; return 1; }
201 }
202 return 0;
203 }
204
205 /* --- the encoder --- */
206
207 int stmt_encode(asm_ctx_t *ctx, stmt_t *st, uint8_t *out, int strict) {
208 emitter_t e = { out, 0 };
209 const char *m = st->mnemonic;
210 operand_t *a = &st->ops[0];
211 operand_t *b = &st->ops[1];
212 operand_t *c = &st->ops[2];
213 int is_bin = ctx->have_org;
214
215 /* --- pseudo-ops for data directives: one scalar value per stmt --- */
216 if (strcmp(m, ".db") == 0 || strcmp(m, ".dw") == 0 || strcmp(m, ".dd") == 0 || strcmp(m, ".dq") == 0) {
217 int width = strcmp(m, ".db") == 0 ? 1 : strcmp(m, ".dw") == 0 ? 2 : strcmp(m, ".dd") == 0 ? 4 : 8;
218 int64_t v = a->has_sym
219 ? resolve_abs(ctx, is_bin, a->sym, a->disp, st->section, st->offset, strict)
220 : a->disp;
221 if (width == 1) e8(&e, (uint8_t)v);
222 else if (width == 2) e16(&e, (uint16_t)v);
223 else if (width == 4) e32(&e, (uint32_t)v);
224 else { e32(&e, (uint32_t)v); e32(&e, (uint32_t)(v >> 32)); }
225 return e.len;
226 }
227 if (strcmp(m, ".resb") == 0) {
228 for (int64_t i = 0; i < a->disp; i++) e8(&e, 0);
229 return e.len;
230 }
231 if (strcmp(m, ".pad") == 0) {
232 /* `times N db V` folds to one .pad stmt: a->disp = N, b->disp = V */
233 for (int64_t i = 0; i < a->disp; i++) e8(&e, (uint8_t)b->disp);
234 return e.len;
235 }
236
237 /* --- generic 32-bit ALU ops (add/or/and/sub/xor/cmp): reg<->reg,
238 * reg<->mem(ebp/esp/label), reg<->imm32 - what CC's codegen emits.
239 * Gated on bits==32 so this can never shadow the 16-bit real-mode$ cd /home/martin/workspace/prototypes/boot-to-claude
grep -n 'asm_ctx_t ctx\|static asm_ctx_t' toolchain/as/main.c
cat > /tmp/sizecheck.c <<'EOF'
#include "toolchain/as/as.h"
#include <stdio.h>
int main(void) { printf("sizeof(asm_ctx_t) = %zu\n", sizeof(asm_ctx_t)); return 0; }
EOF
gcc -I. -o /tmp/sizecheck /tmp/sizecheck.c && /tmp/sizecheck37: static asm_ctx_t ctx; sizeof(asm_ctx_t) = 19857560
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'as_encode_pass2' -A 25 toolchain/as/encode.c | head -35
733:int as_encode_pass2(asm_ctx_t *ctx) {
734- for (int i = 0; i < ctx->nstmts; i++) {
735- stmt_t *st = &ctx->stmts[i];
736- section_kind_t sec = st->section;
737- uint8_t *dst = ctx->sec_bytes[sec] + st->offset;
738- int len = stmt_encode(ctx, st, dst, 1);
739- if ((uint32_t)len != st->length) {
740- fprintf(stderr, "as: internal error: length mismatch for '%s' at line %d (pass1=%u pass2=%d)\n",
741- st->mnemonic, st->line_no, st->length, len);
742- return -1;
743- }
744- if (st->offset + (uint32_t)len > ctx->sec_len[sec]) {
745- ctx->sec_len[sec] = st->offset + (uint32_t)len;
746- }
747- }
748- return 0;
749-}$ cd /home/martin/workspace/prototypes/boot-to-claude sed -n '1,45p' toolchain/as/encode.c
#include "as.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int regnum(reg_t r) {
switch (r) {
case R_AL: case R_AX: case R_EAX: return 0;
case R_CL: case R_CX: case R_ECX: return 1;
case R_DL: case R_DX: case R_EDX: return 2;
case R_BL: case R_BX: case R_EBX: return 3;
case R_AH: case R_SP: case R_ESP: return 4;
case R_CH: case R_BP: case R_EBP: return 5;
case R_DH: case R_SI: case R_ESI: return 6;
case R_BH: case R_DI: case R_EDI: return 7;
case R_ES: return 0;
case R_CS: return 1;
case R_SS: return 2;
case R_DS: return 3;
case R_FS: return 4;
case R_GS: return 5;
case R_CR0: return 0;
default: return 0;
}
}
static int is_reg32(reg_t r) { return r >= R_EAX && r <= R_EDI; }
static int is_reg16(reg_t r) { return r >= R_AX && r <= R_DI; }
static int is_reg8(reg_t r) { return r >= R_AL && r <= R_BH; }
static int is_sreg(reg_t r) { return r >= R_ES && r <= R_GS; }
/* --- growable emission into `out` (NULL in dry-run/length-only mode) --- */
typedef struct {
uint8_t *out;
int len;
} emitter_t;
static void e8(emitter_t *e, uint8_t b) {
if (e->out) e->out[e->len] = b;
e->len++;
}
static void e16(emitter_t *e, uint16_t v) { e8(e, v & 0xFF); e8(e, (v >> 8) & 0xFF); }
static void e32(emitter_t *e, uint32_t v) {- for (int i = 0; i < ctx->nstmts; i++) {
- stmt_t *st = &ctx->stmts[i];
- section_kind_t sec = st->section;
- uint8_t *dst = ctx->sec_bytes[sec] + st->offset;
- int len = stmt_encode(ctx, st, dst, 1);
+ for (int i = 0; i < ctx->nstmts; i++) {
+ stmt_t *st = &ctx->stmts[i];
+ section_kind_t sec = st->section;
+ /* .bss bytes are never actually read back (the ELF/flat writers
+ * both skip emitting them - a loader zeroes that address range
+ * at load time instead), only their *count* matters. Passing
+ * NULL here (the same "dry-run, length-only" mode pass 1 always
+ * uses - see e8) skips materializing them into sec_bytes at all,
+ * which matters once self-hosting is in the picture (Phase T8):
+ * AS's own `ctx` global alone reserves ~20MB of .bss, and
+ * sec_bytes[section] is a fixed 64KB buffer - writing real
+ * zero bytes for a reservation that large overflows it. */
+ uint8_t *dst = (sec == SEC_BSS) ? NULL : ctx->sec_bytes[sec] + st->offset;
+ int len = stmt_encode(ctx, st, dst, 1);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 'SEC_BSS\|sec_bytes\[SEC_BSS\]\|sec_bytes\[sec\]' toolchain/as/bin_writer.c toolchain/as/elf_writer.c toolchain/ld/*.c
toolchain/ld/link_flat.c:21: ctx->merged_addr[SEC_BSS] = addr;
toolchain/ld/link_flat.c:22: uint32_t bss_end = addr + ctx->merged_size[SEC_BSS];
toolchain/ld/link_flat.c:26: s1->addr = ctx->merged_addr[SEC_BSS];
toolchain/ld/link_flat.c:125: if (k != SEC_BSS) off += ctx->merged_size[k];
toolchain/ld/link_flat.c:152: if (shidx[k] < 0 || k == SEC_BSS) continue;
toolchain/ld/link_flat.c:168: sh.sh_type = (k == SEC_BSS) ? SHT_NOBITS : SHT_PROGBITS;
toolchain/ld/link_flat.c:169: sh.sh_flags = SHF_ALLOC | (k == SEC_TEXT ? SHF_EXECINSTR : 0) | ((k == SEC_DATA || k == SEC_BSS) ? SHF_WRITE : 0);
toolchain/ld/link_static_exec.c:23: ctx->merged_addr[SEC_BSS] = addr;
toolchain/ld/link_static_exec.c:39: uint32_t memsz = (ctx->merged_addr[SEC_BSS] + ctx->merged_size[SEC_BSS]) - STATIC_EXEC_BASE;
toolchain/ld/elf_reader.c:20: if (strncmp(name, ".bss", 4) == 0) return SEC_BSS;
toolchain/ld/main.c:34: if (k != SEC_BSS && cursor[k] > 0) {
toolchain/ld/main.c:42: if (s->kind == SEC_KIND_OTHER || s->kind == SEC_BSS) continue;$ cd /home/martin/workspace/prototypes/boot-to-claude cat toolchain/as/bin_writer.c
#include "as.h"
#include <stdio.h>
/* -f bin: our source files needing this mode never declare `section`, so
* everything lands in the default SEC_TEXT bucket - just dump it. */
int write_bin(asm_ctx_t *ctx, const char *path) {
FILE *f = fopen(path, "wb");
if (!f) { perror("as: fopen"); return -1; }
size_t n = fwrite(ctx->sec_bytes[SEC_TEXT], 1, ctx->sec_len[SEC_TEXT], f);
fclose(f);
return n == ctx->sec_len[SEC_TEXT] ? 0 : -1;
}$ cd /home/martin/workspace/prototypes/boot-to-claude
gcc -Wall -Wextra -O2 -o build/toolchain/as toolchain/as/*.c 2>&1 | tail -20
fail=0
for s in build/stage2/*.S; do
o="${s%.S}.o"
out=$(./build/toolchain/as -f elf32 -o "$o" "$s" 2>&1)
rc=$?
if [ $rc -ne 0 ]; then echo "=== AS FAIL ($rc): $s ==="; echo "$out"; fail=1; fi
done
echo "assemble done fail=$fail"assemble done fail=0
$ cd /home/martin/workspace/prototypes/boot-to-claude ls build/stage2/*.o | grep -c . ls build/stage2/toolchain_as_*.o build/stage2/toolchain_hostlib_*.o echo "--- linking as ---" ./build/toolchain/ld -o build/stage2/as $(ls build/stage2/toolchain_as_*.o) $(ls build/stage2/toolchain_hostlib_*.o) 2>&1 echo "exit=$?"
21 build/stage2/toolchain_as_bin_writer.o build/stage2/toolchain_as_elf_writer.o build/stage2/toolchain_as_encode.o build/stage2/toolchain_as_lexer.o build/stage2/toolchain_as_main.o build/stage2/toolchain_as_parser.o build/stage2/toolchain_hostlib_ctype.o build/stage2/toolchain_hostlib_stdio.o build/stage2/toolchain_hostlib_stdlib.o build/stage2/toolchain_hostlib_string.o --- linking as --- ld: build/stage2/toolchain_as_encode.o: too many symbols exit=1
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'MAX_SYM\|MAX_SEC\|MAX_RELOC\|MAX_OUT\|#define MAX' toolchain/ld/ld.h
28:#define MAX_INPUT_FILES 32 29:#define MAX_SECTIONS_PER_FILE 16 30:#define MAX_SYMS_PER_FILE 512 31:#define MAX_RELOCS_PER_FILE 512 32:#define MAX_NAME 128 65: in_section_t sections[MAX_SECTIONS_PER_FILE]; 67: in_symbol_t syms[MAX_SYMS_PER_FILE]; 69: in_reloc_t relocs[MAX_RELOCS_PER_FILE]; 82:#define MAX_OUT_SYMS 2048 88: out_symbol_t out_syms[MAX_OUT_SYMS];
$ cd /home/martin/workspace/prototypes/boot-to-claude readelf -s build/stage2/toolchain_as_encode.o 2>/dev/null | wc -l readelf -r build/stage2/toolchain_as_encode.o 2>/dev/null | wc -l for f in build/stage2/toolchain_*.o; do n=$(readelf -s "$f" 2>/dev/null | wc -l) echo "$n $f" done | sort -rn | head -5
816 175 816 build/stage2/toolchain_as_encode.o 716 build/stage2/toolchain_cc_parser.o 636 build/stage2/toolchain_cc_codegen.o 542 build/stage2/toolchain_as_parser.o 329 build/stage2/toolchain_cc_lexer.o
<tool_use_error>InputValidationError: Read was called with input that could not be parsed as JSON.
You sent (first 103 of 103 bytes): {"file_path": "/home/martin/workspace/prototypes/boot-to-claude/toolchain/ld/ld.h", "offset": 20, 40}
}
Common causes: unescaped backslashes in file paths (use / or \\), unescaped control characters, or truncated output. Retry with valid JSON.</tool_use_error>20 #include <stddef.h>
21
22 static inline void safe_strcpy(char *dst, const char *src, size_t dstsize) {
23 size_t i = 0;
24 for (; i + 1 < dstsize && src[i]; i++) dst[i] = src[i];
25 dst[i] = '\0';
26 }
27
28 #define MAX_INPUT_FILES 32
29 #define MAX_SECTIONS_PER_FILE 16
30 #define MAX_SYMS_PER_FILE 512
31 #define MAX_RELOCS_PER_FILE 512
32 #define MAX_NAME 128
33
34 typedef enum { SEC_TEXT, SEC_RODATA, SEC_DATA, SEC_BSS, SEC_KIND_COUNT, SEC_KIND_OTHER } sec_kind_t;
35
36 typedef struct {
37 char name[MAX_NAME];
38 sec_kind_t kind; /* classified from the section name; SEC_KIND_OTHER is skipped (e.g. .symtab) */
39 int is_nobits; /* .bss: no file content, size still counts */
40 uint32_t file_off; /* offset of this section's bytes within in_file_t.data (PROGBITS only) */
41 uint32_t size;
42 uint32_t merged_off; /* filled in during layout: offset within the OUTPUT section of this kind */
43 } in_section_t;
44
45 typedef struct {
46 char name[MAX_NAME];
47 int defined; /* 0 = extern/undefined in this file */
48 int is_global;
49 int section_idx; /* index into this file's sections[], -1 if undefined */
50 uint32_t value; /* offset within that section, as given by the object file */
51 } in_symbol_t;
52
53 typedef struct {
54 int section_idx; /* which of this file's sections the relocation patches */
55 uint32_t offset; /* byte offset within that section */
56 int symbol_idx; /* which of this file's symbols is referenced */
57 int type; /* R_386_32 or R_386_PC32 */
58 } in_reloc_t;
59 - #define MAX_INPUT_FILES 32 - #define MAX_SECTIONS_PER_FILE 16 - #define MAX_SYMS_PER_FILE 512 - #define MAX_RELOCS_PER_FILE 512 - #define MAX_NAME 128 + #define MAX_INPUT_FILES 32 + #define MAX_SECTIONS_PER_FILE 16 + /* Generous - Phase T8's self-hosting build links object files with + * hundreds of symbols each (toolchain/as/encode.c's own object alone + * has 800+, mostly its many small static encoding-table entries and + * helper functions - see docs/toolchain.md), well past what any + * hand-written kernel/boot source file ever needed. */ + #define MAX_SYMS_PER_FILE 4096 + #define MAX_RELOCS_PER_FILE 4096 + #define MAX_NAME 128
The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/ld/ld.h has been updated successfully. (file state is current in your context — no need to Read it back)
78 /* Global (whole-link) symbol table entry - one per unique defined name
79 * across all input files, with its final linked address filled in once
80 * layout is decided. */
81 typedef struct {
82 char name[MAX_NAME];
83 uint32_t addr;
84 int defined;
85 } out_symbol_t;
86
87 #define MAX_OUT_SYMS 2048
88
89 typedef struct {
90 in_file_t files[MAX_INPUT_FILES];
91 int n_files;
92 - #define MAX_OUT_SYMS 2048 + #define MAX_OUT_SYMS 16384
The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/ld/ld.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/ld toolchain/ld/*.c 2>&1 | tail -20 ./build/toolchain/ld -o build/stage2/as $(ls build/stage2/toolchain_as_*.o) $(ls build/stage2/toolchain_hostlib_*.o) echo "exit=$?" ls -la build/stage2/as 2>/dev/null
exit=0 -rwxr-xr-x 1 martin martin 86096 Jul 19 04:41 build/stage2/as
$ cd /home/martin/workspace/prototypes/boot-to-claude ./build/toolchain/ld -o build/stage2/cc $(ls build/stage2/toolchain_cc_*.o) $(ls build/stage2/toolchain_hostlib_*.o) echo "cc link exit=$?" ./build/toolchain/ld -o build/stage2/ld $(ls build/stage2/toolchain_ld_*.o) $(ls build/stage2/toolchain_hostlib_*.o) echo "ld link exit=$?" ls -la build/stage2/as build/stage2/cc build/stage2/ld chmod +x build/stage2/as build/stage2/cc build/stage2/ld file build/stage2/as build/stage2/cc build/stage2/ld
cc link exit=0 ld: undefined reference to 'chmod' (from build/stage2/toolchain_ld_link_static_exec.o) ld link exit=1 ls: cannot access 'build/stage2/ld': No such file or directory -rwxr-xr-x 1 martin martin 86096 Jul 19 04:41 build/stage2/as -rwxr-xr-x 1 martin martin 98696 Jul 19 04:41 build/stage2/cc chmod: cannot access 'build/stage2/ld': No such file or directory build/stage2/as: ELF 32-bit LSB executable, Intel i386, version 1 (SYSV), statically linked, no section header build/stage2/cc: ELF 32-bit LSB executable, Intel i386, version 1 (SYSV), statically linked, no section header build/stage2/ld: cannot open `build/stage2/ld' (No such file or directory)
#include <sys/stat.h>
#include <hostsys.h>
int chmod(const char *path, int mode) {
return __sys_chmod(path, mode);
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/toolchain/hostlib/stat.c (file state is current in your context — no need to Read it back)
$ cd /home/martin/workspace/prototypes/boot-to-claude ./build/toolchain/cc -Itoolchain/include --target=host -o build/stage2/toolchain_hostlib_stat.S toolchain/hostlib/stat.c && \ ./build/toolchain/as -f elf32 -o build/stage2/toolchain_hostlib_stat.o build/stage2/toolchain_hostlib_stat.S && \ ./build/toolchain/ld -o build/stage2/ld $(ls build/stage2/toolchain_ld_*.o) $(ls build/stage2/toolchain_hostlib_*.o) echo "exit=$?" ls -la build/stage2/as build/stage2/cc build/stage2/ld chmod +x build/stage2/ld
Exit code 1 ld: undefined reference to 'strtoul' (from build/stage2/toolchain_ld_main.o) exit=1 ls: cannot access 'build/stage2/ld': No such file or directory -rwxr-xr-x 1 martin martin 86096 Jul 19 04:41 build/stage2/as -rwxr-xr-x 1 martin martin 98696 Jul 19 04:41 build/stage2/cc chmod: cannot access 'build/stage2/ld': No such file or directory
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'strtoul\|strtol\b' toolchain/ld/*.c toolchain/as/*.c toolchain/cc/*.c
toolchain/ld/main.c:128: flat_base = (uint32_t)strtoul(argv[++i], NULL, 0);
- int64_t strtoll(const char *s, char **endptr, int base); + int64_t strtoll(const char *s, char **endptr, int base); + + /* base 0 (used by ld/main.c's `--flat-base 0xADDR`) auto-detects a + * "0x"/"0X" prefix as hex, otherwise decimal - no octal auto-detect + * (nothing in this codebase ever passes a leading-zero value meaning + * octal). */ + unsigned long strtoul(const char *s, char **endptr, int base);
The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/include/stdlib.h has been updated successfully. (file state is current in your context — no need to Read it back)
- if (endptr) *endptr = (char *)s;
- return neg ? -v : v;
- }
+ if (endptr) *endptr = (char *)s;
+ return neg ? -v : v;
+ }
+
+ unsigned long strtoul(const char *s, char **endptr, int base) {
+ if (base == 0) {
+ if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) base = 16;
+ else base = 10;
+ }
+ if (base == 16 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) s += 2;
+ unsigned long v = 0;
+ while (*s) {
+ char c = *s;
+ int d;
+ if (c >= '0' && c <= '9') d = c - '0';
+ else if (c >= 'a' && c <= 'f') d = c - 'a' + 10;
+ else if (c >= 'A' && c <= 'F') d = c - 'A' + 10;
+ else break;
+ if (d >= base) break;
+ v = v * (unsigned long)base + (unsigned long)d;
+ s++;
+ }
+ if (endptr) *endptr = (char *)s;
+ return v;
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/hostlib/stdlib.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 ./build/toolchain/cc -Itoolchain/include --target=host -o build/stage2/toolchain_hostlib_stdlib.S toolchain/hostlib/stdlib.c && \ ./build/toolchain/as -f elf32 -o build/stage2/toolchain_hostlib_stdlib.o build/stage2/toolchain_hostlib_stdlib.S && \ ./build/toolchain/ld -o build/stage2/ld $(ls build/stage2/toolchain_ld_*.o) $(ls build/stage2/toolchain_hostlib_*.o) echo "exit=$?" ls -la build/stage2/as build/stage2/cc build/stage2/ld chmod +x build/stage2/as build/stage2/cc build/stage2/ld 2>/dev/null file build/stage2/as build/stage2/cc build/stage2/ld
exit=0 -rwxr-xr-x 1 martin martin 86096 Jul 19 04:41 build/stage2/as -rwxr-xr-x 1 martin martin 98696 Jul 19 04:41 build/stage2/cc -rwxr-xr-x 1 martin martin 29612 Jul 19 04:41 build/stage2/ld build/stage2/as: ELF 32-bit LSB executable, Intel i386, version 1 (SYSV), statically linked, no section header build/stage2/cc: ELF 32-bit LSB executable, Intel i386, version 1 (SYSV), statically linked, no section header build/stage2/ld: ELF 32-bit LSB executable, Intel i386, version 1 (SYSV), statically linked, no section header
$ cd /home/martin/workspace/prototypes/boot-to-claude ./build/stage2/as 2>&1; echo "as exit=$?" echo --- ./build/stage2/cc 2>&1; echo "cc exit=$?" echo --- ./build/stage2/ld 2>&1; echo "ld exit=$?"
usage: as -f bin|elf32 -o <out> <in.S> as exit=1 --- usage: cc [-I dir]... [--target=kernel|host] -o <out.S> <in.c> cc exit=1 --- usage: ld [-T <script>] [--flat-base 0xADDR] [--format elf|bin] -o <out> <in.o>... ld exit=1
$ cd /home/martin/workspace/prototypes/boot-to-claude CC_BIN=./build/stage2/cc AS_BIN=./build/stage2/as LD_BIN=./build/stage2/ld ./tools/run_toolchain_tests.sh 2>&1 | tail -25
./tools/run_toolchain_tests.sh: line 50: 3717209 Segmentation fault (core dumped) "$CC" -I toolchain/include --target=host -o "$BUILD/$name.S" "$src"
$ cd /home/martin/workspace/prototypes/boot-to-claude for f in tests/toolchain/*.c; do echo "=== $f ===" ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" echo "exit=$?" done 2>&1
=== tests/toolchain/t01_basic.c === /bin/bash: line 7: 3717221 Segmentation fault (core dumped) ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" exit=139 === tests/toolchain/t02_control.c === /bin/bash: line 7: 3717228 Segmentation fault (core dumped) ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" exit=139 === tests/toolchain/t02_types.c === /bin/bash: line 7: 3717235 Segmentation fault (core dumped) ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" exit=139 === tests/toolchain/t03_recursion.c === /bin/bash: line 7: 3717242 Segmentation fault (core dumped) ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" exit=139 === tests/toolchain/t04_pointers.c === /bin/bash: line 7: 3717248 Segmentation fault (core dumped) ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" exit=139 === tests/toolchain/t05_structs.c === /bin/bash: line 7: 3717254 Segmentation fault (core dumped) ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" exit=139 === tests/toolchain/t06_switch.c === /bin/bash: line 7: 3717261 Segmentation fault (core dumped) ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" exit=139 === tests/toolchain/t07_comma.c === /bin/bash: line 7: 3717267 Segmentation fault (core dumped) ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" exit=139 === tests/toolchain/t08_forward_struct.c === /bin/bash: line 7: 3717277 Segmentation fault (core dumped) ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" exit=139 === tests/toolchain/t09_local_init.c === /bin/bash: line 7: 3717285 Segmentation fault (core dumped) ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" exit=139 === tests/toolchain/t10_short.c === /bin/bash: line 7: 3717291 Segmentation fault (core dumped) ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" exit=139 === tests/toolchain/t11_array_size.c === /bin/bash: line 7: 3717298 Segmentation fault (core dumped) ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" exit=139 === tests/toolchain/t12_local_array_size.c === /bin/bash: line 7: 3717304 Segmentation fault (core dumped) ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" exit=139 === tests/toolchain/t13_struct_copy.c === /bin/bash: line 7: 3717310 Segmentation fault (core dumped) ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" exit=139 === tests/toolchain/t14_struct_return.c === /bin/bash: line 7: 3717317 Segmentation fault (core dumped) ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" exit=139 === tests/toolchain/t15_escapes.c === /bin/bash: line 7: 3717325 Segmentation fault (core dumped) ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" exit=139
$ cd /home/martin/workspace/prototypes/boot-to-claude gdb -q -batch -ex run -ex bt --args ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S tests/toolchain/t01_basic.c 2>&1 | tail -20
Program received signal SIGSEGV, Segmentation fault. 0x08051734 in ?? () #0 0x08051734 in ?? () #1 0x080546f5 in ?? () #2 0x08054cec in ?? () #3 0x08059fb3 in ?? () #4 0x0805152d in ?? () #5 0x08051679 in ?? ()
$ cd /home/martin/workspace/prototypes/boot-to-claude
cat > /tmp/hltest.c <<'EOF'
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf("hello %d\n", 42);
void *p = malloc(16);
printf("malloc ok\n");
FILE *f = fopen("/tmp/hltest.c", "rb");
printf("fopen ok fd=%d\n", f ? f->fd : -1);
char buf[64];
size_t n = fread(buf, 1, 10, f);
printf("fread n=%d\n", (int)n);
fclose(f);
return 0;
}
EOF
./build/toolchain/cc -Itoolchain/include --target=host -o /tmp/hltest.S /tmp/hltest.c && \
./build/toolchain/as -f elf32 -o /tmp/hltest.o /tmp/hltest.S && \
./build/toolchain/ld -o /tmp/hltest /tmp/hltest.o build/stage2/toolchain_hostlib_*.o
echo "link exit=$?"
/tmp/hltest
echo "run exit=$?"link exit=0 hello 42 malloc ok fopen ok fd=3 fread n=10 run exit=0
$ cd /home/martin/workspace/prototypes/boot-to-claude
echo 'int main(void) { return 0; }' > /tmp/tiny.c
./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/tiny.c
echo "parse-only exit=$?"cc: parsed /tmp/tiny.c OK parse-only exit=0
$ cd /home/martin/workspace/prototypes/boot-to-claude ./build/stage2/cc -Itoolchain/include --target=host -o /tmp/tiny.S /tmp/tiny.c echo "exit=$?" cat /tmp/tiny.S 2>/dev/null
exit=0
BITS 32
section .text
global main
main:
push ebp
mov ebp, esp
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 tests/toolchain/t01_basic.c echo "--- parse-only ---" ./build/stage2/cc -Itoolchain/include --target=host --parse-only tests/toolchain/t01_basic.c echo "exit=$?"
int add(int a, int b) {
return a + b;
}
int main(void) {
return add(2, 3); /* expect 5 */
}
--- parse-only ---
/bin/bash: line 8: 3717459 Segmentation fault (core dumped) ./build/stage2/cc -Itoolchain/include --target=host --parse-only tests/toolchain/t01_basic.c
exit=139$ cd /home/martin/workspace/prototypes/boot-to-claude
echo 'int add(int a, int b) { return a + b; }
int main(void) { return 0; }' > /tmp/t2.c
./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/t2.c
echo "exit1=$?"
echo 'int main(void) { return add(2,3); }
int add(int a, int b) { return a + b; }' > /tmp/t3.c
./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/t3.c
echo "exit2=$?"/bin/bash: line 13: 3717476 Segmentation fault (core dumped) ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/t2.c exit1=139 /bin/bash: line 18: 3717483 Segmentation fault (core dumped) ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/t3.c exit2=139
$ cd /home/martin/workspace/prototypes/boot-to-claude
echo 'int add(int a, int b) { return a + b; }' > /tmp/t4.c
./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/t4.c
echo "exit1=$?"
echo 'int main(void) { return 0; }
int main2(void) { return 1; }' > /tmp/t5.c
./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/t5.c
echo "exit2=$?"/bin/bash: line 11: 3717494 Segmentation fault (core dumped) ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/t4.c exit1=139 cc: parsed /tmp/t5.c OK exit2=0
$ cd /home/martin/workspace/prototypes/boot-to-claude
echo 'int add(int a) { return a; }' > /tmp/t6.c
./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/t6.c
echo "exit1=$?"/bin/bash: line 6: 3717510 Segmentation fault (core dumped) ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/t6.c exit1=139
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'static type_t \*parse_param_list' -A 55 toolchain/cc/parser.c | head -60
356:static type_t *parse_param_list(parser_t *p, char names[][MAX_NAME], int *n_names) {
357- type_t *head = NULL, *tail = NULL;
358- int n = 0;
359- if (check(p, TK_RPAREN)) return NULL;
360- if (is_kw(p, "void") && p->toks[p->pos + 1].kind == TK_RPAREN) { advance(p); return NULL; }
361- for (;;) {
362- /* `...` (variadic tail, e.g. `fprintf`'s or codegen.c's own
363- * `out(const char *fmt, ...)`): stops named-parameter parsing
364- * here with no further storage of its own - call sites don't
365- * check argument counts against a prototype (each call arg is
366- * parsed/pushed independently, see codegen_call_or_builtin), so
367- * nothing downstream needs to know this function is variadic
368- * beyond "stop expecting more named params". */
369- if (check(p, TK_ELLIPSIS)) { advance(p); break; }
370- type_t *base = parse_type_spec(p, NULL, NULL);
371- type_t *pt = base;
372- char nm[MAX_NAME] = {0};
373- while (match(p, TK_STAR)) pt = ptr_to(pt);
374- if (check(p, TK_LPAREN) && p->toks[p->pos + 1].kind == TK_STAR) {
375- /* function-pointer parameter: TYPE (*name)(PARAMS), e.g.
376- * parse_binop_level's `ast_node_t *(*next)(parser_t *)` -
377- * mirrors parse_declarator's identical form, duplicated here
378- * (rather than delegating to it) because plain parameters in
379- * this loop tolerate an unnamed parameter and parse_declarator
380- * doesn't. */
381- advance(p);
382- expect(p, TK_STAR);
383- if (check(p, TK_IDENT)) { safe_strcpy(nm, cur(p).text, MAX_NAME); advance(p); }
384- expect(p, TK_RPAREN);
385- expect(p, TK_LPAREN);
386- type_t *functy = type_new(TY_FUNC);
387- functy->base = pt;
388- functy->param = parse_param_list(p, NULL, NULL);
389- expect(p, TK_RPAREN);
390- pt = ptr_to(functy);
391- } else {
392- if (check(p, TK_IDENT)) { safe_strcpy(nm, cur(p).text, MAX_NAME); advance(p); }
393- while (match(p, TK_LBRACKET)) {
394- if (!check(p, TK_RBRACKET)) advance(p);
395- expect(p, TK_RBRACKET);
396- pt = ptr_to(pt); /* array param decays to pointer */
397- }
398- }
399- type_t *node = type_new(pt->kind);
400- *node = *pt;
401- node->next = NULL;
402- if (tail) tail->next = node; else head = node;
403- tail = node;
404- if (names && n_names) safe_strcpy(names[n], nm, MAX_NAME);
405- n++;
406- if (!match(p, TK_COMMA)) break;
407- }
408- if (n_names) *n_names = n;
409- return head;
410-}
411-$ cd /home/martin/workspace/prototypes/boot-to-claude cat tests/toolchain/t13_struct_copy.c
/* 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 */
}$ cd /home/martin/workspace/prototypes/boot-to-claude
cat > /tmp/tptrcopy.c <<'EOF'
typedef struct {
int a;
int b;
} pair_t;
int main(void) {
pair_t x;
x.a = 10;
x.b = 20;
pair_t y;
y.a = 0;
y.b = 0;
pair_t *px = &x;
pair_t *py = &y;
*py = *px;
return y.a + y.b;
}
EOF
./build/toolchain/cc -Itoolchain/include --target=host -o /tmp/tptrcopy.S /tmp/tptrcopy.c && \
./build/toolchain/as -f elf32 -o /tmp/tptrcopy.o /tmp/tptrcopy.S && \
./build/toolchain/ld -o /tmp/tptrcopy /tmp/tptrcopy.o
/tmp/tptrcopy
echo "exit=$? (expect 30)"exit=30 (expect 30)
120 /* ---------------- expressions (forward decls) ---------------- */ 121 122 static node_t *parse_expr(parser_t *p); 123 static node_t *parse_assign(parser_t *p); 124 static node_t *parse_cast(parser_t *p); 125 static node_t *parse_unary(parser_t *p); 126 static type_t *parse_type_name(parser_t *p); /* for casts/sizeof(type) */ 127 static node_t *parse_stmt(parser_t *p); 128 static node_t *parse_block(parser_t *p); 129 static node_t *make_binop(tok_kind_t op, node_t *a, node_t *b); /* for va_start/va_arg desugaring */ 130
- static node_t *make_binop(tok_kind_t op, node_t *a, node_t *b); /* for va_start/va_arg desugaring */ + static node_t *make_binop(tok_kind_t op, node_t *a, node_t *b); /* for va_start/va_arg desugaring */ + static type_t *parse_array_dims(parser_t *p, type_t *base); /* for multi-dim array param decay */
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)
- } 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 */
- }
- }
+ } else {
+ if (check(p, TK_IDENT)) { safe_strcpy(nm, cur(p).text, MAX_NAME); advance(p); }
+ if (check(p, TK_LBRACKET)) {
+ /* Only the *outermost* dimension decays to a pointer -
+ * `T x[][M]` as a parameter is `T (*x)[M]`, not `T**`.
+ * (Real bug: parse_param_list's own declaration, right
+ * here in this file, is `char names[][MAX_NAME]` - Phase
+ * T8's self-hosting build is what first parsed this
+ * function's signature through CC instead of gcc, and
+ * the old always-ptr_to-every-bracket version silently
+ * mis-typed its own `names` parameter as char**, making
+ * every `names[n]` inside this same function index by
+ * 4-byte pointer strides instead of MAX_NAME-byte array
+ * strides - see docs/toolchain.md.) */
+ advance(p); /* consume the outer '[' */
+ if (!check(p, TK_RBRACKET)) advance(p); /* its size, if any - irrelevant for a param */
+ expect(p, TK_RBRACKET);
+ pt = parse_array_dims(p, pt); /* any further [N] groups stay real array dims */
+ pt = ptr_to(pt);
+ }
+ }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 -Wall -Wextra -O2 -o build/toolchain/cc toolchain/cc/*.c 2>&1 | tail -20 ./build/toolchain/cc -Itoolchain/include --target=host --parse-only /tmp/t6.c echo "t6 exit=$?"
cc: parsed /tmp/t6.c OK t6 exit=0
$ cd /home/martin/workspace/prototypes/boot-to-claude
rm -rf build/stage2 && mkdir -p build/stage2
fail=0
for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c; do
base=$(echo "$f" | tr '/' '_')
./build/toolchain/cc -Itoolchain/include --target=host -o "build/stage2/${base%.c}.S" "$f" || { echo "CODEGEN FAIL: $f"; fail=1; }
done
for s in build/stage2/*.S; do
o="${s%.S}.o"
./build/toolchain/as -f elf32 -o "$o" "$s" || { echo "AS FAIL: $s"; fail=1; }
done
./build/toolchain/ld -o build/stage2/as $(ls build/stage2/toolchain_as_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
./build/toolchain/ld -o build/stage2/cc $(ls build/stage2/toolchain_cc_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
./build/toolchain/ld -o build/stage2/ld $(ls build/stage2/toolchain_ld_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
chmod +x build/stage2/as build/stage2/cc build/stage2/ld
echo "build fail=$fail"
./build/stage2/cc -Itoolchain/include --target=host --parse-only tests/toolchain/t01_basic.c
echo "t01 parse exit=$?"build fail=0 cc: parsed tests/toolchain/t01_basic.c OK t01 parse exit=0
$ cd /home/martin/workspace/prototypes/boot-to-claude CC_BIN=./build/stage2/cc AS_BIN=./build/stage2/as LD_BIN=./build/stage2/ld ./tools/run_toolchain_tests.sh 2>&1 | tail -25
Command did not complete within its 120s timeout and was moved to the background (ID: b3bnfufsp). Output is being written to: /tmp/claude-1001/-home-martin-workspace-prototypes-boot-to-claude/03468d1c-5b29-4e24-8f0d-2a7435d227ab/tasks/b3bnfufsp.output. You will be notified when it completes. To check interim output, use Read on that file path. Session cwd remains /home/martin/workspace/prototypes/boot-to-claude; directory changes made by the backgrounded command do not apply to subsequent commands.
{
"query": "Monitor",
"max_results": 3
}$ cd /home/martin/workspace/prototypes/boot-to-claude cat tools/run_toolchain_tests.sh | head -30
#!/usr/bin/env bash
# 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
fi
declare -A EXPECTED=($ tail -30 /home/martin/workspace/prototypes/boot-to-claude/tools/run_tests.sh 2>/dev/null | head -40 echo === head -20 /home/martin/workspace/prototypes/boot-to-claude/tools/run_tests.sh
wait_for "hello.txt"
type_line "cat hello.txt"
wait_for "Hello, tinyfs!"
type_line "write greet.txt hi there"
wait_for "write: ok"
type_line "cat greet.txt"
wait_for "hi there"
type_line "rm greet.txt"
wait_for "rm: ok"
type_line "cat greet.txt"
wait_for "cat: no such file"
type_line "help"
wait_for "commands:"
type_line "run hello.tsh"
check_fixture_output tests/expected/hello.tsh.txt
type_line "build hello.tc hello.tbc"
wait_for "build: ok"
type_line "exec hello.tbc"
check_fixture_output tests/expected/hello.tc.txt
echo "run_tests: PASS"
===
#!/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 \#!/usr/bin/env bash
# Phase T8: the self-hosting bootstrap. Builds AS/CC/LD from source three
# times - Stage1 via the host's real compiler (BOOTSTRAP_CC, only ever
# used here), Stage2 by compiling the exact same source with Stage1, and
# Stage3 by compiling it again with Stage2 - and requires Stage2 and
# Stage3 to be byte-identical. That fixed point is the standard GCC/LLVM
# proof that a compiler genuinely compiles itself, rather than having
# merely been *compiled once* by something else that happened to tolerate
# its source. See docs/toolchain.md.
#
# On success, installs Stage2 (== Stage3) at $BUILD/toolchain/{as,cc,ld} -
# the same path the main Makefile's AS/CC/LD variables already point at,
# so every OS build after this one is self-hosted, not just bootstrapped.
set -euo pipefail
cd "$(dirname "$0")/.."
BUILD=${1:-build}
BOOTSTRAP_CC=${BOOTSTRAP_CC:-gcc}
AS_SRCS=(toolchain/as/*.c)
CC_SRCS=(toolchain/cc/*.c)
LD_SRCS=(toolchain/ld/*.c)
HOSTLIB_SRCS=(toolchain/hostlib/*.c)
echo "bootstrap: stage1 (host $BOOTSTRAP_CC)"
mkdir -p "$BUILD/toolchain-stage1"
"$BOOTSTRAP_CC" -Wall -Wextra -O2 -o "$BUILD/toolchain-stage1/as" "${AS_SRCS[@]}"
"$BOOTSTRAP_CC" -Wall -Wextra -O2 -o "$BUILD/toolchain-stage1/cc" "${CC_SRCS[@]}"
"$BOOTSTRAP_CC" -Wall -Wextra -O2 -o "$BUILD/toolchain-stage1/ld" "${LD_SRCS[@]}"
# build_stage NAME: compiles toolchain/{as,cc,ld}+hostlib's source with
# $BUILD/toolchain-$PREV_STAGE's tools (set by the caller), producing
# $BUILD/toolchain-$NAME/{as,cc,ld}.
build_stage() {
local name=$1
local prev_cc=$2
local prev_as=$3
local prev_ld=$4
local outdir="$BUILD/toolchain-$name"
local objdir="$BUILD/toolchain-$name-obj"
mkdir -p "$outdir" "$objdir"
local as_objs=()
local cc_objs=()
local ld_objs=()
local hostlib_objs=()
local src tag base s o
for src in "${AS_SRCS[@]}" "${CC_SRCS[@]}" "${LD_SRCS[@]}" "${HOSTLIB_SRCS[@]}"; do
case "$src" in
toolchain/as/*) tag=as ;;
toolchain/cc/*) tag=cc ;;
toolchain/ld/*) tag=ld ;;
toolchain/hostlib/*) tag=hostlib ;;
esac
base=$(basename "$src" .c)
s="$objdir/${tag}_${base}.S"
o="$objdir/${tag}_${base}.o"
"$prev_cc" -Itoolchain/include --target=host -o "$s" "$src"
"$prev_as" -f elf32 -o "$o" "$s"
case "$tag" in
as) as_objs+=("$o") ;;
cc) cc_objs+=("$o") ;;
ld) ld_objs+=("$o") ;;
hostlib) hostlib_objs+=("$o") ;;
esac
done
"$prev_ld" -o "$outdir/as" "${as_objs[@]}" "${hostlib_objs[@]}"
"$prev_ld" -o "$outdir/cc" "${cc_objs[@]}" "${hostlib_objs[@]}"
"$prev_ld" -o "$outdir/ld" "${ld_objs[@]}" "${hostlib_objs[@]}"
chmod +x "$outdir/as" "$outdir/cc" "$outdir/ld"
}
echo "bootstrap: stage2 (via stage1)"
build_stage stage2 \
"$BUILD/toolchain-stage1/cc" "$BUILD/toolchain-stage1/as" "$BUILD/toolchain-stage1/ld"
echo "bootstrap: stage3 (via stage2)"
build_stage stage3 \
"$BUILD/toolchain-stage2/cc" "$BUILD/toolchain-stage2/as" "$BUILD/toolchain-stage2/ld"
echo "bootstrap: verifying stage2 == stage3"
fail=0
for t in as cc ld; do
if cmp -s "$BUILD/toolchain-stage2/$t" "$BUILD/toolchain-stage3/$t"; then
echo " $t: identical"
else
echo " $t: DIFFERS" >&2
fail=1
fi
done
if [ "$fail" -ne 0 ]; then
echo "bootstrap: FAIL - stage2 and stage3 are not byte-identical" >&2
exit 1
fi
mkdir -p "$BUILD/toolchain"
cp "$BUILD/toolchain-stage2/as" "$BUILD/toolchain-stage2/cc" "$BUILD/toolchain-stage2/ld" "$BUILD/toolchain/"
chmod +x "$BUILD/toolchain/as" "$BUILD/toolchain/cc" "$BUILD/toolchain/ld"
echo "bootstrap: PASS - self-hosted toolchain installed at $BUILD/toolchain"
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/tools/bootstrap_selfhost.sh (file state is current in your context — no need to Read it back)
$ chmod +x /home/martin/workspace/prototypes/boot-to-claude/tools/bootstrap_selfhost.sh
(Bash completed with no output)
$ cat /tmp/claude-1001/-home-martin-workspace-prototypes-boot-to-claude/03468d1c-5b29-4e24-8f0d-2a7435d227ab/tasks/b3bnfufsp.output 2>&1 | tail -40
(Bash completed with no output)
$ ps aux | grep -E 'stage2|run_toolchain' | grep -v grep
martin 3717913 0.0 0.0 13312 3892 ? Ss 04:45 0:00 /bin/bash -c source /home/martin/.claude/shell-snapshots/snapshot-bash-1784307599835-hlkzgk.sh 2>/dev/null || true && shopt -u extglob 2>/dev/null || true && eval 'cd /home/martin/workspace/prototypes/boot-to-claude CC_BIN=./build/stage2/cc AS_BIN=./build/stage2/as LD_BIN=./build/stage2/ld ./tools/run_toolchain_tests.sh 2>&1 | tail -25' < /dev/null && pwd -P >| /tmp/claude-be67-cwd martin 3717915 0.0 0.0 13300 3956 ? S 04:45 0:00 bash ./tools/run_toolchain_tests.sh martin 3717921 99.9 0.0 20164 17576 ? R 04:45 3:01 ./build/stage2/as -f elf32 -o build/toolchain_tests/t01_basic.o build/toolchain_tests/t01_basic.S
$ kill -9 3717921 3717915 3717913 2>/dev/null sleep 1 ps aux | grep -E 'stage2|run_toolchain' | grep -v grep echo "---" cd /home/martin/workspace/prototypes/boot-to-claude timeout 5 gdb -q -batch -ex run -ex bt --args ./build/stage2/as -f elf32 -o /tmp/t01.o build/toolchain_tests/t01_basic.S 2>&1 | tail -30
Exit code 143 --- Terminated
$ cd /home/martin/workspace/prototypes/boot-to-claude cat > /tmp/gdbscript.txt <<'EOF' set pagination off run -f elf32 -o /tmp/t01.o build/toolchain_tests/t01_basic.S EOF (gdb -q -x /tmp/gdbscript.txt --args ./build/stage2/as -f elf32 -o /tmp/t01.o build/toolchain_tests/t01_basic.S & GDBPID=$! sleep 3 kill -INT $GDBPID 2>/dev/null wait $GDBPID) 2>&1 | tail -20
as: build/toolchain_tests/t01_basic.S:18263722: expected directive/instruction as: build/toolchain_tests/t01_basic.S:18263723: expected directive/instruction as: build/toolchain_tests/t01_basic.S:18263724: expected directive/instruction as: build/toolchain_tests/t01_basic.S:18263725: expected directive/instruction as: build/toolchain_tests/t01_basic.S:18263726: expected directive/instruction as: build/toolchain_tests/t01_basic.S:18263727: expected directive/instruction as: build/toolchain_tests/t01_basic.S:18263728: expected directive/instruction as: build/toolchain_tests/t01_basic.S:18263729: expected directive/instruction as: build/toolchain_tests/t01_basic.S:18263730: expected directive/instruction as: build/toolchain_tests/t01_basic.S:18263731: expected directive/instruction as: build/toolchain_tests/t01_basic.S:18263732: expected directive/instruction as: build/toolchain_tests/t01_basic.S:18263733: expected directive/instruction Program received signal SIGINT, Interrupt. 0x08055d0c in ?? () (gdb) A debugging session is active. Inferior 1 [process 3720415] will be killed. Quit anyway? (y or n) [answered Y; input not from terminal]
$ cd /home/martin/workspace/prototypes/boot-to-claude cat toolchain/as/main.c
#include "as.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static char *read_file(const char *path) {
FILE *f = fopen(path, "rb");
if (!f) { perror("as: fopen"); exit(1); }
fseek(f, 0, SEEK_END);
long n = ftell(f);
fseek(f, 0, SEEK_SET);
char *buf = malloc((size_t)n + 1);
if (fread(buf, 1, (size_t)n, f) != (size_t)n) { perror("as: fread"); exit(1); }
buf[n] = '\0';
fclose(f);
return buf;
}
int main(int argc, char **argv) {
const char *fmt = NULL;
const char *outpath = NULL;
const char *inpath = NULL;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-f") == 0 && i + 1 < argc) { fmt = argv[++i]; }
else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) { outpath = argv[++i]; }
else { inpath = argv[i]; }
}
if (!fmt || !outpath || !inpath) {
fprintf(stderr, "usage: as -f bin|elf32 -o <out> <in.S>\n");
return 1;
}
char *src = read_file(inpath);
static asm_ctx_t ctx;
memset(&ctx, 0, sizeof(ctx));
if (as_parse_pass1(&ctx, src, inpath) != 0) {
fprintf(stderr, "as: parse errors, not assembling %s\n", inpath);
return 1;
}
if (as_encode_pass2(&ctx) != 0) {
fprintf(stderr, "as: encode errors, not writing %s\n", outpath);
return 1;
}
int rc;
if (strcmp(fmt, "bin") == 0) {
rc = write_bin(&ctx, outpath);
} else if (strcmp(fmt, "elf32") == 0) {
rc = write_elf32(&ctx, outpath);
} else {
fprintf(stderr, "as: unknown format '%s' (expected bin or elf32)\n", fmt);
return 1;
}
return rc == 0 ? 0 : 1;
}- long n = ftell(f);
- fseek(f, 0, SEEK_SET);
- char *buf = malloc((size_t)n + 1);
- if (fread(buf, 1, (size_t)n, f) != (size_t)n) { perror("as: fread"); exit(1); }
+ long n = ftell(f);
+ fprintf(stderr, "DEBUG: ftell n=%d\n", (int)n);
+ fseek(f, 0, SEEK_SET);
+ char *buf = malloc((size_t)n + 1);
+ size_t got = fread(buf, 1, (size_t)n, f);
+ fprintf(stderr, "DEBUG: fread got=%d\n", (int)got);
+ if (got != (size_t)n) { perror("as: fread"); exit(1); }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/main.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 wc -c build/toolchain_tests/t01_basic.S ./build/toolchain/cc -Itoolchain/include --target=host -o /tmp/as_main_dbg.S toolchain/as/main.c && \ ./build/toolchain/as -f elf32 -o /tmp/as_main_dbg.o /tmp/as_main_dbg.S && \ ./build/toolchain/ld -o /tmp/as_dbg /tmp/as_main_dbg.o \ $(ls build/stage2/toolchain_as_bin_writer.o build/stage2/toolchain_as_elf_writer.o build/stage2/toolchain_as_encode.o build/stage2/toolchain_as_lexer.o build/stage2/toolchain_as_parser.o) \ $(ls build/stage2/toolchain_hostlib_*.o) timeout 3 /tmp/as_dbg -f elf32 -o /tmp/dbgout.o build/toolchain_tests/t01_basic.S 2>&1 | head -10
Exit code 143 648 build/toolchain_tests/t01_basic.S Terminated
$ cd /home/martin/workspace/prototypes/boot-to-claude timeout 3 /tmp/as_dbg -f elf32 -o /tmp/dbgout.o build/toolchain_tests/t01_basic.S > /tmp/dbg.out 2>&1 echo "exit=$?" cat /tmp/dbg.out
exit=124 DEBUG: ftell n=648 DEBUG: fread got=648
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'expected directive/instruction' toolchain/as/parser.c
414: if (line_peek(ln).kind != TK_IDENT) { perr(ps, "expected directive/instruction"); return; }525 /* --- driver --- */
526
527 int as_parse_pass1(asm_ctx_t *ctx, const char *src, const char *filename) {
528 pstate_t ps = {0};
529 ps.ctx = ctx;
530 ps.bits = 16;
531 ps.cur_section = SEC_TEXT;
532 ps.filename = filename;
533
534 lexer_t lx;
535 lexer_init(&lx, src);
536
537 for (;;) {
538 line_t ln = {0};
539 for (;;) {
540 token_t t = lexer_next(&lx);
541 if (t.kind == TK_NEWLINE || t.kind == TK_EOF) {
542 if (ln.n < MAX_LINE_TOKS) ln.toks[ln.n++] = t;
543 break;
544 }
545 if (ln.n < MAX_LINE_TOKS) ln.toks[ln.n++] = t;
546 }
547 ps.line_no++;
548 int is_eof = ln.toks[ln.n - 1].kind == TK_EOF;
549 parse_line(&ps, &ln);
550 if (is_eof) break;
551 }
552
553 return ps.had_error ? -1 : 0;
554 }
555 $ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'codegen_init_local' toolchain/cc/codegen.c | head -5
41:static void codegen_init_local(int base_offset, type_t *t, node_t *init);
436: /* codegen_init_local handles both brace-init lists (e.g.
440: if (n->init) codegen_init_local(n->frame_offset, n->type, n->init);
629:static void codegen_init_local(int base_offset, type_t *t, node_t *init) {
633: codegen_init_local(base_offset + off, t->base, v);625 * writing bytes it emits store instructions relative to a base
626 * ebp-offset; a plain scalar just evaluates and stores at that offset,
627 * which also serves as the non-aggregate base case (a bare `int x = 1;`
628 * behaves like a one-element "aggregate" of itself). */
629 static void codegen_init_local(int base_offset, type_t *t, node_t *init) {
630 if (t->kind == TY_ARRAY && init->kind == ND_BLOCK) {
631 int off = 0;
632 for (node_t *v = init->list; v; v = v->next) {
633 codegen_init_local(base_offset + off, t->base, v);
634 off += type_size(t->base);
635 }
636 return;
637 }
638 if ((t->kind == TY_STRUCT || t->kind == TY_UNION) && init->kind == ND_BLOCK) {
639 member_t *m = t->members;
640 node_t *v = init->list;
641 for (; m && v; m = m->next, v = v->next) {
642 codegen_init_local(base_offset + m->offset, m->type, v);
643 }
644 return;
645 }
646 out(" lea eax, [ebp%+d]\n", base_offset);
647 out(" push eax\n");
648 codegen_expr(init);
649 out(" pop ecx\n");
650 store_value(t);
651 }
652
653 static void codegen_func(node_t *fn) {
654 safe_strcpy(g_cur_func, fn->name, MAX_NAME);
655 if (fn->is_global) out("global %s\n", fn->name); /* static functions keep internal linkage - no `global` */
656 out("%s:\n", fn->name);
657 out(" push ebp\n mov ebp, esp\n");
658 if (fn->num) out(" sub esp, %d\n", (int)fn->num);
659 codegen_stmt(fn->b);
660 out(" mov eax, 0\n mov esp, ebp\n pop ebp\n ret\n");
661 }
662
663 extern int is_builtin_name(const char *name);
664
665 #define MAX_EXTERNS 64
666 static char g_externs[MAX_EXTERNS][MAX_NAME];
667 static int g_n_externs;
668
669 static int already_extern(const char *name) {
670 for (int i = 0; i < g_n_externs; i++) if (strcmp(g_externs[i], name) == 0) return 1;
671 return 0;
672 }
673
674 #define MAX_STRINGS 256
675 static node_t *g_strings[MAX_STRINGS];
676
677 #define MAX_STATICS 64
678 static node_t *g_statics[MAX_STATICS];
679 static int g_n_statics;
680 static int g_n_strings;
681
682 /* Walks every reachable node (function bodies, global initializers - a/b/
683 * c/d/list/init/next cover every kind of child link used anywhere in the
684 * AST), collecting (not yet emitting - the caller isn't in the right
685 * section context here) two things in one pass: names of called-but-not-
686 * locally-defined, non-builtin functions (need `extern`), and string
687 * literal nodes (need their bytes emitted into a data section later). */
688 /* skip_next=1 for the one call the top-level driver makes per top-level
689 * node (see codegen_emit) - that loop already advances over prog's own610 for (; m && v; m = m->next, v = v->next) {
611 if (m->offset > cur_off) out(" resb %d\n", m->offset - cur_off);
612 emit_data_bytes(v, m->type);
613 cur_off = m->offset + type_size(m->type);
614 }
615 int total = type_size(t);
616 if (total > cur_off) out(" resb %d\n", total - cur_off);
617 return;
618 }
619 emit_scalar_bytes(init, t);
620 }
621
622 /* Runtime equivalent of emit_data_bytes, for a local (stack) variable's
623 * brace-init - e.g. `exec_result_t r = { EXEC_NORMAL, VAL_ZERO };` (used
624 * in kernel/lang/script/eval.c). Recurses the same way, but instead of
625 * writing bytes it emits store instructions relative to a base
626 * ebp-offset; a plain scalar just evaluates and stores at that offset,
627 * which also serves as the non-aggregate base case (a bare `int x = 1;`
628 * behaves like a one-element "aggregate" of itself). */
629 static void codegen_init_local(int base_offset, type_t *t, node_t *init) {- 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;
- }
+ static void codegen_zero_range(int base_offset, int size) {
+ char ltop[MAX_NAME], lend[MAX_NAME];
+ new_label(ltop); new_label(lend);
+ out(" push ecx\n");
+ out(" mov ecx, 0\n");
+ out("%s:\n", ltop);
+ out(" cmp ecx, %d\n", size);
+ out(" jge %s\n", lend);
+ out(" lea eax, [ebp%+d]\n", base_offset);
+ out(" add eax, ecx\n");
+ out(" mov byte [eax], 0\n");
+ out(" add ecx, 1\n");
+ out(" jmp %s\n", ltop);
+ out("%s:\n", lend);
+ out(" pop ecx\n");
+ }
+
+ static void codegen_init_local(int base_offset, type_t *t, node_t *init) {
+ /* A short brace-init list (fewer values than the aggregate has
+ * elements/members - e.g. `line_t ln = {0};` for a struct with three
+ * members, one of them a whole array) leaves every uncovered byte at
+ * whatever garbage was already on the stack, not zero - the same gap
+ * emit_data_bytes already closes for *global* aggregates via a
+ * trailing `resb`, but nothing did for a local one until now (see
+ * docs/toolchain.md, Phase T8: this exact gap, on this exact
+ * pattern, sent as/parser.c's own line-tokenizing loop into an
+ * infinite loop reading uninitialized bounds/EOF-check fields). Zero
+ * the whole range up front (redundant on nested recursive calls, but
+ * harmless and simplest) so every element/member not explicitly
+ * listed below is correctly zero rather than garbage. */
+ if ((t->kind == TY_ARRAY || t->kind == TY_STRUCT || t->kind == TY_UNION) && init->kind == ND_BLOCK) {
+ codegen_zero_range(base_offset, type_size(t));
+ }
+ 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;
+ }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)
- long n = ftell(f);
- fprintf(stderr, "DEBUG: ftell n=%d\n", (int)n);
- fseek(f, 0, SEEK_SET);
- char *buf = malloc((size_t)n + 1);
- size_t got = fread(buf, 1, (size_t)n, f);
- fprintf(stderr, "DEBUG: fread got=%d\n", (int)got);
- if (got != (size_t)n) { perror("as: fread"); exit(1); }
+ long n = ftell(f);
+ fseek(f, 0, SEEK_SET);
+ char *buf = malloc((size_t)n + 1);
+ if (fread(buf, 1, (size_t)n, f) != (size_t)n) { perror("as: fread"); exit(1); }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/main.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/cc toolchain/cc/*.c 2>&1 | tail -20
gcc -Wall -Wextra -O2 -o build/toolchain/as toolchain/as/*.c 2>&1 | tail -20
gcc -Wall -Wextra -O2 -o build/toolchain/ld toolchain/ld/*.c 2>&1 | tail -20
echo "stage1 rebuilt"
rm -rf build/stage2 && mkdir -p build/stage2
fail=0
for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c; do
base=$(echo "$f" | tr '/' '_')
./build/toolchain/cc -Itoolchain/include --target=host -o "build/stage2/${base%.c}.S" "$f" || { echo "CODEGEN FAIL: $f"; fail=1; }
done
for s in build/stage2/*.S; do
o="${s%.S}.o"
./build/toolchain/as -f elf32 -o "$o" "$s" || { echo "AS FAIL: $s"; fail=1; }
done
./build/toolchain/ld -o build/stage2/as $(ls build/stage2/toolchain_as_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
./build/toolchain/ld -o build/stage2/cc $(ls build/stage2/toolchain_cc_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
./build/toolchain/ld -o build/stage2/ld $(ls build/stage2/toolchain_ld_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
chmod +x build/stage2/as build/stage2/cc build/stage2/ld
echo "stage2 build fail=$fail"stage1 rebuilt stage2 build fail=0
$ cd /home/martin/workspace/prototypes/boot-to-claude timeout 60 ./build/stage2/as -f elf32 -o /tmp/t01.o build/toolchain_tests/t01_basic.S echo "exit=$?"
/bin/bash: line 4: 3723182 Segmentation fault (core dumped) timeout 60 ./build/stage2/as -f elf32 -o /tmp/t01.o build/toolchain_tests/t01_basic.S exit=139
$ cd /home/martin/workspace/prototypes/boot-to-claude gdb -q -batch -ex run -ex bt --args ./build/stage2/as -f elf32 -o /tmp/t01.o build/toolchain_tests/t01_basic.S 2>&1 | tail -15
Program received signal SIGSEGV, Segmentation fault. 0x0805cc3c in ?? () #0 0x0805cc3c in ?? () #1 0x08056241 in ?? () #2 0x0805868c in ?? () #3 0x0805a32b in ?? () #4 0x08059517 in ?? () #5 0x0805abac in ?? () #6 0x0805582e in ?? () #7 0x08055a0e in ?? ()
380 /* --- one line --- */
381
382 static void parse_rest_of_line(pstate_t *ps, line_t *ln);
383
384 static void parse_line(pstate_t *ps, line_t *ln) {
385 if (line_peek(ln).kind == TK_EOF) return;
386
387 /* label: */
388 if (line_peek(ln).kind == TK_IDENT) {
389 line_t save = *ln;
390 token_t name = line_next(ln);
391 if (line_peek(ln).kind == TK_COLON) {
392 line_next(ln);
393 define_label(ps, name.text);
394 parse_rest_of_line(ps, ln);
395 return;
396 }
397 /* NAME equ EXPR */
398 if (line_is_ident(ln, "equ")) {
399 line_next(ln);
400 value_t v = eval_expr(ps, ln);
401 if (v.has_sym) { perr(ps, "equ value must be constant"); return; }
402 symbol_t *s = sym_get_or_create(ps->ctx, name.text);
403 s->is_equ = 1;
404 s->equ_value = v.val;
405 return;
406 }
407 *ln = save;
408 }
409 parse_rest_of_line(ps, ln);
410 }
411
412 static void parse_rest_of_line(pstate_t *ps, line_t *ln) {
413 if (line_peek(ln).kind == TK_EOF || line_peek(ln).kind == TK_NEWLINE) return;
414 if (line_peek(ln).kind != TK_IDENT) { perr(ps, "expected directive/instruction"); return; }
415
416 token_t head = line_next(ln);
417
418 if (ieq(head.text, "BITS")) { ps->bits = (int)line_next(ln).number; return; }
419 if (ieq(head.text, "ORG")) {1 #include "as.h"
2
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6
7 static char *read_file(const char *path) {
8 FILE *f = fopen(path, "rb");
9 if (!f) { perror("as: fopen"); exit(1); }
10 fseek(f, 0, SEEK_END);
11 long n = ftell(f);
12 fseek(f, 0, SEEK_SET);
13 char *buf = malloc((size_t)n + 1);
14 if (fread(buf, 1, (size_t)n, f) != (size_t)n) { perror("as: fread"); exit(1); }
15 buf[n] = '\0';
16 fclose(f);
17 return buf;
18 }
19
20 int main(int argc, char **argv) {
21 const char *fmt = NULL;
22 const char *outpath = NULL;
23 const char *inpath = NULL;
24
25 for (int i = 1; i < argc; i++) {
26 if (strcmp(argv[i], "-f") == 0 && i + 1 < argc) { fmt = argv[++i]; }
27 else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) { outpath = argv[++i]; }
28 else { inpath = argv[i]; }
29 }
30 if (!fmt || !outpath || !inpath) {
31 fprintf(stderr, "usage: as -f bin|elf32 -o <out> <in.S>\n");
32 return 1;
33 }
34
35 char *src = read_file(inpath);
36
37 static asm_ctx_t ctx;
38 memset(&ctx, 0, sizeof(ctx));
39
40 if (as_parse_pass1(&ctx, src, inpath) != 0) {
41 fprintf(stderr, "as: parse errors, not assembling %s\n", inpath);
42 return 1;
43 }
44 if (as_encode_pass2(&ctx) != 0) {
45 fprintf(stderr, "as: encode errors, not writing %s\n", outpath);
46 return 1;
47 }
48
49 int rc;
50 if (strcmp(fmt, "bin") == 0) {
51 rc = write_bin(&ctx, outpath);
52 } else if (strcmp(fmt, "elf32") == 0) {
53 rc = write_elf32(&ctx, outpath);
54 } else {
55 fprintf(stderr, "as: unknown format '%s' (expected bin or elf32)\n", fmt);
56 return 1;
57 }
58 return rc == 0 ? 0 : 1;
59 }
60 - char *src = read_file(inpath);
-
- static asm_ctx_t ctx;
- memset(&ctx, 0, sizeof(ctx));
-
- if (as_parse_pass1(&ctx, src, inpath) != 0) {
+ char *src = read_file(inpath);
+ fprintf(stderr, "DBG: read %s\n", inpath);
+
+ static asm_ctx_t ctx;
+ memset(&ctx, 0, sizeof(ctx));
+ fprintf(stderr, "DBG: ctx zeroed\n");
+
+ if (as_parse_pass1(&ctx, src, inpath) != 0) {The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/main.c has been updated successfully. (file state is current in your context — no need to Read it back)
- static void parse_line(pstate_t *ps, line_t *ln) {
- if (line_peek(ln).kind == TK_EOF) return;
+ static void parse_line(pstate_t *ps, line_t *ln) {
+ fprintf(stderr, "DBG: parse_line n=%d line_no=%d\n", ln->n, ps->line_no);
+ if (line_peek(ln).kind == TK_EOF) return;The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/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
for f in toolchain/as/main.c toolchain/as/parser.c; do
base=$(echo "$f" | tr '/' '_')
./build/toolchain/cc -Itoolchain/include --target=host -o "build/stage2/${base%.c}.S" "$f"
./build/toolchain/as -f elf32 -o "build/stage2/${base%.c}.o" "build/stage2/${base%.c}.S"
done
./build/toolchain/ld -o build/stage2/as $(ls build/stage2/toolchain_as_*.o) $(ls build/stage2/toolchain_hostlib_*.o)
chmod +x build/stage2/as
timeout 10 ./build/stage2/as -f elf32 -o /tmp/t01.o build/toolchain_tests/t01_basic.S 2>&1 | tail -30
echo "exit=$?"DBG: read build/toolchain_tests/t01_basic.S DBG: ctx zeroed DBG: parse_line n=3 line_no=1 DBG: parse_line n=3 line_no=2 DBG: parse_line n=3 line_no=3 DBG: parse_line n=3 line_no=4 DBG: parse_line n=3 line_no=5 exit=0
$ cd /home/martin/workspace/prototypes/boot-to-claude timeout 10 ./build/stage2/as -f elf32 -o /tmp/t01.o build/toolchain_tests/t01_basic.S echo "real exit=$?" sed -n '1,7p' build/toolchain_tests/t01_basic.S
DBG: read build/toolchain_tests/t01_basic.S
DBG: ctx zeroed
DBG: parse_line n=3 line_no=1
DBG: parse_line n=3 line_no=2
DBG: parse_line n=3 line_no=3
DBG: parse_line n=3 line_no=4
DBG: parse_line n=3 line_no=5
/bin/bash: line 5: 3723421 Segmentation fault (core dumped) timeout 10 ./build/stage2/as -f elf32 -o /tmp/t01.o build/toolchain_tests/t01_basic.S
real exit=139
BITS 32
section .text
global add
add:
push ebp
mov ebp, esp
lea eax, [ebp+12]- /* label: */
- if (line_peek(ln).kind == TK_IDENT) {
- line_t save = *ln;
- token_t name = line_next(ln);
- if (line_peek(ln).kind == TK_COLON) {
- line_next(ln);
- define_label(ps, name.text);
- parse_rest_of_line(ps, ln);
- return;
- }
+ /* label: */
+ if (line_peek(ln).kind == TK_IDENT) {
+ fprintf(stderr, "DBG: is ident, about to copy line_t\n");
+ line_t save = *ln;
+ fprintf(stderr, "DBG: copied, save.n=%d\n", save.n);
+ token_t name = line_next(ln);
+ fprintf(stderr, "DBG: name=%s\n", name.text);
+ if (line_peek(ln).kind == TK_COLON) {
+ fprintf(stderr, "DBG: is colon, defining label\n");
+ line_next(ln);
+ define_label(ps, name.text);
+ fprintf(stderr, "DBG: label defined\n");
+ parse_rest_of_line(ps, ln);
+ return;
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/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 ./build/toolchain/cc -Itoolchain/include --target=host -o build/stage2/toolchain_as_parser.S toolchain/as/parser.c ./build/toolchain/as -f elf32 -o build/stage2/toolchain_as_parser.o build/stage2/toolchain_as_parser.S ./build/toolchain/ld -o build/stage2/as $(ls build/stage2/toolchain_as_*.o) $(ls build/stage2/toolchain_hostlib_*.o) chmod +x build/stage2/as timeout 10 ./build/stage2/as -f elf32 -o /tmp/t01.o build/toolchain_tests/t01_basic.S echo "exit=$?"
DBG: read build/toolchain_tests/t01_basic.S DBG: ctx zeroed DBG: parse_line n=3 line_no=1 DBG: is ident, about to copy line_t DBG: copied, save.n=3 DBG: name=BITS DBG: parse_line n=3 line_no=2 DBG: is ident, about to copy line_t DBG: copied, save.n=3 DBG: name=section DBG: parse_line n=3 line_no=3 DBG: is ident, about to copy line_t DBG: copied, save.n=3 DBG: name=global DBG: parse_line n=3 line_no=4 DBG: is ident, about to copy line_t DBG: copied, save.n=3 DBG: name=add DBG: is colon, defining label DBG: label defined DBG: parse_line n=3 line_no=5 DBG: is ident, about to copy line_t DBG: copied, save.n=3 DBG: name=push /bin/bash: line 12: 3723477 Segmentation fault (core dumped) timeout 10 ./build/stage2/as -f elf32 -o /tmp/t01.o build/toolchain_tests/t01_basic.S exit=139
384 static void parse_line(pstate_t *ps, line_t *ln) {
385 fprintf(stderr, "DBG: parse_line n=%d line_no=%d\n", ln->n, ps->line_no);
386 if (line_peek(ln).kind == TK_EOF) return;
387
388 /* label: */
389 if (line_peek(ln).kind == TK_IDENT) {
390 fprintf(stderr, "DBG: is ident, about to copy line_t\n");
391 line_t save = *ln;
392 fprintf(stderr, "DBG: copied, save.n=%d\n", save.n);
393 token_t name = line_next(ln);
394 fprintf(stderr, "DBG: name=%s\n", name.text);
395 if (line_peek(ln).kind == TK_COLON) {
396 fprintf(stderr, "DBG: is colon, defining label\n");
397 line_next(ln);
398 define_label(ps, name.text);
399 fprintf(stderr, "DBG: label defined\n");
400 parse_rest_of_line(ps, ln);
401 return;
402 }
403 /* NAME equ EXPR */
404 if (line_is_ident(ln, "equ")) {
405 line_next(ln);
406 value_t v = eval_expr(ps, ln);
407 if (v.has_sym) { perr(ps, "equ value must be constant"); return; }
408 symbol_t *s = sym_get_or_create(ps->ctx, name.text);
409 s->is_equ = 1;
410 s->equ_value = v.val;
411 return;
412 }
413 *ln = save;- /* NAME equ EXPR */
- if (line_is_ident(ln, "equ")) {
- line_next(ln);
- value_t v = eval_expr(ps, ln);
- if (v.has_sym) { perr(ps, "equ value must be constant"); return; }
- symbol_t *s = sym_get_or_create(ps->ctx, name.text);
- s->is_equ = 1;
- s->equ_value = v.val;
- return;
- }
- *ln = save;
+ /* NAME equ EXPR */
+ fprintf(stderr, "DBG: checking equ\n");
+ if (line_is_ident(ln, "equ")) {
+ line_next(ln);
+ value_t v = eval_expr(ps, ln);
+ if (v.has_sym) { perr(ps, "equ value must be constant"); return; }
+ symbol_t *s = sym_get_or_create(ps->ctx, name.text);
+ s->is_equ = 1;
+ s->equ_value = v.val;
+ return;
+ }
+ fprintf(stderr, "DBG: not equ, restoring ln from save\n");
+ *ln = save;
+ fprintf(stderr, "DBG: restored\n");The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/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
./build/toolchain/cc -Itoolchain/include --target=host -o build/stage2/toolchain_as_parser.S toolchain/as/parser.c
./build/toolchain/as -f elf32 -o build/stage2/toolchain_as_parser.o build/stage2/toolchain_as_parser.S
./build/toolchain/ld -o build/stage2/as $(ls build/stage2/toolchain_as_*.o) $(ls build/stage2/toolchain_hostlib_*.o)
chmod +x build/stage2/as
timeout 10 ./build/stage2/as -f elf32 -o /tmp/t01.o build/toolchain_tests/t01_basic.S 2>&1 | tail -10
echo "exit=${PIPESTATUS[0]}"DBG: name=add DBG: is colon, defining label DBG: label defined DBG: parse_line n=3 line_no=5 DBG: is ident, about to copy line_t DBG: copied, save.n=3 DBG: name=push DBG: checking equ DBG: not equ, restoring ln from save DBG: restored exit=139
416 fprintf(stderr, "DBG: restored\n");
417 }
418 parse_rest_of_line(ps, ln);
419 }
420
421 static void parse_rest_of_line(pstate_t *ps, line_t *ln) {
422 if (line_peek(ln).kind == TK_EOF || line_peek(ln).kind == TK_NEWLINE) return;
423 if (line_peek(ln).kind != TK_IDENT) { perr(ps, "expected directive/instruction"); return; }
424
425 token_t head = line_next(ln);
426
427 if (ieq(head.text, "BITS")) { ps->bits = (int)line_next(ln).number; return; }
428 if (ieq(head.text, "ORG")) {
429 value_t v = eval_expr(ps, ln);
430 ps->ctx->org = (uint32_t)v.val;
431 ps->ctx->have_org = 1;
432 return;
433 }
434 if (ieq(head.text, "section")) {
435 token_t name = line_next(ln);
436 ps->cur_section = normalize_section(name.text);
437 return;
438 }
439 if (ieq(head.text, "global")) {
440 token_t name = line_next(ln);
441 sym_get_or_create(ps->ctx, name.text)->is_global = 1;
442 return;
443 }
444 if (ieq(head.text, "extern")) {
445 token_t name = line_next(ln);
446 sym_get_or_create(ps->ctx, name.text)->is_extern = 1;
447 return;
448 }
449 if (ieq(head.text, "align")) {
450 value_t v = eval_expr(ps, ln);
451 uint32_t n = (uint32_t)v.val;
452 uint32_t cur = ps->offsets[ps->cur_section];
453 uint32_t pad = (n - (cur % n)) % n;
454 stmt_t st = {0};
455 safe_strcpy(st.mnemonic, ".resb", sizeof(st.mnemonic));
456 st.nops = 1;
457 st.ops[0].kind = OPND_IMM;
458 st.ops[0].disp = pad;
459 push_stmt(ps, st);
460 return;
461 }
462 if (ieq(head.text, "resb")) {
463 value_t v = eval_expr(ps, ln);
464 stmt_t st = {0};
465 safe_strcpy(st.mnemonic, ".resb", sizeof(st.mnemonic));
466 st.nops = 1;
467 st.ops[0].kind = OPND_IMM;
468 st.ops[0].disp = v.val;
469 push_stmt(ps, st);
470 return;
471 }
472 if (ieq(head.text, "times")) {
473 value_t count = eval_expr(ps, ln);
474 token_t sizekw = line_next(ln); /* db/dw/dd */
475 value_t val = eval_expr(ps, ln);
476 (void)sizekw; /* our only usage is `times N db V` */
477 stmt_t st = {0};
478 safe_strcpy(st.mnemonic, ".pad", sizeof(st.mnemonic));
479 st.nops = 2;
480 st.ops[0].disp = count.val;
481 st.ops[1].disp = val.val;
482 push_stmt(ps, st);
483 return;
484 }
485 if (ieq(head.text, "db")) { parse_data_directive(ps, ln, ".db"); return; }
486 if (ieq(head.text, "dw")) { parse_data_directive(ps, ln, ".dw"); return; }
487 if (ieq(head.text, "dd")) { parse_data_directive(ps, ln, ".dd"); return; }
488 if (ieq(head.text, "dq")) { parse_data_directive(ps, ln, ".dq"); return; }
489
490 /* instruction */
491 stmt_t st = {0};
492 safe_strcpy(st.mnemonic, head.text, sizeof(st.mnemonic));
493 for (char *p = st.mnemonic; *p; p++) *p = (char)tolower((unsigned char)*p);
494
495 if (line_peek(ln).kind != TK_NEWLINE && line_peek(ln).kind != TK_EOF) {- static void parse_rest_of_line(pstate_t *ps, line_t *ln) {
- if (line_peek(ln).kind == TK_EOF || line_peek(ln).kind == TK_NEWLINE) return;
- if (line_peek(ln).kind != TK_IDENT) { perr(ps, "expected directive/instruction"); return; }
-
- token_t head = line_next(ln);
+ static void parse_rest_of_line(pstate_t *ps, line_t *ln) {
+ fprintf(stderr, "DBG: parse_rest_of_line entered\n");
+ if (line_peek(ln).kind == TK_EOF || line_peek(ln).kind == TK_NEWLINE) return;
+ if (line_peek(ln).kind != TK_IDENT) { perr(ps, "expected directive/instruction"); return; }
+
+ token_t head = line_next(ln);
+ fprintf(stderr, "DBG: head=%s\n", head.text);The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/parser.c has been updated successfully. (file state is current in your context — no need to Read it back)
- /* instruction */
- stmt_t st = {0};
- safe_strcpy(st.mnemonic, head.text, sizeof(st.mnemonic));
- for (char *p = st.mnemonic; *p; p++) *p = (char)tolower((unsigned char)*p);
-
- if (line_peek(ln).kind != TK_NEWLINE && line_peek(ln).kind != TK_EOF) {
+ /* instruction */
+ fprintf(stderr, "DBG: reached instruction parsing for %s\n", head.text);
+ stmt_t st = {0};
+ fprintf(stderr, "DBG: st zeroed\n");
+ safe_strcpy(st.mnemonic, head.text, sizeof(st.mnemonic));
+ fprintf(stderr, "DBG: mnemonic copied\n");
+ for (char *p = st.mnemonic; *p; p++) *p = (char)tolower((unsigned char)*p);
+ fprintf(stderr, "DBG: lowercased\n");
+
+ if (line_peek(ln).kind != TK_NEWLINE && line_peek(ln).kind != TK_EOF) {The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/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
./build/toolchain/cc -Itoolchain/include --target=host -o build/stage2/toolchain_as_parser.S toolchain/as/parser.c
./build/toolchain/as -f elf32 -o build/stage2/toolchain_as_parser.o build/stage2/toolchain_as_parser.S
./build/toolchain/ld -o build/stage2/as $(ls build/stage2/toolchain_as_*.o) $(ls build/stage2/toolchain_hostlib_*.o)
chmod +x build/stage2/as
timeout 10 ./build/stage2/as -f elf32 -o /tmp/t01.o build/toolchain_tests/t01_basic.S 2>&1 | tail -15
echo "exit=${PIPESTATUS[0]}"DBG: label defined DBG: parse_rest_of_line entered DBG: parse_line n=3 line_no=5 DBG: is ident, about to copy line_t DBG: copied, save.n=3 DBG: name=push DBG: checking equ DBG: not equ, restoring ln from save DBG: restored DBG: parse_rest_of_line entered DBG: head=push DBG: reached instruction parsing for push DBG: st zeroed DBG: mnemonic copied DBG: lowercased exit=139
498 for (char *p = st.mnemonic; *p; p++) *p = (char)tolower((unsigned char)*p);
499 fprintf(stderr, "DBG: lowercased\n");
500
501 if (line_peek(ln).kind != TK_NEWLINE && line_peek(ln).kind != TK_EOF) {
502 st.ops[0] = parse_operand(ps, ln);
503 st.nops = 1;
504 /* far jmp: `jmp SEG:OFFSET` - the value just parsed as ops[0] is
505 * actually the segment; re-read it as ops[0].far_seg and parse
506 * the offset as the real ops[0]. */
507 if (ieq(st.mnemonic, "jmp") && line_peek(ln).kind == TK_COLON) {
508 line_next(ln);
509 operand_t seg = st.ops[0];
510 operand_t off = parse_operand(ps, ln);
511 operand_t far = {0};
512 far.kind = OPND_FAR;
513 far.has_sym = off.has_sym;
514 safe_strcpy(far.sym, off.sym, MAX_NAME);
515 far.disp = off.disp;
516 far.far_seg = seg.disp;
517 far.far_seg_has_sym = seg.has_sym; /* e.g. a forward-referenced EQU like CODE_SEG - deferred to pass 2 */
518 safe_strcpy(far.far_seg_sym, seg.sym, MAX_NAME);
519 st.ops[0] = far;
520 } else if (line_peek(ln).kind == TK_COMMA) {
521 line_next(ln);
522 st.ops[1] = parse_operand(ps, ln);
523 st.nops = 2;
524 /* only `imul r32, r/m32, imm32` uses a 3rd operand - without
525 * this, a trailing `, imm` was silently dropped (never
526 * consumed, never reported as an error) and the immediate
527 * scale factor was lost, e.g. `imul ecx, ecx, 4` for array
528 * indexing silently became `imul ecx, ecx` (self-square
529 * instead of *4) - see docs/toolchain.md. */
530 if (line_peek(ln).kind == TK_COMMA) {
531 line_next(ln);
532 st.ops[2] = parse_operand(ps, ln);
533 st.nops = 3;
534 }
535 }
536 }
537 push_stmt(ps, st);- if (line_peek(ln).kind != TK_NEWLINE && line_peek(ln).kind != TK_EOF) {
- st.ops[0] = parse_operand(ps, ln);
- st.nops = 1;
+ if (line_peek(ln).kind != TK_NEWLINE && line_peek(ln).kind != TK_EOF) {
+ fprintf(stderr, "DBG: about to parse_operand\n");
+ operand_t o0 = parse_operand(ps, ln);
+ fprintf(stderr, "DBG: parsed operand kind=%d\n", o0.kind);
+ st.ops[0] = o0;
+ fprintf(stderr, "DBG: assigned to ops[0]\n");
+ st.nops = 1;The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/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
./build/toolchain/cc -Itoolchain/include --target=host -o build/stage2/toolchain_as_parser.S toolchain/as/parser.c
./build/toolchain/as -f elf32 -o build/stage2/toolchain_as_parser.o build/stage2/toolchain_as_parser.S
./build/toolchain/ld -o build/stage2/as $(ls build/stage2/toolchain_as_*.o) $(ls build/stage2/toolchain_hostlib_*.o)
chmod +x build/stage2/as
timeout 10 ./build/stage2/as -f elf32 -o /tmp/t01.o build/toolchain_tests/t01_basic.S 2>&1 | tail -8
echo "exit=${PIPESTATUS[0]}"DBG: restored DBG: parse_rest_of_line entered DBG: head=push DBG: reached instruction parsing for push DBG: st zeroed DBG: mnemonic copied DBG: lowercased DBG: about to parse_operand exit=139
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'static operand_t parse_operand\|^operand_t parse_operand' toolchain/as/parser.c
270:static operand_t parse_operand(pstate_t *ps, line_t *ln) {238 /* --- operand parsing --- */
239
240 static int size_keyword(const char *w, int *size) {
241 if (ieq(w, "byte")) { *size = 1; return 1; }
242 if (ieq(w, "word")) { *size = 2; return 1; }
243 if (ieq(w, "dword")) { *size = 4; return 1; }
244 return 0;
245 }
246
247 static operand_t parse_mem(pstate_t *ps, line_t *ln) {
248 operand_t o = {0};
249 o.kind = OPND_MEM;
250 /* [reg], [reg+disp], [reg-disp], [sym], [sym+disp] */
251 token_t t = line_peek(ln);
252 if (t.kind == TK_IDENT) {
253 const regdef_t *r = reg_lookup(t.text);
254 if (r) { o.reg = r->reg; line_next(ln); }
255 }
256 if (o.reg == R_NONE) {
257 value_t v = eval_expr(ps, ln);
258 if (v.has_sym) { o.has_sym = 1; safe_strcpy(o.sym, v.sym, MAX_NAME); o.disp = v.val; }
259 else { o.disp = v.val; }
260 } else if (line_peek(ln).kind == TK_PLUS || line_peek(ln).kind == TK_MINUS) {
261 int neg = line_peek(ln).kind == TK_MINUS;
262 line_next(ln);
263 value_t v = eval_expr(ps, ln);
264 o.disp = neg ? -v.val : v.val; /* our only reg+/-disp forms use plain constants */
265 }
266 if (line_peek(ln).kind == TK_RBRACK) line_next(ln); else perr(ps, "expected ]");
267 return o;
268 }
269
270 static operand_t parse_operand(pstate_t *ps, line_t *ln) {
271 operand_t o = {0};
272 int sz = 0;
273 if (line_peek(ln).kind == TK_IDENT && size_keyword(line_peek(ln).text, &sz)) {
274 line_next(ln);
275 if (line_peek(ln).kind == TK_LBRACK) {
276 /* `word|dword [mem]` */
277 line_next(ln);
278 o = parse_mem(ps, ln);
279 o.size_override = sz;
280 return o;
281 }
282 /* `dword N` - a size hint on an immediate (e.g. `push dword 0`) */
283 value_t v = eval_expr(ps, ln);
284 o.kind = OPND_IMM;
285 o.has_sym = v.has_sym;
286 if (v.has_sym) safe_strcpy(o.sym, v.sym, MAX_NAME);
287 o.disp = v.val;
288 o.size_override = sz;
289 return o;
290 }
291 if (line_peek(ln).kind == TK_LBRACK) {
292 line_next(ln);
293 o = parse_mem(ps, ln);
294 return o;
295 }
296 if (line_peek(ln).kind == TK_IDENT) {
297 const regdef_t *r = reg_lookup(line_peek(ln).text);
298 if (r) {
299 line_next(ln);
300 o.kind = OPND_REG;
301 o.reg = r->reg;
302 return o;
303 }
304 }
305 value_t v = eval_expr(ps, ln);
306 o.kind = OPND_IMM;
307 o.has_sym = v.has_sym;- if (line_peek(ln).kind == TK_IDENT) {
- const regdef_t *r = reg_lookup(line_peek(ln).text);
- if (r) {
- line_next(ln);
- o.kind = OPND_REG;
- o.reg = r->reg;
- return o;
- }
- }
- value_t v = eval_expr(ps, ln);
+ if (line_peek(ln).kind == TK_IDENT) {
+ fprintf(stderr, "DBG: parse_operand ident=%s\n", line_peek(ln).text);
+ const regdef_t *r = reg_lookup(line_peek(ln).text);
+ fprintf(stderr, "DBG: reg_lookup returned %p\n", (void*)r);
+ if (r) {
+ line_next(ln);
+ o.kind = OPND_REG;
+ o.reg = r->reg;
+ fprintf(stderr, "DBG: about to return o, kind=%d reg=%d\n", o.kind, o.reg);
+ return o;
+ }
+ }
+ value_t v = eval_expr(ps, ln);The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/parser.c has been updated successfully. (file state is current in your context — no need to Read it back)
- fprintf(stderr, "DBG: reg_lookup returned %p\n", (void*)r); + fprintf(stderr, "DBG: reg_lookup returned nonnull=%d\n", r != NULL);
The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/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
./build/toolchain/cc -Itoolchain/include --target=host -o build/stage2/toolchain_as_parser.S toolchain/as/parser.c
./build/toolchain/as -f elf32 -o build/stage2/toolchain_as_parser.o build/stage2/toolchain_as_parser.S
./build/toolchain/ld -o build/stage2/as $(ls build/stage2/toolchain_as_*.o) $(ls build/stage2/toolchain_hostlib_*.o)
chmod +x build/stage2/as
timeout 10 ./build/stage2/as -f elf32 -o /tmp/t01.o build/toolchain_tests/t01_basic.S 2>&1 | tail -10
echo "exit=${PIPESTATUS[0]}"DBG: not equ, restoring ln from save DBG: restored DBG: parse_rest_of_line entered DBG: head=push DBG: reached instruction parsing for push DBG: st zeroed DBG: mnemonic copied DBG: lowercased DBG: about to parse_operand DBG: parse_operand ident=ebp exit=139
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'reg_lookup\|regdef_t\|register table\|--- register table' toolchain/as/parser.c | head -10
82:/* --- register table --- */
84:typedef struct { const char *name; reg_t reg; int size; } regdef_t;
85:static const regdef_t REGS[] = {
96:static const regdef_t *reg_lookup(const char *name) {
253: const regdef_t *r = reg_lookup(t.text);
298: const regdef_t *r = reg_lookup(line_peek(ln).text);
299: fprintf(stderr, "DBG: reg_lookup returned nonnull=%d\n", r != NULL);82 /* --- register table --- */
83
84 typedef struct { const char *name; reg_t reg; int size; } regdef_t;
85 static const regdef_t REGS[] = {
86 {"al",R_AL,1},{"cl",R_CL,1},{"dl",R_DL,1},{"bl",R_BL,1},
87 {"ah",R_AH,1},{"ch",R_CH,1},{"dh",R_DH,1},{"bh",R_BH,1},
88 {"ax",R_AX,2},{"cx",R_CX,2},{"dx",R_DX,2},{"bx",R_BX,2},
89 {"sp",R_SP,2},{"bp",R_BP,2},{"si",R_SI,2},{"di",R_DI,2},
90 {"eax",R_EAX,4},{"ecx",R_ECX,4},{"edx",R_EDX,4},{"ebx",R_EBX,4},
91 {"esp",R_ESP,4},{"ebp",R_EBP,4},{"esi",R_ESI,4},{"edi",R_EDI,4},
92 {"es",R_ES,2},{"cs",R_CS,2},{"ss",R_SS,2},{"ds",R_DS,2},{"fs",R_FS,2},{"gs",R_GS,2},
93 {"cr0",R_CR0,4},
94 };
95
96 static const regdef_t *reg_lookup(const char *name) {
97 for (size_t i = 0; i < sizeof(REGS) / sizeof(REGS[0]); i++) {
98 if (strcmp(REGS[i].name, name) == 0) return ®S[i];
99 }
100 return NULL;
101 }
102
103 /* --- expression evaluation ---
104 * Returns a value that's either a pure constant or "one symbol + constant
105 * addend" - the only two shapes this codebase's expressions ever need
106 * (see docs/toolchain.md). Symbols already defined fold to constants
107 * immediately; at most one *undefined-so-far* symbol may appear. */
108
109 typedef struct {
110 int has_sym;
111 char sym[MAX_NAME];
112 int64_t val; /* constant, or addend when has_sym */
113 } value_t;
114
115 static value_t val_const(int64_t v) { value_t r = {0, {0}, v}; return r; }
116
117 static value_t eval_primary(pstate_t *ps, line_t *ln);
118 static value_t eval_expr(pstate_t *ps, line_t *ln);
119
120 /* NASM-style local labels (`.name`) are scoped to the most recent
121 * preceding global label - qualify to a unique internal name so
122 * `.hang`/`.loop`/etc. reused across many labels don't collide. */
123 static void qualify_local(pstate_t *ps, const char *raw, char *out, size_t outsz) {
124 if (raw[0] == '.') {
125 size_t i = 0;
126 const char *g = ps->ctx->last_global_label;
127 for (; i + 1 < outsz && g[i]; i++) out[i] = g[i];
128 for (size_t j = 0; i + 1 < outsz && raw[j]; i++, j++) out[i] = raw[j];
129 out[i] = '\0';
130 } else {
131 safe_strcpy(out, raw, outsz);
132 }
133 }
134
135 static value_t val_add(pstate_t *ps, value_t a, value_t b, int negate_b) {
136 if (a.has_sym && b.has_sym) {
137 /* A difference of two labels already defined in the same
138 * section is link-time-invariant (just a byte count), so it's
139 * safe to fold to a plain constant regardless of context (EQU
140 * or an operand/data value) - this is what gdt_end - gdt_start
141 * and CODE_SEG/DATA_SEG rely on. Anything else combining two
142 * still-symbolic values isn't needed by this codebase. */
143 if (negate_b) {
144 symbol_t *sa = sym_find(ps->ctx, a.sym);
145 symbol_t *sb = sym_find(ps->ctx, b.sym);
146 if (sa && sa->defined && sb && sb->defined && sa->section == sb->section) {
147 int64_t diff = (int64_t)sa->offset - (int64_t)sb->offset;
148 return val_const(diff + a.val - b.val);
149 }
150 }
151 perr(ps, "expression combines two unresolved symbols");
152 return val_const(0);
153 }
154 value_t r;
155 r.has_sym = a.has_sym || b.has_sym;
156 if (r.has_sym) {
157 if (a.has_sym) { safe_strcpy(r.sym, a.sym, MAX_NAME); r.val = a.val + (negate_b ? -b.val : b.val); }
158 else { safe_strcpy(r.sym, b.sym, MAX_NAME); r.val = (negate_b ? -b.val : b.val) + a.val; }
159 } else {
160 r.val = a.val + (negate_b ? -b.val : b.val);
161 r.sym[0] = '\0';
162 }
163 return r;
164 }
165
166 static value_t eval_primary(pstate_t *ps, line_t *ln) {
167 token_t t = line_peek(ln);
168 if (t.kind == TK_NUMBER) { line_next(ln); return val_const(t.number); }
169 if (t.kind == TK_MINUS) {
170 line_next(ln);
171 value_t v = eval_primary(ps, ln);
172 if (v.has_sym) { perr(ps, "cannot negate a symbol"); return val_const(0); }
173 return val_const(-v.val);
174 }
175 if (t.kind == TK_LPAREN) {
176 line_next(ln);
177 value_t v = eval_expr(ps, ln);
178 if (line_peek(ln).kind == TK_RPAREN) line_next(ln); else perr(ps, "expected )");
179 return v;
180 }
181 if (t.kind == TK_DOLLAR) {
182 line_next(ln);
183 return val_const((int64_t)ps->offsets[ps->cur_section]);- static const regdef_t *reg_lookup(const char *name) {
- for (size_t i = 0; i < sizeof(REGS) / sizeof(REGS[0]); i++) {
- if (strcmp(REGS[i].name, name) == 0) return ®S[i];
- }
- return NULL;
- }
+ static const regdef_t *reg_lookup(const char *name) {
+ fprintf(stderr, "DBG: reg_lookup entered name=%s count=%d\n", name, (int)(sizeof(REGS) / sizeof(REGS[0])));
+ for (size_t i = 0; i < sizeof(REGS) / sizeof(REGS[0]); i++) {
+ fprintf(stderr, "DBG: i=%d REGS[i].name_is_null=%d\n", (int)i, REGS[i].name == NULL);
+ if (strcmp(REGS[i].name, name) == 0) return ®S[i];
+ }
+ return NULL;
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/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
./build/toolchain/cc -Itoolchain/include --target=host -o build/stage2/toolchain_as_parser.S toolchain/as/parser.c
./build/toolchain/as -f elf32 -o build/stage2/toolchain_as_parser.o build/stage2/toolchain_as_parser.S
./build/toolchain/ld -o build/stage2/as $(ls build/stage2/toolchain_as_*.o) $(ls build/stage2/toolchain_hostlib_*.o)
chmod +x build/stage2/as
timeout 10 ./build/stage2/as -f elf32 -o /tmp/t01.o build/toolchain_tests/t01_basic.S 2>&1 | tail -15
echo "exit=${PIPESTATUS[0]}"DBG: copied, save.n=3 DBG: name=push DBG: checking equ DBG: not equ, restoring ln from save DBG: restored DBG: parse_rest_of_line entered DBG: head=push DBG: reached instruction parsing for push DBG: st zeroed DBG: mnemonic copied DBG: lowercased DBG: about to parse_operand DBG: parse_operand ident=ebp DBG: reg_lookup entered name=ebp count=31 DBG: i=0 REGS[i].name_is_null=1 exit=139
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'emit_data_bytes\|emit_scalar_bytes' toolchain/cc/codegen.c | head -10
581:static void emit_data_bytes(node_t *init, type_t *t);
583:static void emit_scalar_bytes(node_t *v, type_t *t) {
588:static void emit_data_bytes(node_t *init, type_t *t) {
595: for (node_t *v = init->list; v; v = v->next) { emit_data_bytes(v, t->base); count++; }
612: emit_data_bytes(v, m->type);
619: emit_scalar_bytes(init, t);
622:/* Runtime equivalent of emit_data_bytes, for a local (stack) variable's
651: * emit_data_bytes already closes for *global* aggregates via a
880: emit_data_bytes(n->init, n->type);
889: emit_data_bytes(n->init, n->type);560 * continue_label rather than giving this frame its own. */
561 safe_strcpy(lc.continue_label, g_loop ? g_loop->continue_label : "", MAX_NAME);
562 g_loop = &lc;
563 for (node_t *s = n->b->list; s; s = s->next) {
564 if (s->kind == ND_CASE || s->kind == ND_DEFAULT) out("%s:\n", s->asm_label);
565 else codegen_stmt(s);
566 }
567 g_loop = lc.parent;
568 out("%s:\n", lend);
569 return;
570 }
571 case ND_NOP:
572 return;
573 default:
574 fprintf(stderr, "cc: internal error: unsupported statement kind %d\n", n->kind);
575 exit(1);
576 }
577 }
578
579 /* ---------------- top level ---------------- */
580
581 static void emit_data_bytes(node_t *init, type_t *t);
582
583 static void emit_scalar_bytes(node_t *v, type_t *t) {
584 if (t->kind == TY_CHAR) out(" db %lld\n", (long long)v->num);
585 else out(" dd %lld\n", (long long)v->num);
586 }
587
588 static void emit_data_bytes(node_t *init, type_t *t) {
589 if (!init) {
590 out(" resb %d\n", type_size(t));
591 return;
592 }
593 if (t->kind == TY_ARRAY && init->kind == ND_BLOCK) {
594 int count = 0;
595 for (node_t *v = init->list; v; v = v->next) { emit_data_bytes(v, t->base); count++; }
596 int pad = t->array_len - count;
597 for (int i = 0; i < pad; i++) out(" resb %d\n", type_size(t->base));
598 return;
599 }
600 if ((t->kind == TY_STRUCT || t->kind == TY_UNION) && init->kind == ND_BLOCK) {
601 /* Positional: walk declared members alongside the initializer
602 * list (no designated-initializer support - see
603 * docs/toolchain.md). A union brace-init like value_t's
604 * `{ VAL_INT, { 0 } }` naturally works here too: its one element
605 * targets the first member at offset 0, and the trailing-padding
606 * check below fills out the rest of the union's size. */
607 member_t *m = t->members;
608 node_t *v = init->list;
609 int cur_off = 0;
610 for (; m && v; m = m->next, v = v->next) {
611 if (m->offset > cur_off) out(" resb %d\n", m->offset - cur_off);
612 emit_data_bytes(v, m->type);
613 cur_off = m->offset + type_size(m->type);
614 }
615 int total = type_size(t);
616 if (total > cur_off) out(" resb %d\n", total - cur_off);
617 return;
618 }
619 emit_scalar_bytes(init, t);581 static void emit_data_bytes(node_t *init, type_t *t);
582
583 static void emit_scalar_bytes(node_t *v, type_t *t) {
584 if (t->kind == TY_CHAR) out(" db %lld\n", (long long)v->num);
585 else out(" dd %lld\n", (long long)v->num);
586 }
587 - static void emit_scalar_bytes(node_t *v, type_t *t) {
- if (t->kind == TY_CHAR) out(" db %lld\n", (long long)v->num);
- else out(" dd %lld\n", (long long)v->num);
- }
+ static void emit_scalar_bytes(node_t *v, type_t *t) {
+ /* A string-literal initializer for a pointer-typed field (e.g.
+ * toolchain/as/parser.c's `static const regdef_t REGS[] =
+ * {{"al",R_AL,1}, ...}` - Phase T8's self-hosting build is what
+ * first exercised a string literal inside a struct-array global
+ * initializer) needs the string's own label, not v->num - an ND_STR
+ * node never sets ->num at all, so this silently emitted `dd 0`
+ * (a NULL pointer) instead of a relocation to the string's bytes. */
+ if (v->kind == ND_STR) { out(" dd %s\n", v->asm_label); return; }
+ if (t->kind == TY_CHAR) out(" db %lld\n", (long long)v->num);
+ else out(" dd %lld\n", (long long)v->num);
+ }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_ex\|g_strings\[' toolchain/cc/codegen.c | head -20
715:static node_t *g_strings[MAX_STRINGS];
743:static void walk_collect_ex(node_t *n, node_t *prog, int skip_next) {
764: g_strings[g_n_strings++] = n;
773: walk_collect_ex(n->a, prog, 0);
774: walk_collect_ex(n->b, prog, 0);
775: walk_collect_ex(n->c, prog, 0);
776: walk_collect_ex(n->d, prog, 0);
777: walk_collect_ex(n->list, prog, 0);
778: walk_collect_ex(n->init, prog, 0);
779: if (!skip_next) walk_collect_ex(n->next, prog, 0);
795: * chain itself (see walk_collect_ex's comment). */
796: if (n->kind != ND_FUNC_DEF) walk_collect_ex(n, prog, 1); /* globals: just for string literals in initializers */
797: else walk_collect_ex(n->b, prog, 1); /* function bodies */
855: node_t *s = g_strings[i];$ cd /home/martin/workspace/prototypes/boot-to-claude git diff --stat toolchain/as/main.c toolchain/as/parser.c
toolchain/as/main.c | 2 ++ toolchain/as/parser.c | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-)
$ cd /home/martin/workspace/prototypes/boot-to-claude git diff toolchain/as/main.c
diff --git a/toolchain/as/main.c b/toolchain/as/main.c
index 4c4b3c7..ae30739 100644
--- a/toolchain/as/main.c
+++ b/toolchain/as/main.c
@@ -33,9 +33,11 @@ int main(int argc, char **argv) {
}
char *src = read_file(inpath);
+ fprintf(stderr, "DBG: read %s\n", inpath);
static asm_ctx_t ctx;
memset(&ctx, 0, sizeof(ctx));
+ fprintf(stderr, "DBG: ctx zeroed\n");
if (as_parse_pass1(&ctx, src, inpath) != 0) {
fprintf(stderr, "as: parse errors, not assembling %s\n", inpath);$ cd /home/martin/workspace/prototypes/boot-to-claude git checkout -- toolchain/as/main.c git diff toolchain/as/main.c echo "clean"
clean
$ cd /home/martin/workspace/prototypes/boot-to-claude git diff toolchain/as/parser.c
diff --git a/toolchain/as/parser.c b/toolchain/as/parser.c
index a6127f4..0463c2e 100644
--- a/toolchain/as/parser.c
+++ b/toolchain/as/parser.c
@@ -33,7 +33,13 @@ typedef struct {
static token_t line_peek(line_t *ln) {
if (ln->pos < ln->n) return ln->toks[ln->pos];
- return (token_t){ .kind = TK_EOF };
+ /* Not a compound literal (CC doesn't support them - see
+ * docs/toolchain.md, Phase T3/T8): memset then set the one field
+ * that matters, same zeroed-rest semantics as `{ .kind = TK_EOF }`. */
+ token_t t;
+ memset(&t, 0, sizeof(t));
+ t.kind = TK_EOF;
+ return t;
}
static token_t line_next(line_t *ln) {
token_t t = line_peek(ln);
@@ -88,7 +94,9 @@ static const regdef_t REGS[] = {
};
static const regdef_t *reg_lookup(const char *name) {
+ fprintf(stderr, "DBG: reg_lookup entered name=%s count=%d\n", name, (int)(sizeof(REGS) / sizeof(REGS[0])));
for (size_t i = 0; i < sizeof(REGS) / sizeof(REGS[0]); i++) {
+ fprintf(stderr, "DBG: i=%d REGS[i].name_is_null=%d\n", (int)i, REGS[i].name == NULL);
if (strcmp(REGS[i].name, name) == 0) return ®S[i];
}
return NULL;
@@ -288,11 +296,14 @@ static operand_t parse_operand(pstate_t *ps, line_t *ln) {
return o;
}
if (line_peek(ln).kind == TK_IDENT) {
+ fprintf(stderr, "DBG: parse_operand ident=%s\n", line_peek(ln).text);
const regdef_t *r = reg_lookup(line_peek(ln).text);
+ fprintf(stderr, "DBG: reg_lookup returned nonnull=%d\n", r != NULL);
if (r) {
line_next(ln);
o.kind = OPND_REG;
o.reg = r->reg;
+ fprintf(stderr, "DBG: about to return o, kind=%d reg=%d\n", o.kind, o.reg);
return o;
}
}
@@ -376,19 +387,26 @@ static void parse_data_directive(pstate_t *ps, line_t *ln, const char *mnem) {
static void parse_rest_of_line(pstate_t *ps, line_t *ln);
static void parse_line(pstate_t *ps, line_t *ln) {
+ fprintf(stderr, "DBG: parse_line n=%d line_no=%d\n", ln->n, ps->line_no);
if (line_peek(ln).kind == TK_EOF) return;
/* label: */
if (line_peek(ln).kind == TK_IDENT) {
+ fprintf(stderr, "DBG: is ident, about to copy line_t\n");
line_t save = *ln;
+ fprintf(stderr, "DBG: copied, save.n=%d\n", save.n);
token_t name = line_next(ln);
+ fprintf(stderr, "DBG: name=%s\n", name.text);
if (line_peek(ln).kind == TK_COLON) {
+ fprintf(stderr, "DBG: is colon, defining label\n");
line_next(ln);
define_label(ps, name.text);
+ fprintf(stderr, "DBG: label defined\n");
parse_rest_of_line(ps, ln);
return;
}
/* NAME equ EXPR */
+ fprintf(stderr, "DBG: checking equ\n");
if (line_is_ident(ln, "equ")) {
line_next(ln);
value_t v = eval_expr(ps, ln);
@@ -398,16 +416,20 @@ static void parse_line(pstate_t *ps, line_t *ln) {
s->equ_value = v.val;
return;
}
+ fprintf(stderr, "DBG: not equ, restoring ln from save\n");
*ln = save;
+ fprintf(stderr, "DBG: restored\n");
}
parse_rest_of_line(ps, ln);
}
static void parse_rest_of_line(pstate_t *ps, line_t *ln) {
+ fprintf(stderr, "DBG: parse_rest_of_line entered\n");
if (line_peek(ln).kind == TK_EOF || line_peek(ln).kind == TK_NEWLINE) return;
if (line_peek(ln).kind != TK_IDENT) { perr(ps, "expected directive/instruction"); return; }
token_t head = line_next(ln);
+ fprintf(stderr, "DBG: head=%s\n", head.text);
if (ieq(head.text, "BITS")) { ps->bits = (int)line_next(ln).number; return; }
if (ieq(head.text, "ORG")) {
@@ -473,12 +495,20 @@ static void parse_rest_of_line(pstate_t *ps, line_t *ln) {
if (ieq(head.text, "dq")) { parse_data_directive(ps, ln, ".dq"); return; }
/* instruction */
+ fprintf(stderr, "DBG: reached instruction parsing for %s\n", head.text);
stmt_t st = {0};
+ fprintf(stderr, "DBG: st zeroed\n");
safe_strcpy(st.mnemonic, head.text, sizeof(st.mnemonic));
+ fprintf(stderr, "DBG: mnemonic copied\n");
for (char *p = st.mnemonic; *p; p++) *p = (char)tolower((unsigned char)*p);
+ fprintf(stderr, "DBG: lowercased\n");
if (line_peek(ln).kind != TK_NEWLINE && line_peek(ln).kind != TK_EOF) {
- st.ops[0] = parse_operand(ps, ln);
+ fprintf(stderr, "DBG: about to parse_operand\n");
+ operand_t o0 = parse_operand(ps, ln);
+ fprintf(stderr, "DBG: parsed operand kind=%d\n", o0.kind);
+ st.ops[0] = o0;
+ fprintf(stderr, "DBG: assigned to ops[0]\n");
st.nops = 1;
/* far jmp: `jmp SEG:OFFSET` - the value just parsed as ops[0] is
* actually the segment; re-read it as ops[0].far_seg and parse92 {"es",R_ES,2},{"cs",R_CS,2},{"ss",R_SS,2},{"ds",R_DS,2},{"fs",R_FS,2},{"gs",R_GS,2},
93 {"cr0",R_CR0,4},
94 };
95
96 static const regdef_t *reg_lookup(const char *name) {
97 fprintf(stderr, "DBG: reg_lookup entered name=%s count=%d\n", name, (int)(sizeof(REGS) / sizeof(REGS[0])));
98 for (size_t i = 0; i < sizeof(REGS) / sizeof(REGS[0]); i++) {
99 fprintf(stderr, "DBG: i=%d REGS[i].name_is_null=%d\n", (int)i, REGS[i].name == NULL);
100 if (strcmp(REGS[i].name, name) == 0) return ®S[i];
101 }- static const regdef_t *reg_lookup(const char *name) {
- fprintf(stderr, "DBG: reg_lookup entered name=%s count=%d\n", name, (int)(sizeof(REGS) / sizeof(REGS[0])));
- for (size_t i = 0; i < sizeof(REGS) / sizeof(REGS[0]); i++) {
- fprintf(stderr, "DBG: i=%d REGS[i].name_is_null=%d\n", (int)i, REGS[i].name == NULL);
- if (strcmp(REGS[i].name, name) == 0) return ®S[i];
- }
+ static const regdef_t *reg_lookup(const char *name) {
+ for (size_t i = 0; i < sizeof(REGS) / sizeof(REGS[0]); i++) {
+ if (strcmp(REGS[i].name, name) == 0) return ®S[i];
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/parser.c has been updated successfully. (file state is current in your context — no need to Read it back)
- if (line_peek(ln).kind == TK_IDENT) {
- fprintf(stderr, "DBG: parse_operand ident=%s\n", line_peek(ln).text);
- const regdef_t *r = reg_lookup(line_peek(ln).text);
- fprintf(stderr, "DBG: reg_lookup returned nonnull=%d\n", r != NULL);
- if (r) {
- line_next(ln);
- o.kind = OPND_REG;
- o.reg = r->reg;
- fprintf(stderr, "DBG: about to return o, kind=%d reg=%d\n", o.kind, o.reg);
- return o;
- }
- }
+ if (line_peek(ln).kind == TK_IDENT) {
+ const regdef_t *r = reg_lookup(line_peek(ln).text);
+ if (r) {
+ line_next(ln);
+ o.kind = OPND_REG;
+ o.reg = r->reg;
+ return o;
+ }
+ }The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/parser.c has been updated successfully. (file state is current in your context — no need to Read it back)
- static void parse_line(pstate_t *ps, line_t *ln) {
- fprintf(stderr, "DBG: parse_line n=%d line_no=%d\n", ln->n, ps->line_no);
- if (line_peek(ln).kind == TK_EOF) return;
-
- /* label: */
- if (line_peek(ln).kind == TK_IDENT) {
- fprintf(stderr, "DBG: is ident, about to copy line_t\n");
- line_t save = *ln;
- fprintf(stderr, "DBG: copied, save.n=%d\n", save.n);
- token_t name = line_next(ln);
- fprintf(stderr, "DBG: name=%s\n", name.text);
- if (line_peek(ln).kind == TK_COLON) {
- fprintf(stderr, "DBG: is colon, defining label\n");
- line_next(ln);
- define_label(ps, name.text);
- fprintf(stderr, "DBG: label defined\n");
- parse_rest_of_line(ps, ln);
- return;
- }
- /* NAME equ EXPR */
- fprintf(stderr, "DBG: checking equ\n");
- if (line_is_ident(ln, "equ")) {
- line_next(ln);
- value_t v = eval_expr(ps, ln);
- if (v.has_sym) { perr(ps, "equ value must be constant"); return; }
- symbol_t *s = sym_get_or_create(ps->ctx, name.text);
- s->is_equ = 1;
- s->equ_value = v.val;
- return;
- }
- fprintf(stderr, "DBG: not equ, restoring ln from save\n");
- *ln = save;
- fprintf(stderr, "DBG: restored\n");
- }
- parse_rest_of_line(ps, ln);
- }
-
- static void parse_rest_of_line(pstate_t *ps, line_t *ln) {
- fprintf(stderr, "DBG: parse_rest_of_line entered\n");
- if (line_peek(ln).kind == TK_EOF || line_peek(ln).kind == TK_NEWLINE) return;
- if (line_peek(ln).kind != TK_IDENT) { perr(ps, "expected directive/instruction"); return; }
-
- token_t head = line_next(ln);
- fprintf(stderr, "DBG: head=%s\n", head.text);
+ static void parse_line(pstate_t *ps, line_t *ln) {
+ if (line_peek(ln).kind == TK_EOF) return;
+
+ /* label: */
+ if (line_peek(ln).kind == TK_IDENT) {
+ line_t save = *ln;
+ token_t name = line_next(ln);
+ if (line_peek(ln).kind == TK_COLON) {
+ line_next(ln);
+ define_label(ps, name.text);
+ parse_rest_of_line(ps, ln);
+ return;
+ }
+ /* NAME equ EXPR */
+ if (line_is_ident(ln, "equ")) {
+ line_next(ln);
+ value_t v = eval_expr(ps, ln);
+ if (v.has_sym) { perr(ps, "equ value must be constant"); return; }
+ symbol_t *s = sym_get_or_create(ps->ctx, name.text);
+ s->is_equ = 1;
+ s->equ_value = v.val;
+ return;
+ }
+ *ln = save;
+ }
+ parse_rest_of_line(ps, ln);
+ }
+
+ static void parse_rest_of_line(pstate_t *ps, line_t *ln) {
+ if (line_peek(ln).kind == TK_EOF || line_peek(ln).kind == TK_NEWLINE) return;
+ if (line_peek(ln).kind != TK_IDENT) { perr(ps, "expected directive/instruction"); return; }
+
+ token_t head = line_next(ln);The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/parser.c has been updated successfully. (file state is current in your context — no need to Read it back)
- /* instruction */
- fprintf(stderr, "DBG: reached instruction parsing for %s\n", head.text);
- stmt_t st = {0};
- fprintf(stderr, "DBG: st zeroed\n");
- safe_strcpy(st.mnemonic, head.text, sizeof(st.mnemonic));
- fprintf(stderr, "DBG: mnemonic copied\n");
- for (char *p = st.mnemonic; *p; p++) *p = (char)tolower((unsigned char)*p);
- fprintf(stderr, "DBG: lowercased\n");
-
- if (line_peek(ln).kind != TK_NEWLINE && line_peek(ln).kind != TK_EOF) {
- fprintf(stderr, "DBG: about to parse_operand\n");
- operand_t o0 = parse_operand(ps, ln);
- fprintf(stderr, "DBG: parsed operand kind=%d\n", o0.kind);
- st.ops[0] = o0;
- fprintf(stderr, "DBG: assigned to ops[0]\n");
- st.nops = 1;
+ /* instruction */
+ stmt_t st = {0};
+ safe_strcpy(st.mnemonic, head.text, sizeof(st.mnemonic));
+ for (char *p = st.mnemonic; *p; p++) *p = (char)tolower((unsigned char)*p);
+
+ if (line_peek(ln).kind != TK_NEWLINE && line_peek(ln).kind != TK_EOF) {
+ st.ops[0] = parse_operand(ps, ln);
+ st.nops = 1;The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/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 git diff toolchain/as/parser.c
diff --git a/toolchain/as/parser.c b/toolchain/as/parser.c
index a6127f4..a5e0eea 100644
--- a/toolchain/as/parser.c
+++ b/toolchain/as/parser.c
@@ -33,7 +33,13 @@ typedef struct {
static token_t line_peek(line_t *ln) {
if (ln->pos < ln->n) return ln->toks[ln->pos];
- return (token_t){ .kind = TK_EOF };
+ /* Not a compound literal (CC doesn't support them - see
+ * docs/toolchain.md, Phase T3/T8): memset then set the one field
+ * that matters, same zeroed-rest semantics as `{ .kind = TK_EOF }`. */
+ token_t t;
+ memset(&t, 0, sizeof(t));
+ t.kind = TK_EOF;
+ return t;
}
static token_t line_next(line_t *ln) {
token_t t = line_peek(ln);$ cd /home/martin/workspace/prototypes/boot-to-claude
gcc -Wall -Wextra -O2 -o build/toolchain/cc toolchain/cc/*.c 2>&1 | tail -20
gcc -Wall -Wextra -O2 -o build/toolchain/as toolchain/as/*.c 2>&1 | tail -20
gcc -Wall -Wextra -O2 -o build/toolchain/ld toolchain/ld/*.c 2>&1 | tail -20
echo "stage1 rebuilt"
rm -rf build/stage2 && mkdir -p build/stage2
fail=0
for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c; do
base=$(echo "$f" | tr '/' '_')
./build/toolchain/cc -Itoolchain/include --target=host -o "build/stage2/${base%.c}.S" "$f" || { echo "CODEGEN FAIL: $f"; fail=1; }
done
for s in build/stage2/*.S; do
o="${s%.S}.o"
./build/toolchain/as -f elf32 -o "$o" "$s" || { echo "AS FAIL: $s"; fail=1; }
done
./build/toolchain/ld -o build/stage2/as $(ls build/stage2/toolchain_as_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
./build/toolchain/ld -o build/stage2/cc $(ls build/stage2/toolchain_cc_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
./build/toolchain/ld -o build/stage2/ld $(ls build/stage2/toolchain_ld_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
chmod +x build/stage2/as build/stage2/cc build/stage2/ld
echo "stage2 build fail=$fail"
timeout 30 ./build/stage2/as -f elf32 -o /tmp/t01.o build/toolchain_tests/t01_basic.S
echo "as exit=$?"stage1 rebuilt stage2 build fail=0 as exit=0
$ cd /home/martin/workspace/prototypes/boot-to-claude timeout 90 env CC_BIN=./build/stage2/cc AS_BIN=./build/stage2/as LD_BIN=./build/stage2/ld ./tools/run_toolchain_tests.sh 2>&1 | tail -30
PASS t01_basic (5) ./tools/run_toolchain_tests.sh: line 50: 3726425 Segmentation fault (core dumped) "$CC" -I toolchain/include --target=host -o "$BUILD/$name.S" "$src"
$ cd /home/martin/workspace/prototypes/boot-to-claude for f in tests/toolchain/*.c; do timeout 10 ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" > /tmp/ccout.log 2>&1 rc=$? if [ $rc -ne 0 ]; then echo "FAIL($rc): $f"; fi done
/bin/bash: line 7: 3726444 Segmentation fault (core dumped) timeout 10 ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" > /tmp/ccout.log 2>&1 FAIL(139): tests/toolchain/t02_control.c /bin/bash: line 7: 3726456 Segmentation fault (core dumped) timeout 10 ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" > /tmp/ccout.log 2>&1 FAIL(139): tests/toolchain/t02_types.c /bin/bash: line 7: 3726471 Segmentation fault (core dumped) timeout 10 ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" > /tmp/ccout.log 2>&1 FAIL(139): tests/toolchain/t04_pointers.c /bin/bash: line 7: 3726483 Segmentation fault (core dumped) timeout 10 ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" > /tmp/ccout.log 2>&1 FAIL(139): tests/toolchain/t05_structs.c /bin/bash: line 7: 3726496 Segmentation fault (core dumped) timeout 10 ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" > /tmp/ccout.log 2>&1 FAIL(139): tests/toolchain/t06_switch.c /bin/bash: line 7: 3726509 Segmentation fault (core dumped) timeout 10 ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" > /tmp/ccout.log 2>&1 FAIL(139): tests/toolchain/t07_comma.c /bin/bash: line 7: 3726521 Segmentation fault (core dumped) timeout 10 ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" > /tmp/ccout.log 2>&1 FAIL(139): tests/toolchain/t08_forward_struct.c /bin/bash: line 7: 3726533 Segmentation fault (core dumped) timeout 10 ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" > /tmp/ccout.log 2>&1 FAIL(139): tests/toolchain/t09_local_init.c /bin/bash: line 7: 3726551 Segmentation fault (core dumped) timeout 10 ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" > /tmp/ccout.log 2>&1 FAIL(139): tests/toolchain/t10_short.c /bin/bash: line 7: 3726564 Segmentation fault (core dumped) timeout 10 ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" > /tmp/ccout.log 2>&1 FAIL(139): tests/toolchain/t11_array_size.c /bin/bash: line 7: 3726587 Segmentation fault (core dumped) timeout 10 ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" > /tmp/ccout.log 2>&1 FAIL(139): tests/toolchain/t12_local_array_size.c /bin/bash: line 7: 3726617 Segmentation fault (core dumped) timeout 10 ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" > /tmp/ccout.log 2>&1 FAIL(139): tests/toolchain/t13_struct_copy.c /bin/bash: line 7: 3726630 Segmentation fault (core dumped) timeout 10 ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" > /tmp/ccout.log 2>&1 FAIL(139): tests/toolchain/t14_struct_return.c /bin/bash: line 7: 3726643 Segmentation fault (core dumped) timeout 10 ./build/stage2/cc -I toolchain/include --target=host -o /tmp/out.S "$f" > /tmp/ccout.log 2>&1 FAIL(139): tests/toolchain/t15_escapes.c
$ cd /home/martin/workspace/prototypes/boot-to-claude cat tests/toolchain/t02_control.c
int main(void) {
int sum = 0;
int i = 0;
while (i < 5) {
sum = sum + i;
i = i + 1;
}
/* sum = 0+1+2+3+4 = 10 */
for (int j = 0; j < 3; j++) {
sum = sum + 1;
}
/* sum = 13 */
int k = 0;
do {
k++;
} while (k < 4);
sum = sum + k; /* sum = 17 */
if (sum > 15) {
sum = sum + 100;
} else {
sum = 0;
}
/* sum = 117 */
int total = 0;
for (int n = 0; n < 10; n++) {
if (n == 5) continue;
if (n == 8) break;
total = total + 1;
}
/* n=0..4 (5), skip 5, n=6,7 (2), break at 8: total = 7 */
return sum - 117 + total; /* expect 7 */
}$ cd /home/martin/workspace/prototypes/boot-to-claude timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only tests/toolchain/t02_control.c echo "exit=$?"
/bin/bash: line 4: 3726670 Segmentation fault (core dumped) timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only tests/toolchain/t02_control.c exit=139
$ cd /home/martin/workspace/prototypes/boot-to-claude
echo 'int main(void) { int i = 0; while (i < 5) { i = i + 1; } return i; }' > /tmp/tw.c
timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/tw.c
echo "while exit=$?"
echo 'int main(void) { for (int j = 0; j < 3; j++) { } return 0; }' > /tmp/tf.c
timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/tf.c
echo "for exit=$?"
echo 'int main(void) { int k = 0; do { k++; } while (k < 4); return k; }' > /tmp/td.c
timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/td.c
echo "do-while exit=$?"/bin/bash: line 14: 3726690 Segmentation fault (core dumped) timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/tw.c while exit=139 /bin/bash: line 18: 3726717 Segmentation fault (core dumped) timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/tf.c for exit=139 /bin/bash: line 22: 3726758 Segmentation fault (core dumped) timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/td.c do-while exit=139
$ cd /home/martin/workspace/prototypes/boot-to-claude
echo 'int main(void) { if (1) { return 1; } return 0; }' > /tmp/ti.c
timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/ti.c
echo "if exit=$?"
echo 'int main(void) { { int x = 1; } return 0; }' > /tmp/tb.c
timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/tb.c
echo "block exit=$?"
echo 'int main(void) { while (0) { } return 0; }' > /tmp/tw2.c
timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/tw2.c
echo "empty while exit=$?"cc: parsed /tmp/ti.c OK if exit=0 /bin/bash: line 18: 3726784 Segmentation fault (core dumped) timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/tb.c block exit=139 cc: parsed /tmp/tw2.c OK empty while exit=0
$ cd /home/martin/workspace/prototypes/boot-to-claude
echo 'int main(void) { int x = 1; return 0; }' > /tmp/tx.c
timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/tx.c
echo "toplevel decl exit=$?"
echo 'int main(void) { { int x; } return 0; }' > /tmp/tx2.c
timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/tx2.c
echo "nested decl no init exit=$?"
echo 'int main(void) { { return 0; } }' > /tmp/tx3.c
timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/tx3.c
echo "nested block no decl exit=$?"/bin/bash: line 14: 3726805 Segmentation fault (core dumped) timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/tx.c toplevel decl exit=139 /bin/bash: line 18: 3726817 Segmentation fault (core dumped) timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/tx2.c nested decl no init exit=139 cc: parsed /tmp/tx3.c OK nested block no decl exit=0
$ cd /home/martin/workspace/prototypes/boot-to-claude
echo 'int main(void) { int x; return 0; }' > /tmp/ty.c
timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/ty.c
echo "bare decl exit=$?"/bin/bash: line 6: 3726846 Segmentation fault (core dumped) timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/ty.c bare decl exit=139
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'parse_local_decl_stmt\|parse_one_local_decl' toolchain/cc/parser.c | head
904:static node_t *parse_one_local_decl(parser_t *p, type_t *base, int is_static) {
928:static node_t *parse_local_decl_stmt(parser_t *p) {
931: node_t *head = parse_one_local_decl(p, base, is_static);
934: node_t *n = parse_one_local_decl(p, base, is_static);
976: n->a = is_type_start(p) ? parse_local_decl_stmt(p) : NULL;
1027: if (is_type_start(p)) return parse_local_decl_stmt(p);
1043: while (tail->next) tail = tail->next; /* parse_local_decl_stmt may chain multiple decls */904 static node_t *parse_one_local_decl(parser_t *p, type_t *base, int is_static) {
905 char name[MAX_NAME];
906 type_t *t = parse_declarator(p, base, name, NULL, NULL);
907 sym_t *s = add_local(p, name, t, is_static);
908 node_t *n = node_new(ND_VAR_DECL);
909 safe_strcpy(n->name, name, MAX_NAME);
910 n->type = t;
911 n->is_static_local = is_static;
912 safe_strcpy(n->asm_label, s->asm_label, MAX_NAME);
913 n->frame_offset = s->frame_offset;
914 if (match(p, TK_ASSIGN)) n->init = parse_initializer(p);
915 /* Same `T name[] = { ... }` size-inference fix as parse_global_var -
916 * needed here too for e.g. a function-local `static const T ops[] =
917 * {...};` (used throughout the kernel's scripting-language parser
918 * files) - see that function's comment for why array_len otherwise
919 * silently means sizeof(name) == 0. */
920 if (t->kind == TY_ARRAY && t->array_len < 0 && n->init && n->init->kind == ND_BLOCK) {
921 int count = 0;
922 for (node_t *v = n->init->list; v; v = v->next) count++;
923 t->array_len = count;
924 }
925 return n;
926 }
927
928 static node_t *parse_local_decl_stmt(parser_t *p) {
929 int is_static = 0, is_extern = 0;
930 type_t *base = parse_type_spec(p, &is_static, &is_extern);
931 node_t *head = parse_one_local_decl(p, base, is_static);
932 node_t *tail = head;
933 while (match(p, TK_COMMA)) {
934 node_t *n = parse_one_local_decl(p, base, is_static);
935 tail->next = n;
936 tail = n;
937 }
938 expect(p, TK_SEMI);
939 return head;
940 }
941
942 static node_t *parse_stmt(parser_t *p) {
943 if (check(p, TK_LBRACE)) return parse_block(p);
944
945 if (match_kw(p, "if")) {
946 expect(p, TK_LPAREN);
947 node_t *n = node_new(ND_IF);
948 n->a = parse_expr(p);$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'static sym_t \*add_local' -A 25 toolchain/cc/parser.c | head -30
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);
123-static node_t *parse_assign(parser_t *p);
124-static node_t *parse_cast(parser_t *p);
125-static node_t *parse_unary(parser_t *p);
126-static type_t *parse_type_name(parser_t *p); /* for casts/sizeof(type) */
127-static node_t *parse_stmt(parser_t *p);
128-static node_t *parse_block(parser_t *p);- 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);
+ static node_t *parse_one_local_decl(parser_t *p, type_t *base, int is_static) {
+ fprintf(stderr, "DBG: parse_one_local_decl entered base=%p is_static=%d\n", (void*)base, is_static);
+ char name[MAX_NAME];
+ type_t *t = parse_declarator(p, base, name, NULL, NULL);
+ fprintf(stderr, "DBG: declarator parsed name=%s t=%p\n", name, (void*)t);
+ sym_t *s = add_local(p, name, t, is_static);
+ fprintf(stderr, "DBG: add_local done s=%p\n", (void*)s);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)
- static node_t *parse_one_local_decl(parser_t *p, type_t *base, int is_static) {
- fprintf(stderr, "DBG: parse_one_local_decl entered base=%p is_static=%d\n", (void*)base, is_static);
- char name[MAX_NAME];
- type_t *t = parse_declarator(p, base, name, NULL, NULL);
- fprintf(stderr, "DBG: declarator parsed name=%s t=%p\n", name, (void*)t);
- sym_t *s = add_local(p, name, t, is_static);
- fprintf(stderr, "DBG: add_local done s=%p\n", (void*)s);
+ static node_t *parse_one_local_decl(parser_t *p, type_t *base, int is_static) {
+ fprintf(stderr, "DBG: parse_one_local_decl entered is_static=%d\n", is_static);
+ char name[MAX_NAME];
+ type_t *t = parse_declarator(p, base, name, NULL, NULL);
+ fprintf(stderr, "DBG: declarator parsed name=%s\n", name);
+ sym_t *s = add_local(p, name, t, is_static);
+ fprintf(stderr, "DBG: add_local done\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)
- static sym_t *add_local(parser_t *p, const char *name, type_t *type, int is_static) {
- sym_t *s = calloc(1, sizeof(sym_t));
- safe_strcpy(s->name, name, MAX_NAME);
- s->type = type;
- if (is_static) {
- s->is_static_local = 1;
- snprintf(s->asm_label, MAX_NAME, "__static_%d", p->next_static_id++);
- } else {
- p->frame_size += type_size(type) < 4 ? 4 : type_size(type); /* min 4-byte slots - simplicity over density */
- p->frame_size = (p->frame_size + 3) & ~3;
- s->frame_offset = -p->frame_size;
- }
- s->next = p->locals;
- p->locals = s;
- return s;
- }
+ static sym_t *add_local(parser_t *p, const char *name, type_t *type, int is_static) {
+ fprintf(stderr, "DBG: add_local entered name=%s\n", name);
+ sym_t *s = calloc(1, sizeof(sym_t));
+ fprintf(stderr, "DBG: calloc done\n");
+ safe_strcpy(s->name, name, MAX_NAME);
+ fprintf(stderr, "DBG: name copied\n");
+ s->type = type;
+ if (is_static) {
+ s->is_static_local = 1;
+ snprintf(s->asm_label, MAX_NAME, "__static_%d", p->next_static_id++);
+ } else {
+ fprintf(stderr, "DBG: about to type_size\n");
+ int sz = type_size(type);
+ fprintf(stderr, "DBG: type_size=%d\n", sz);
+ p->frame_size += sz < 4 ? 4 : sz; /* min 4-byte slots - simplicity over density */
+ p->frame_size = (p->frame_size + 3) & ~3;
+ s->frame_offset = -p->frame_size;
+ }
+ s->next = p->locals;
+ p->locals = s;
+ fprintf(stderr, "DBG: add_local returning\n");
+ return s;
+ }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 -Wall -Wextra -O2 -o build/toolchain/cc toolchain/cc/*.c 2>&1 | tail -20 ./build/toolchain/cc -Itoolchain/include --target=host -o build/stage2/toolchain_cc_parser.S toolchain/cc/parser.c ./build/toolchain/as -f elf32 -o build/stage2/toolchain_cc_parser.o build/stage2/toolchain_cc_parser.S ./build/toolchain/ld -o build/stage2/cc $(ls build/stage2/toolchain_cc_*.o) $(ls build/stage2/toolchain_hostlib_*.o) chmod +x build/stage2/cc timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/ty.c echo "exit=$?"
<persisted-output> Output too large (47KB). Full output saved to: /home/martin/.claude/projects/-home-martin-workspace-prototypes-boot-to-claude/03468d1c-5b29-4e24-8f0d-2a7435d227ab/tool-results/bli5m6yti.txt Preview (first 2KB): DBG: parse_one_local_decl entered is_static=0 DBG: declarator parsed name=i DBG: add_local entered name=i DBG: calloc done DBG: name copied DBG: about to type_size DBG: type_size=4 DBG: add_local returning DBG: add_local done DBG: parse_one_local_decl entered is_static=0 DBG: declarator parsed name=t DBG: add_local entered name=t DBG: calloc done DBG: name copied DBG: about to type_size DBG: type_size=276 DBG: add_local returning DBG: add_local done DBG: parse_one_local_decl entered is_static=0 DBG: declarator parsed name=s DBG: add_local entered name=s DBG: calloc done DBG: name copied DBG: about to type_size DBG: type_size=4 DBG: add_local returning DBG: add_local done DBG: parse_one_local_decl entered is_static=0 DBG: declarator parsed name=s DBG: add_local entered name=s DBG: calloc done DBG: name copied DBG: about to type_size DBG: type_size=4 DBG: add_local returning DBG: add_local done DBG: parse_one_local_decl entered is_static=0 DBG: declarator parsed name=s DBG: add_local entered name=s DBG: calloc done DBG: name copied DBG: about to type_size DBG: type_size=4 DBG: add_local returning DBG: add_local done DBG: parse_one_local_decl entered is_static=0 DBG: declarator parsed name=t DBG: add_local entered name=t DBG: calloc done DBG: name copied DBG: about to type_size DBG: type_size=4 DBG: add_local returning DBG: add_local done DBG: parse_one_local_decl entered is_static=0 DBG: declarator parsed name=s DBG: add_local entered name=s DBG: calloc done DBG: name copied DBG: about to type_size DBG: type_size=4 DBG: add_local returning DBG: add_local done DBG: parse_one_local_decl entered is_static=0 DBG: declarator parsed name=s DBG: add_local entered name=s DBG: calloc done DBG: name copied DBG: about to type_size DBG: type_size=4 DBG: add_local returning DBG: add_local done DBG: parse_one_local_decl entered is_static=0 DBG: declarator parsed name=s DBG: add_local entered name=s DBG: calloc done DBG: name copied DBG: about to type_size DBG: type_size=4 ... </persisted-output>
$ tail -60 "/home/martin/.claude/projects/-home-martin-workspace-prototypes-boot-to-claude/03468d1c-5b29-4e24-8f0d-2a7435d227ab/tool-results/bli5m6yti.txt"
DBG: about to type_size DBG: type_size=1024 DBG: add_local returning DBG: add_local done DBG: parse_one_local_decl entered is_static=0 DBG: declarator parsed name=n_params DBG: add_local entered name=n_params DBG: calloc done DBG: name copied DBG: about to type_size DBG: type_size=4 DBG: add_local returning DBG: add_local done DBG: parse_one_local_decl entered is_static=0 DBG: declarator parsed name=name DBG: add_local entered name=name DBG: calloc done DBG: name copied DBG: about to type_size DBG: type_size=64 DBG: add_local returning DBG: add_local done DBG: parse_one_local_decl entered is_static=0 DBG: declarator parsed name=t DBG: add_local entered name=t DBG: calloc done DBG: name copied DBG: about to type_size DBG: type_size=4 DBG: add_local returning DBG: add_local done DBG: parse_one_local_decl entered is_static=0 DBG: declarator parsed name=name2 DBG: add_local entered name=name2 DBG: calloc done DBG: name copied DBG: about to type_size DBG: type_size=64 DBG: add_local returning DBG: add_local done DBG: parse_one_local_decl entered is_static=0 DBG: declarator parsed name=t2 DBG: add_local entered name=t2 DBG: calloc done DBG: name copied DBG: about to type_size DBG: type_size=4 DBG: add_local returning DBG: add_local done DBG: parse_one_local_decl entered is_static=0 DBG: declarator parsed name=more DBG: add_local entered name=more DBG: calloc done DBG: name copied DBG: about to type_size DBG: type_size=4 DBG: add_local returning DBG: add_local done /bin/bash: line 14: 3726979 Segmentation fault (core dumped) timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/ty.c exit=139
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'static node_t \*parse_local_decl_stmt' -A 3 toolchain/cc/parser.c
938:static node_t *parse_local_decl_stmt(parser_t *p) {
939- int is_static = 0, is_extern = 0;
940- type_t *base = parse_type_spec(p, &is_static, &is_extern);
941- node_t *head = parse_one_local_decl(p, base, is_static);- static node_t *parse_local_decl_stmt(parser_t *p) {
- int is_static = 0, is_extern = 0;
- type_t *base = parse_type_spec(p, &is_static, &is_extern);
- node_t *head = parse_one_local_decl(p, base, is_static);
+ static node_t *parse_local_decl_stmt(parser_t *p) {
+ fprintf(stderr, "DBG: parse_local_decl_stmt entered\n");
+ int is_static = 0, is_extern = 0;
+ type_t *base = parse_type_spec(p, &is_static, &is_extern);
+ fprintf(stderr, "DBG: type_spec parsed\n");
+ node_t *head = parse_one_local_decl(p, base, is_static);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 -Wall -Wextra -O2 -o build/toolchain/cc toolchain/cc/*.c 2>&1 | tail -20 ./build/toolchain/cc -Itoolchain/include --target=host --parse-only /tmp/ty.c 2>&1
DBG: parse_local_decl_stmt entered DBG: type_spec parsed DBG: parse_one_local_decl entered is_static=0 DBG: declarator parsed name=x DBG: add_local entered name=x DBG: calloc done DBG: name copied DBG: about to type_size DBG: type_size=4 DBG: add_local returning DBG: add_local done cc: parsed /tmp/ty.c OK
$ cd /home/martin/workspace/prototypes/boot-to-claude ./build/toolchain/cc -Itoolchain/include --target=host -o build/stage2/toolchain_cc_parser.S toolchain/cc/parser.c > /tmp/stage1_compiling_parser.log 2>&1 ./build/toolchain/as -f elf32 -o build/stage2/toolchain_cc_parser.o build/stage2/toolchain_cc_parser.S ./build/toolchain/ld -o build/stage2/cc $(ls build/stage2/toolchain_cc_*.o) $(ls build/stage2/toolchain_hostlib_*.o) chmod +x build/stage2/cc echo "=== running stage2 on ty.c ===" timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/ty.c echo "exit=$?"
=== running stage2 on ty.c === /bin/bash: line 14: 3727270 Segmentation fault (core dumped) timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/ty.c exit=139
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'static int is_type_start' -A 15 toolchain/cc/parser.c
179:static int is_type_start(parser_t *p) {
180- if (!check(p, TK_IDENT)) return 0;
181- const char *kws[] = { "void", "char", "short", "int", "long", "signed", "unsigned",
182- "struct", "union", "enum", "const", "volatile", "static", "extern", NULL };
183- for (int i = 0; kws[i]; i++) if (strcmp(cur(p).text, kws[i]) == 0) return 1;
184- return is_typedef_name(p, cur(p).text);
185-}
186-
187-/* Parses base type specifiers + storage class; returns the type and sets
188- * is_static and is_extern. Does not consume any declarator. */
189-static type_t *parse_type_spec(parser_t *p, int *is_static, int *is_extern) {
190- int saw_void = 0, saw_char = 0, saw_short = 0, saw_int = 0, saw_long = 0, saw_unsigned = 0, saw_signed = 0;
191- type_t *named = NULL;
192- if (is_static) *is_static = 0;
193- if (is_extern) *is_extern = 0;
194-- static int is_type_start(parser_t *p) {
- if (!check(p, TK_IDENT)) return 0;
- const char *kws[] = { "void", "char", "short", "int", "long", "signed", "unsigned",
- "struct", "union", "enum", "const", "volatile", "static", "extern", NULL };
- for (int i = 0; kws[i]; i++) if (strcmp(cur(p).text, kws[i]) == 0) return 1;
- return is_typedef_name(p, cur(p).text);
- }
+ static int is_type_start(parser_t *p) {
+ fprintf(stderr, "DBG: is_type_start entered\n");
+ if (!check(p, TK_IDENT)) { fprintf(stderr, "DBG: not ident, return 0\n"); return 0; }
+ const char *kws[] = { "void", "char", "short", "int", "long", "signed", "unsigned",
+ "struct", "union", "enum", "const", "volatile", "static", "extern", NULL };
+ fprintf(stderr, "DBG: kws array built, kws[0]=%s\n", kws[0] ? kws[0] : "(null)");
+ for (int i = 0; kws[i]; i++) {
+ fprintf(stderr, "DBG: i=%d kws[i]=%s\n", i, kws[i]);
+ if (strcmp(cur(p).text, kws[i]) == 0) return 1;
+ }
+ fprintf(stderr, "DBG: falling to is_typedef_name\n");
+ return is_typedef_name(p, cur(p).text);
+ }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 -Wall -Wextra -O2 -o build/toolchain/cc toolchain/cc/*.c 2>&1 | tail -20
./build/toolchain/cc -Itoolchain/include --target=host -o build/stage2/toolchain_cc_parser.S toolchain/cc/parser.c
./build/toolchain/as -f elf32 -o build/stage2/toolchain_cc_parser.o build/stage2/toolchain_cc_parser.S
./build/toolchain/ld -o build/stage2/cc $(ls build/stage2/toolchain_cc_*.o) $(ls build/stage2/toolchain_hostlib_*.o)
chmod +x build/stage2/cc
timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/ty.c 2>&1 | head -20
echo "exit=${PIPESTATUS[0]}"<persisted-output> Output too large (312.6KB). Full output saved to: /home/martin/.claude/projects/-home-martin-workspace-prototypes-boot-to-claude/03468d1c-5b29-4e24-8f0d-2a7435d227ab/tool-results/b0d1c8lcd.txt Preview (first 2KB): DBG: is_type_start entered DBG: kws array built, kws[0]=void DBG: i=0 kws[i]=void DBG: i=1 kws[i]=char DBG: i=2 kws[i]=short DBG: i=3 kws[i]=int DBG: i=4 kws[i]=long DBG: i=5 kws[i]=signed DBG: i=6 kws[i]=unsigned DBG: i=7 kws[i]=struct DBG: i=8 kws[i]=union DBG: i=9 kws[i]=enum DBG: i=10 kws[i]=const DBG: i=11 kws[i]=volatile DBG: i=12 kws[i]=static DBG: i=13 kws[i]=extern DBG: falling to is_typedef_name DBG: parse_local_decl_stmt entered DBG: type_spec parsed DBG: parse_one_local_decl entered is_static=0 DBG: declarator parsed name=i DBG: add_local entered name=i DBG: calloc done DBG: name copied DBG: about to type_size DBG: type_size=4 DBG: add_local returning DBG: add_local done DBG: is_type_start entered DBG: kws array built, kws[0]=void DBG: i=0 kws[i]=void DBG: i=1 kws[i]=char DBG: i=2 kws[i]=short DBG: i=3 kws[i]=int DBG: i=4 kws[i]=long DBG: i=5 kws[i]=signed DBG: i=6 kws[i]=unsigned DBG: i=7 kws[i]=struct DBG: i=8 kws[i]=union DBG: i=9 kws[i]=enum DBG: i=10 kws[i]=const DBG: i=11 kws[i]=volatile DBG: i=12 kws[i]=static DBG: i=13 kws[i]=extern DBG: falling to is_typedef_name DBG: is_type_start entered DBG: kws array built, kws[0]=void DBG: i=0 kws[i]=void DBG: i=1 kws[i]=char DBG: i=2 kws[i]=short DBG: i=3 kws[i]=int DBG: i=4 kws[i]=long DBG: i=5 kws[i]=signed DBG: i=6 kws[i]=unsigned DBG: i=7 kws[i]=struct DBG: i=8 kws[i]=union DBG: i=9 kws[i]=enum DBG: i=10 kws[i]=const DBG: i=11 kws[i]=volatile DBG: i=12 kws[i]=static DBG: i=13 kws[i]=extern DBG: falling to is_typedef_name DBG: is_type_start entered DBG: kws array built, kws[0]=void DBG: i=0 kws[i]=void DBG: i=1 kws[i]=char DBG: i=2 kws[i]=short DBG: i=3 kws[i]=int DBG: i=4 kws[i]=long DBG: i=5 kws[i]=signed DBG: i=6 kws[i]=unsigned DBG: i=7 kws[i]=struct DBG: i=8 kws[i]=union DBG: i=9 kws[i]=enum DBG: i=10 kws[i]=const DBG: i=11 kws[i]=volatile DBG: i=12 kws[i]=static DBG: i=13 kws[i]=extern DBG: falling to is_typedef_name DBG: is_type_start entered DBG: not ident, return 0 DBG: is_type_start entered ... </persisted-output>
$ tail -80 "/home/martin/.claude/projects/-home-martin-workspace-prototypes-boot-to-claude/03468d1c-5b29-4e24-8f0d-2a7435d227ab/tool-results/b0d1c8lcd.txt"
DBG: i=7 kws[i]=struct DBG: i=8 kws[i]=union DBG: i=9 kws[i]=enum DBG: i=10 kws[i]=const DBG: i=11 kws[i]=volatile DBG: i=12 kws[i]=static DBG: i=13 kws[i]=extern DBG: falling to is_typedef_name DBG: is_type_start entered DBG: kws array built, kws[0]=void DBG: i=0 kws[i]=void DBG: i=1 kws[i]=char DBG: i=2 kws[i]=short DBG: i=3 kws[i]=int DBG: i=4 kws[i]=long DBG: i=5 kws[i]=signed DBG: i=6 kws[i]=unsigned DBG: i=7 kws[i]=struct DBG: i=8 kws[i]=union DBG: i=9 kws[i]=enum DBG: i=10 kws[i]=const DBG: i=11 kws[i]=volatile DBG: i=12 kws[i]=static DBG: i=13 kws[i]=extern DBG: falling to is_typedef_name DBG: is_type_start entered DBG: kws array built, kws[0]=void DBG: i=0 kws[i]=void DBG: i=1 kws[i]=char DBG: i=2 kws[i]=short DBG: i=3 kws[i]=int DBG: i=4 kws[i]=long DBG: i=5 kws[i]=signed DBG: i=6 kws[i]=unsigned DBG: i=7 kws[i]=struct DBG: i=8 kws[i]=union DBG: i=9 kws[i]=enum DBG: i=10 kws[i]=const DBG: i=11 kws[i]=volatile DBG: i=12 kws[i]=static DBG: i=13 kws[i]=extern DBG: falling to is_typedef_name DBG: is_type_start entered DBG: kws array built, kws[0]=void DBG: i=0 kws[i]=void DBG: i=1 kws[i]=char DBG: i=2 kws[i]=short DBG: i=3 kws[i]=int DBG: i=4 kws[i]=long DBG: i=5 kws[i]=signed DBG: i=6 kws[i]=unsigned DBG: i=7 kws[i]=struct DBG: i=8 kws[i]=union DBG: i=9 kws[i]=enum DBG: i=10 kws[i]=const DBG: i=11 kws[i]=volatile DBG: i=12 kws[i]=static DBG: i=13 kws[i]=extern DBG: falling to is_typedef_name DBG: is_type_start entered DBG: kws array built, kws[0]=void DBG: i=0 kws[i]=void DBG: i=1 kws[i]=char DBG: i=2 kws[i]=short DBG: i=3 kws[i]=int DBG: i=4 kws[i]=long DBG: i=5 kws[i]=signed DBG: i=6 kws[i]=unsigned DBG: i=7 kws[i]=struct DBG: i=8 kws[i]=union DBG: i=9 kws[i]=enum DBG: i=10 kws[i]=const DBG: i=11 kws[i]=volatile DBG: i=12 kws[i]=static DBG: i=13 kws[i]=extern DBG: falling to is_typedef_name DBG: is_type_start entered DBG: kws array built, kws[0]=void DBG: i=0 kws[i]=void exit=139
- static int is_type_start(parser_t *p) {
- fprintf(stderr, "DBG: is_type_start entered\n");
- if (!check(p, TK_IDENT)) { fprintf(stderr, "DBG: not ident, return 0\n"); return 0; }
- const char *kws[] = { "void", "char", "short", "int", "long", "signed", "unsigned",
- "struct", "union", "enum", "const", "volatile", "static", "extern", NULL };
- fprintf(stderr, "DBG: kws array built, kws[0]=%s\n", kws[0] ? kws[0] : "(null)");
- for (int i = 0; kws[i]; i++) {
- fprintf(stderr, "DBG: i=%d kws[i]=%s\n", i, kws[i]);
- if (strcmp(cur(p).text, kws[i]) == 0) return 1;
- }
- fprintf(stderr, "DBG: falling to is_typedef_name\n");
- return is_typedef_name(p, cur(p).text);
- }
+ static int is_type_start(parser_t *p) {
+ fprintf(stderr, "DBG: is_type_start entered pos=%d curtext=%s curkind=%d\n", p->pos, cur(p).text, cur(p).kind);
+ if (!check(p, TK_IDENT)) { fprintf(stderr, "DBG: not ident, return 0\n"); return 0; }
+ const char *kws[] = { "void", "char", "short", "int", "long", "signed", "unsigned",
+ "struct", "union", "enum", "const", "volatile", "static", "extern", NULL };
+ for (int i = 0; kws[i]; i++) {
+ if (strcmp(cur(p).text, kws[i]) == 0) { fprintf(stderr, "DBG: matched kw %s, return 1\n", kws[i]); return 1; }
+ }
+ fprintf(stderr, "DBG: falling to is_typedef_name for '%s'\n", cur(p).text);
+ int r = is_typedef_name(p, cur(p).text);
+ fprintf(stderr, "DBG: is_typedef_name returned %d\n", r);
+ return r;
+ }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 'typedef struct.*parser_t\|struct parser {' -A 20 toolchain/cc/cc.h | head -25(Bash completed with no output)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'struct parser\|} parser_t' toolchain/cc/cc.h grep -n 'p->pos' toolchain/cc/parser.c | head -5
52: perr_at(p, p->toks[p->pos], msg);
55:static token_t cur(parser_t *p) { return p->toks[p->pos]; }
56:static token_t advance(parser_t *p) { token_t t = p->toks[p->pos]; if (p->toks[p->pos].kind != TK_EOF) p->pos++; return t; }
180: fprintf(stderr, "DBG: is_type_start entered pos=%d curtext=%s curkind=%d\n", p->pos, cur(p).text, cur(p).kind);
374: if (is_kw(p, "void") && p->toks[p->pos + 1].kind == TK_RPAREN) { advance(p); return NULL; }$ cd /home/martin/workspace/prototypes/boot-to-claude gcc -Wall -Wextra -O2 -o build/toolchain/cc toolchain/cc/*.c 2>&1 | tail -20 ./build/toolchain/cc -Itoolchain/include --target=host -o build/stage2/toolchain_cc_parser.S toolchain/cc/parser.c > /dev/null 2>&1 ./build/toolchain/as -f elf32 -o build/stage2/toolchain_cc_parser.o build/stage2/toolchain_cc_parser.S ./build/toolchain/ld -o build/stage2/cc $(ls build/stage2/toolchain_cc_*.o) $(ls build/stage2/toolchain_hostlib_*.o) chmod +x build/stage2/cc timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/ty.c > /tmp/ty_trace.log 2>&1 echo "exit=$?" wc -l /tmp/ty_trace.log tail -40 /tmp/ty_trace.log
exit=0 14 /tmp/ty_trace.log DBG: is_type_start entered pos=6 curtext=int curkind=1 DBG: matched kw int, return 1 DBG: parse_local_decl_stmt entered DBG: type_spec parsed DBG: parse_one_local_decl entered is_static=0 DBG: declarator parsed name=x DBG: add_local entered name=x DBG: calloc done DBG: name copied DBG: about to type_size DBG: type_size=4 DBG: add_local returning DBG: add_local done cc: parsed /tmp/ty.c OK
$ cd /home/martin/workspace/prototypes/boot-to-claude git diff toolchain/cc/parser.c | head -100
diff --git a/toolchain/cc/parser.c b/toolchain/cc/parser.c
index 18c5bb9..ebde1a7 100644
--- a/toolchain/cc/parser.c
+++ b/toolchain/cc/parser.c
@@ -101,19 +101,26 @@ static sym_t *add_global(parser_t *p, const char *name, type_t *type) {
}
static sym_t *add_local(parser_t *p, const char *name, type_t *type, int is_static) {
+ fprintf(stderr, "DBG: add_local entered name=%s\n", name);
sym_t *s = calloc(1, sizeof(sym_t));
+ fprintf(stderr, "DBG: calloc done\n");
safe_strcpy(s->name, name, MAX_NAME);
+ fprintf(stderr, "DBG: name copied\n");
s->type = type;
if (is_static) {
s->is_static_local = 1;
snprintf(s->asm_label, MAX_NAME, "__static_%d", p->next_static_id++);
} else {
- p->frame_size += type_size(type) < 4 ? 4 : type_size(type); /* min 4-byte slots - simplicity over density */
+ fprintf(stderr, "DBG: about to type_size\n");
+ int sz = type_size(type);
+ fprintf(stderr, "DBG: type_size=%d\n", sz);
+ p->frame_size += sz < 4 ? 4 : sz; /* min 4-byte slots - simplicity over density */
p->frame_size = (p->frame_size + 3) & ~3;
s->frame_offset = -p->frame_size;
}
s->next = p->locals;
p->locals = s;
+ fprintf(stderr, "DBG: add_local returning\n");
return s;
}
@@ -126,6 +133,8 @@ static node_t *parse_unary(parser_t *p);
static type_t *parse_type_name(parser_t *p); /* for casts/sizeof(type) */
static node_t *parse_stmt(parser_t *p);
static node_t *parse_block(parser_t *p);
+static node_t *make_binop(tok_kind_t op, node_t *a, node_t *b); /* for va_start/va_arg desugaring */
+static type_t *parse_array_dims(parser_t *p, type_t *base); /* for multi-dim array param decay */
/* Array bounds and enum values in this codebase are often macro
* constants that expand to a small arithmetic expression (e.g.
@@ -168,11 +177,17 @@ static int64_t parse_const_expr(parser_t *p) {
static member_t *parse_struct_members(parser_t *p);
static int is_type_start(parser_t *p) {
- if (!check(p, TK_IDENT)) return 0;
+ fprintf(stderr, "DBG: is_type_start entered pos=%d curtext=%s curkind=%d\n", p->pos, cur(p).text, cur(p).kind);
+ if (!check(p, TK_IDENT)) { fprintf(stderr, "DBG: not ident, return 0\n"); return 0; }
const char *kws[] = { "void", "char", "short", "int", "long", "signed", "unsigned",
"struct", "union", "enum", "const", "volatile", "static", "extern", NULL };
- for (int i = 0; kws[i]; i++) if (strcmp(cur(p).text, kws[i]) == 0) return 1;
- return is_typedef_name(p, cur(p).text);
+ for (int i = 0; kws[i]; i++) {
+ if (strcmp(cur(p).text, kws[i]) == 0) { fprintf(stderr, "DBG: matched kw %s, return 1\n", kws[i]); return 1; }
+ }
+ fprintf(stderr, "DBG: falling to is_typedef_name for '%s'\n", cur(p).text);
+ int r = is_typedef_name(p, cur(p).text);
+ fprintf(stderr, "DBG: is_typedef_name returned %d\n", r);
+ return r;
}
/* Parses base type specifiers + storage class; returns the type and sets
@@ -358,6 +373,14 @@ static type_t *parse_param_list(parser_t *p, char names[][MAX_NAME], int *n_name
if (check(p, TK_RPAREN)) return NULL;
if (is_kw(p, "void") && p->toks[p->pos + 1].kind == TK_RPAREN) { advance(p); return NULL; }
for (;;) {
+ /* `...` (variadic tail, e.g. `fprintf`'s or codegen.c's own
+ * `out(const char *fmt, ...)`): stops named-parameter parsing
+ * here with no further storage of its own - call sites don't
+ * check argument counts against a prototype (each call arg is
+ * parsed/pushed independently, see codegen_call_or_builtin), so
+ * nothing downstream needs to know this function is variadic
+ * beyond "stop expecting more named params". */
+ if (check(p, TK_ELLIPSIS)) { advance(p); break; }
type_t *base = parse_type_spec(p, NULL, NULL);
type_t *pt = base;
char nm[MAX_NAME] = {0};
@@ -381,10 +404,23 @@ static type_t *parse_param_list(parser_t *p, char names[][MAX_NAME], int *n_name
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);
+ if (check(p, TK_LBRACKET)) {
+ /* Only the *outermost* dimension decays to a pointer -
+ * `T x[][M]` as a parameter is `T (*x)[M]`, not `T**`.
+ * (Real bug: parse_param_list's own declaration, right
+ * here in this file, is `char names[][MAX_NAME]` - Phase
+ * T8's self-hosting build is what first parsed this
+ * function's signature through CC instead of gcc, and
+ * the old always-ptr_to-every-bracket version silently
+ * mis-typed its own `names` parameter as char**, making
+ * every `names[n]` inside this same function index by
+ * 4-byte pointer strides instead of MAX_NAME-byte array
+ * strides - see docs/toolchain.md.) */
+ advance(p); /* consume the outer '[' */
+ if (!check(p, TK_RBRACKET)) advance(p); /* its size, if any - irrelevant for a param */
expect(p, TK_RBRACKET);
- pt = ptr_to(pt); /* array param decays to pointer */
+ pt = parse_array_dims(p, pt); /* any further [N] groups stay real array dims */- static sym_t *add_local(parser_t *p, const char *name, type_t *type, int is_static) {
- fprintf(stderr, "DBG: add_local entered name=%s\n", name);
- sym_t *s = calloc(1, sizeof(sym_t));
- fprintf(stderr, "DBG: calloc done\n");
- safe_strcpy(s->name, name, MAX_NAME);
- fprintf(stderr, "DBG: name copied\n");
- s->type = type;
- if (is_static) {
- s->is_static_local = 1;
- snprintf(s->asm_label, MAX_NAME, "__static_%d", p->next_static_id++);
- } else {
- fprintf(stderr, "DBG: about to type_size\n");
- int sz = type_size(type);
- fprintf(stderr, "DBG: type_size=%d\n", sz);
- p->frame_size += sz < 4 ? 4 : sz; /* min 4-byte slots - simplicity over density */
- p->frame_size = (p->frame_size + 3) & ~3;
- s->frame_offset = -p->frame_size;
- }
- s->next = p->locals;
- p->locals = s;
- fprintf(stderr, "DBG: add_local returning\n");
- return s;
- }
+ static sym_t *add_local(parser_t *p, const char *name, type_t *type, int is_static) {
+ sym_t *s = calloc(1, sizeof(sym_t));
+ safe_strcpy(s->name, name, MAX_NAME);
+ s->type = type;
+ if (is_static) {
+ s->is_static_local = 1;
+ snprintf(s->asm_label, MAX_NAME, "__static_%d", p->next_static_id++);
+ } else {
+ int sz = type_size(type);
+ p->frame_size += sz < 4 ? 4 : sz; /* min 4-byte slots - simplicity over density */
+ p->frame_size = (p->frame_size + 3) & ~3;
+ s->frame_offset = -p->frame_size;
+ }
+ s->next = p->locals;
+ p->locals = s;
+ return s;
+ }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)
- static int is_type_start(parser_t *p) {
- fprintf(stderr, "DBG: is_type_start entered pos=%d curtext=%s curkind=%d\n", p->pos, cur(p).text, cur(p).kind);
- if (!check(p, TK_IDENT)) { fprintf(stderr, "DBG: not ident, return 0\n"); return 0; }
- const char *kws[] = { "void", "char", "short", "int", "long", "signed", "unsigned",
- "struct", "union", "enum", "const", "volatile", "static", "extern", NULL };
- for (int i = 0; kws[i]; i++) {
- if (strcmp(cur(p).text, kws[i]) == 0) { fprintf(stderr, "DBG: matched kw %s, return 1\n", kws[i]); return 1; }
- }
- fprintf(stderr, "DBG: falling to is_typedef_name for '%s'\n", cur(p).text);
- int r = is_typedef_name(p, cur(p).text);
- fprintf(stderr, "DBG: is_typedef_name returned %d\n", r);
- return r;
- }
+ static int is_type_start(parser_t *p) {
+ if (!check(p, TK_IDENT)) return 0;
+ const char *kws[] = { "void", "char", "short", "int", "long", "signed", "unsigned",
+ "struct", "union", "enum", "const", "volatile", "static", "extern", NULL };
+ for (int i = 0; kws[i]; i++) if (strcmp(cur(p).text, kws[i]) == 0) return 1;
+ return is_typedef_name(p, cur(p).text);
+ }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 'DBG:' toolchain/cc/parser.c toolchain/cc/codegen.c toolchain/as/*.c toolchain/ld/*.c toolchain/hostlib/*.c
toolchain/cc/parser.c:906: fprintf(stderr, "DBG: parse_one_local_decl entered is_static=%d\n", is_static); toolchain/cc/parser.c:909: fprintf(stderr, "DBG: declarator parsed name=%s\n", name); toolchain/cc/parser.c:911: fprintf(stderr, "DBG: add_local done\n"); toolchain/cc/parser.c:933: fprintf(stderr, "DBG: parse_local_decl_stmt entered\n"); toolchain/cc/parser.c:936: fprintf(stderr, "DBG: type_spec parsed\n");
900 return parse_assign(p);
901 }
902
903 /* ---------------- statements ---------------- */
904
905 static node_t *parse_one_local_decl(parser_t *p, type_t *base, int is_static) {
906 fprintf(stderr, "DBG: parse_one_local_decl entered is_static=%d\n", is_static);
907 char name[MAX_NAME];
908 type_t *t = parse_declarator(p, base, name, NULL, NULL);
909 fprintf(stderr, "DBG: declarator parsed name=%s\n", name);
910 sym_t *s = add_local(p, name, t, is_static);
911 fprintf(stderr, "DBG: add_local done\n");
912 node_t *n = node_new(ND_VAR_DECL);
913 safe_strcpy(n->name, name, MAX_NAME);
914 n->type = t;
915 n->is_static_local = is_static;
916 safe_strcpy(n->asm_label, s->asm_label, MAX_NAME);
917 n->frame_offset = s->frame_offset;
918 if (match(p, TK_ASSIGN)) n->init = parse_initializer(p);
919 /* Same `T name[] = { ... }` size-inference fix as parse_global_var -
920 * needed here too for e.g. a function-local `static const T ops[] =
921 * {...};` (used throughout the kernel's scripting-language parser
922 * files) - see that function's comment for why array_len otherwise
923 * silently means sizeof(name) == 0. */
924 if (t->kind == TY_ARRAY && t->array_len < 0 && n->init && n->init->kind == ND_BLOCK) {
925 int count = 0;
926 for (node_t *v = n->init->list; v; v = v->next) count++;
927 t->array_len = count;
928 }
929 return n;
930 }
931
932 static node_t *parse_local_decl_stmt(parser_t *p) {
933 fprintf(stderr, "DBG: parse_local_decl_stmt entered\n");
934 int is_static = 0, is_extern = 0;
935 type_t *base = parse_type_spec(p, &is_static, &is_extern);
936 fprintf(stderr, "DBG: type_spec parsed\n");
937 node_t *head = parse_one_local_decl(p, base, is_static);
938 node_t *tail = head;
939 while (match(p, TK_COMMA)) {- static node_t *parse_one_local_decl(parser_t *p, type_t *base, int is_static) {
- fprintf(stderr, "DBG: parse_one_local_decl entered is_static=%d\n", is_static);
- char name[MAX_NAME];
- type_t *t = parse_declarator(p, base, name, NULL, NULL);
- fprintf(stderr, "DBG: declarator parsed name=%s\n", name);
- sym_t *s = add_local(p, name, t, is_static);
- fprintf(stderr, "DBG: add_local done\n");
- node_t *n = node_new(ND_VAR_DECL);
+ 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);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)
- static node_t *parse_local_decl_stmt(parser_t *p) {
- fprintf(stderr, "DBG: parse_local_decl_stmt entered\n");
- int is_static = 0, is_extern = 0;
- type_t *base = parse_type_spec(p, &is_static, &is_extern);
- fprintf(stderr, "DBG: type_spec parsed\n");
- node_t *head = parse_one_local_decl(p, base, is_static);
+ static node_t *parse_local_decl_stmt(parser_t *p) {
+ int is_static = 0, is_extern = 0;
+ type_t *base = parse_type_spec(p, &is_static, &is_extern);
+ node_t *head = parse_one_local_decl(p, base, is_static);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 'DBG:' toolchain/cc/*.c toolchain/as/*.c toolchain/ld/*.c toolchain/hostlib/*.c
gcc -Wall -Wextra -O2 -o build/toolchain/cc toolchain/cc/*.c 2>&1 | tail -20
gcc -Wall -Wextra -O2 -o build/toolchain/as toolchain/as/*.c 2>&1 | tail -20
gcc -Wall -Wextra -O2 -o build/toolchain/ld toolchain/ld/*.c 2>&1 | tail -20
echo stage1_ok
rm -rf build/stage2 && mkdir -p build/stage2
fail=0
for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c; do
base=$(echo "$f" | tr '/' '_')
./build/toolchain/cc -Itoolchain/include --target=host -o "build/stage2/${base%.c}.S" "$f" || { echo "CODEGEN FAIL: $f"; fail=1; }
done
for s in build/stage2/*.S; do
o="${s%.S}.o"
./build/toolchain/as -f elf32 -o "$o" "$s" || { echo "AS FAIL: $s"; fail=1; }
done
./build/toolchain/ld -o build/stage2/as $(ls build/stage2/toolchain_as_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
./build/toolchain/ld -o build/stage2/cc $(ls build/stage2/toolchain_cc_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
./build/toolchain/ld -o build/stage2/ld $(ls build/stage2/toolchain_ld_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
chmod +x build/stage2/as build/stage2/cc build/stage2/ld
echo "stage2 build fail=$fail"stage1_ok stage2 build fail=0
$ cd /home/martin/workspace/prototypes/boot-to-claude for i in 1 2 3; do timeout 10 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/ty.c echo "run $i exit=$?" done
/bin/bash: line 6: 3730019 Segmentation fault (core dumped) timeout 10 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/ty.c run 1 exit=139 /bin/bash: line 6: 3730035 Segmentation fault (core dumped) timeout 10 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/ty.c run 2 exit=139 /bin/bash: line 6: 3730049 Segmentation fault (core dumped) timeout 10 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/ty.c run 3 exit=139
$ cd /home/martin/workspace/prototypes/boot-to-claude objdump -t build/stage2/cc 2>&1 | head -20 echo "---" readelf -h build/stage2/cc 2>&1 | head -20
build/stage2/cc: file format elf32-i386 SYMBOL TABLE: no symbols --- ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x80519ad Start of program headers: 52 (bytes into file) Start of section headers: 0 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 1 Size of section headers: 0 (bytes) Number of section headers: 0 Section header string table index: 0
$ cd /home/martin/workspace/prototypes/boot-to-claude ulimit -c unlimited timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/ty.c ls -la core* 2>/dev/null gdb -q -batch -ex "run -Itoolchain/include --target=host --parse-only /tmp/ty.c" -ex "info registers" -ex "x/10i \$pc-20" --args ./build/stage2/cc 2>&1 | tail -30
/bin/bash: line 7: 3730171 Segmentation fault (core dumped) timeout 5 ./build/stage2/cc -Itoolchain/include --target=host --parse-only /tmp/ty.c Program received signal SIGSEGV, Segmentation fault. 0x08051c9e in ?? () eax 0x7e246a69 2116315753 ecx 0xffffaa78 -21896 edx 0x0 0 ebx 0x3 3 esp 0xffffa944 0xffffa944 ebp 0xffffa94c 0xffffa94c esi 0x0 0 edi 0x0 0 eip 0x8051c9e 0x8051c9e eflags 0x10206 [ PF IF RF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x0 0 0x8051c8a: add %al,(%eax) 0x8051c8c: cmp $0x114,%edx 0x8051c92: jge 0x8051cb3 0x8051c98: mov 0x4(%esp),%eax 0x8051c9c: add %edx,%eax => 0x8051c9e: movzbl (%eax),%eax 0x8051ca1: mov (%esp),%ecx 0x8051ca4: add %edx,%ecx 0x8051ca6: mov %al,(%ecx) 0x8051ca8: add $0x1,%edx
$ cd /home/martin/workspace/prototypes/boot-to-claude
grep -n 'struct.*token\|} token_t' -B2 -A 10 toolchain/cc/cc.h | head -20
cat > /tmp/sizecheck2.c <<'EOF'
#include "toolchain/cc/cc.h"
#include <stdio.h>
int main(void) { printf("token_t=%zu node_t=%zu sym_t=%zu type_t=%zu\n", sizeof(token_t), sizeof(node_t), sizeof(sym_t), sizeof(type_t)); return 0; }
EOF
gcc -I. -o /tmp/sizecheck2 /tmp/sizecheck2.c 2>&1 | tail -20
/tmp/sizecheck2Exit code 127
43- const char *file;
44- int bol; /* beginning of line (possibly after whitespace only) - for # detection */
45:} token_t;
46-
47-typedef struct {
48- const char *src;
49- size_t pos, len;
50- const char *filename;
51- int line;
52- int bol;
53- int has_pushback;
54- token_t pushback;
55-} lexer_t;
/tmp/sizecheck2.c: In function ‘main’:
/tmp/sizecheck2.c:3:114: error: ‘sym_t’ undeclared (first use in this function)
3 | int main(void) { printf("token_t=%zu node_t=%zu sym_t=%zu type_t=%zu\n", sizeof(token_t), sizeof(node_t), sizeof(sym_t), sizeof(type_t)); return 0; }
| ^~~~~
/tmp/sizecheck2.c:3:114: note: each undeclared identifier is reported only once for each function it appears in
/bin/bash: line 17: /tmp/sizecheck2: No such file or directory$ cd /home/martin/workspace/prototypes/boot-to-claude
cat > /tmp/sizecheck3.c <<'EOF'
#include "toolchain/cc/cc.h"
#include <stdio.h>
int main(void) { printf("token_t=%zu node_t=%zu type_t=%zu\n", sizeof(token_t), sizeof(node_t), sizeof(type_t)); return 0; }
EOF
gcc -I. -o /tmp/sizecheck3 /tmp/sizecheck3.c && /tmp/sizecheck3
echo "---"
grep -n 'typedef struct.*sym_t\|} sym_t' -B 15 toolchain/cc/parser.c | head -20token_t=296 node_t=760 type_t=128
---
5-#include <string.h>
6-
7-/* ---------------- symbol tables ---------------- */
8-
9-typedef struct sym {
10- char name[MAX_NAME];
11- type_t *type;
12- int frame_offset; /* locals/params, EBP-relative */
13- int is_global;
14- int is_static_local;
15- char asm_label[MAX_NAME];
16- int is_typedef;
17- int is_enum_const;
18- int64_t enum_value;
19- struct sym *next;
20:} sym_t;$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'typedef struct.*parser\|} parser_t' -B 20 toolchain/cc/parser.c | head -30
22-typedef struct tag {
23- char name[MAX_NAME];
24- type_t *type;
25- struct tag *next;
26-} tag_t;
27-
28-typedef struct {
29- token_t *toks;
30- int n, pos;
31-
32- sym_t *globals;
33- sym_t *locals; /* current function's flat symbol list (params+locals) */
34- tag_t *tags;
35-
36- int frame_size; /* current function's local variable area size (grows as locals are declared) */
37- int next_static_id; /* for unique static-local asm labels */
38- int next_str_id; /* for unique string-literal asm labels */
39-
40- type_t *cur_func_ret;
41- int had_error;
42:} parser_t;$ cd /home/martin/workspace/prototypes/boot-to-claude sed -n '36,46p' toolchain/cc/cc.h
} tok_kind_t;
typedef struct {
tok_kind_t kind;
char text[256]; /* TK_IDENT / TK_STR (decoded) */
int64_t num; /* TK_NUM / TK_CHAR */
int line;
const char *file;
int bol; /* beginning of line (possibly after whitespace only) - for # detection */
} token_t;$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'case ND_RETURN' -A 25 toolchain/cc/codegen.c
508: case ND_RETURN:
509- if (n->a && (n->a->type->kind == TY_STRUCT || n->a->type->kind == TY_UNION)) {
510- /* Hidden-pointer struct return (see parse_func /
511- * codegen_call_or_builtin): copy the result into the
512- * caller-supplied slot at [ebp+8], then hand that same
513- * address back in eax - the call site relies on it
514- * being there, same convention as any other
515- * struct-typed rvalue address. */
516- codegen_expr(n->a); /* eax = src address */
517- out(" mov ecx, [ebp+8]\n"); /* ecx = dst address */
518- codegen_struct_copy(n->a->type);
519- out(" mov eax, [ebp+8]\n");
520- } else if (n->a) {
521- codegen_expr(n->a);
522- }
523- out(" mov esp, ebp\n pop ebp\n ret\n");
524- return;
525- case ND_BREAK:
526- out(" jmp %s\n", g_loop->break_label);
527- return;
528- case ND_CONTINUE:
529- out(" jmp %s\n", g_loop->continue_label);
530- return;
531- case ND_SWITCH: {
532- /* n->b is an ND_BLOCK whose ->list is a flat chain of
533- * case/default markers interleaved with ordinary statements -$ cd /home/martin/workspace/prototypes/boot-to-claude
cat > /tmp/tchain.c <<'EOF'
typedef struct {
int kind;
char text[256];
int num;
int line;
} tok_t;
static tok_t make(int k) {
tok_t t;
t.kind = k;
t.text[0] = 'A';
t.num = 42;
t.line = 7;
return t;
}
static tok_t forward(int k) {
return make(k);
}
int main(void) {
tok_t x = forward(5);
return x.kind + x.num + x.line + (int)x.text[0];
/* 5 + 42 + 7 + 65 = 119 */
}
EOF
./build/toolchain/cc -Itoolchain/include --target=host -o /tmp/tchain.S /tmp/tchain.c && \
./build/toolchain/as -f elf32 -o /tmp/tchain.o /tmp/tchain.S && \
./build/toolchain/ld -o /tmp/tchain /tmp/tchain.o
/tmp/tchain
echo "exit=$? (expect 119)"exit=119 (expect 119)
$ cd /home/martin/workspace/prototypes/boot-to-claude
cat > /tmp/tidx.c <<'EOF'
typedef struct {
int kind;
char text[256];
int num;
int line;
} tok_t;
static tok_t arr[4];
int main(void) {
arr[2].kind = 9;
arr[2].text[0] = 'Z';
arr[2].num = 3;
arr[2].line = 1;
tok_t t = arr[2];
return t.kind + t.num + t.line + (int)t.text[0];
/* 9 + 3 + 1 + 90 = 103 */
}
EOF
./build/toolchain/cc -Itoolchain/include --target=host -o /tmp/tidx.S /tmp/tidx.c && \
./build/toolchain/as -f elf32 -o /tmp/tidx.o /tmp/tidx.S && \
./build/toolchain/ld -o /tmp/tidx /tmp/tidx.o
/tmp/tidx
echo "exit=$? (expect 103)"exit=103 (expect 103)
$ cd /home/martin/workspace/prototypes/boot-to-claude
cat > /tmp/treal.c <<'EOF'
typedef enum { TK_EOF, TK_IDENT } tok_kind_t;
typedef struct {
tok_kind_t kind;
char text[256];
int num;
int line;
const char *file;
int bol;
} token_t;
typedef struct {
token_t *toks;
int n, pos;
} parser_t;
static token_t cur(parser_t *p) { return p->toks[p->pos]; }
static token_t advance(parser_t *p) {
token_t t = p->toks[p->pos];
if (p->toks[p->pos].kind != TK_EOF) p->pos++;
return t;
}
static int check(parser_t *p, tok_kind_t k) { return cur(p).kind == k; }
static token_t expect(parser_t *p, tok_kind_t k) {
if (!check(p, k)) { /* error */ }
return advance(p);
}
static token_t g_toks[4];
int main(void) {
g_toks[0].kind = TK_IDENT;
g_toks[0].text[0] = 'x';
g_toks[0].num = 7;
g_toks[0].line = 1;
g_toks[1].kind = TK_EOF;
parser_t p;
p.toks = g_toks;
p.n = 2;
p.pos = 0;
token_t id = expect(&p, TK_IDENT);
return id.kind + id.num + id.line + (int)id.text[0];
/* 1 + 7 + 1 + 120 = 129 */
}
EOF
./build/toolchain/cc -Itoolchain/include --target=host -o /tmp/treal.S /tmp/treal.c && \
./build/toolchain/as -f elf32 -o /tmp/treal.o /tmp/treal.S && \
./build/toolchain/ld -o /tmp/treal /tmp/treal.o
/tmp/treal
echo "exit=$? (expect 129)"exit=129 (expect 129)
$ cd /home/martin/workspace/prototypes/boot-to-claude
cat > /tmp/tits.c <<'EOF'
typedef enum { TK_EOF, TK_IDENT } tok_kind_t;
typedef struct {
tok_kind_t kind;
char text[256];
int num;
int line;
const char *file;
int bol;
} token_t;
typedef struct {
token_t *toks;
int n, pos;
} parser_t;
static int strcmp2(const char *a, const char *b) {
while (*a && (*a == *b)) { a++; b++; }
return (unsigned char)*a - (unsigned char)*b;
}
static token_t cur(parser_t *p) { return p->toks[p->pos]; }
static int check(parser_t *p, tok_kind_t k) { return cur(p).kind == k; }
static int is_type_start(parser_t *p) {
if (!check(p, TK_IDENT)) return 0;
const char *kws[] = { "void", "char", "short", "int", "long", "signed", "unsigned",
"struct", "union", "enum", "const", "volatile", "static", "extern", 0 };
for (int i = 0; kws[i]; i++) if (strcmp2(cur(p).text, kws[i]) == 0) return 1;
return 0;
}
static token_t g_toks[4];
int main(void) {
g_toks[0].kind = TK_IDENT;
g_toks[0].text[0] = 'i'; g_toks[0].text[1] = 'n'; g_toks[0].text[2] = 't'; g_toks[0].text[3] = 0;
g_toks[1].kind = TK_EOF;
parser_t p;
p.toks = g_toks;
p.n = 2;
p.pos = 0;
return is_type_start(&p);
}
EOF
./build/toolchain/cc -Itoolchain/include --target=host -o /tmp/tits.S /tmp/tits.c && \
./build/toolchain/as -f elf32 -o /tmp/tits.o /tmp/tits.S && \
./build/toolchain/ld -o /tmp/tits /tmp/tits.o
timeout 3 /tmp/tits
echo "exit=$? (expect 1)"/bin/bash: line 106: 3732880 Segmentation fault (core dumped) timeout 3 /tmp/tits exit=139 (expect 1)
$ cd /home/martin/workspace/prototypes/boot-to-claude gdb -q -batch -ex run -ex "info registers" -ex "x/10i \$pc-20" --args /tmp/tits 2>&1 | tail -25
ecx 0xffffc99c -13924 edx 0x0 0 ebx 0x0 0 esp 0xffffc97c 0xffffc97c ebp 0xffffc984 0xffffc984 esi 0x0 0 edi 0x0 0 eip 0x8048159 0x8048159 eflags 0x10206 [ PF IF RF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x0 0 0x8048145: add %al,(%eax) 0x8048147: cmp $0x114,%edx 0x804814d: jge 0x804816e 0x8048153: mov 0x4(%esp),%eax 0x8048157: add %edx,%eax => 0x8048159: movzbl (%eax),%eax 0x804815c: mov (%esp),%ecx 0x804815f: add %edx,%ecx 0x8048161: mov %al,(%ecx) 0x8048163: add $0x1,%edx
$ cd /home/martin/workspace/prototypes/boot-to-claude cat -n /tmp/tits.S
1 BITS 32
2 section .text
3 strcmp2:
4 push ebp
5 mov ebp, esp
6 .L0:
7 lea eax, [ebp+8]
8 mov eax, [eax]
9 movzx eax, byte [eax]
10 cmp eax, 0
11 je .L2
12 lea eax, [ebp+12]
13 mov eax, [eax]
14 movzx eax, byte [eax]
15 push eax
16 lea eax, [ebp+8]
17 mov eax, [eax]
18 movzx eax, byte [eax]
19 pop ecx
20 cmp eax, ecx
21 sete al
22 movzx eax, al
23 cmp eax, 0
24 je .L2
25 mov eax, 1
26 jmp .L3
27 .L2:
28 mov eax, 0
29 .L3:
30 cmp eax, 0
31 je .L1
32 lea eax, [ebp+8]
33 mov ecx, eax
34 mov eax, [ecx]
35 mov edx, eax
36 add eax, 1
37 mov [ecx], eax
38 mov eax, edx
39 lea eax, [ebp+12]
40 mov ecx, eax
41 mov eax, [ecx]
42 mov edx, eax
43 add eax, 1
44 mov [ecx], eax
45 mov eax, edx
46 jmp .L0
47 .L1:
48 lea eax, [ebp+12]
49 mov eax, [eax]
50 movzx eax, byte [eax]
51 movzx eax, al
52 push eax
53 lea eax, [ebp+8]
54 mov eax, [eax]
55 movzx eax, byte [eax]
56 movzx eax, al
57 pop ecx
58 sub eax, ecx
59 mov esp, ebp
60 pop ebp
61 ret
62 mov eax, 0
63 mov esp, ebp
64 pop ebp
65 ret
66 cur:
67 push ebp
68 mov ebp, esp
69 lea eax, [ebp+12]
70 mov eax, [eax]
71 add eax, 8
72 mov eax, [eax]
73 push eax
74 lea eax, [ebp+12]
75 mov eax, [eax]
76 mov eax, [eax]
77 pop ecx
78 imul ecx, ecx, 276
79 add eax, ecx
80 mov ecx, [ebp+8]
81 push eax
82 push ecx
83 mov edx, 0
84 .L4:
85 cmp edx, 276
86 jge .L5
87 mov eax, [esp+4]
88 add eax, edx
89 movzx eax, byte [eax]
90 mov ecx, [esp]
91 add ecx, edx
92 mov [ecx], al
93 add edx, 1
94 jmp .L4
95 .L5:
96 add esp, 8
97 mov eax, [ebp+8]
98 mov esp, ebp
99 pop ebp
100 ret
101 mov eax, 0
102 mov esp, ebp
103 pop ebp
104 ret
105 check:
106 push ebp
107 mov ebp, esp
108 sub esp, 276
109 lea eax, [ebp+12]
110 mov eax, [eax]
111 push eax
112 lea eax, [ebp+8]
113 mov eax, [eax]
114 push eax
115 lea eax, [ebp-276]
116 push eax
117 call cur
118 add esp, 8
119 mov eax, [eax]
120 pop ecx
121 cmp eax, ecx
122 sete al
123 movzx eax, al
124 mov esp, ebp
125 pop ebp
126 ret
127 mov eax, 0
128 mov esp, ebp
129 pop ebp
130 ret
131 is_type_start:
132 push ebp
133 mov ebp, esp
134 sub esp, 284
135 mov eax, 1
136 push eax
137 lea eax, [ebp+8]
138 mov eax, [eax]
139 push eax
140 call check
141 add esp, 8
142 cmp eax, 0
143 sete al
144 movzx eax, al
145 cmp eax, 0
146 je .L6
147 mov eax, 0
148 mov esp, ebp
149 pop ebp
150 ret
151 jmp .L7
152 .L6:
153 .L7:
154 push ecx
155 mov ecx, 0
156 .L8:
157 cmp ecx, 60
158 jge .L9
159 lea eax, [ebp-4]
160 add eax, ecx
161 mov byte [eax], 0
162 add ecx, 1
163 jmp .L8
164 .L9:
165 pop ecx
166 lea eax, [ebp-4]
167 push eax
168 lea eax, [__str_0]
169 pop ecx
170 mov [ecx], eax
171 lea eax, [ebp+0]
172 push eax
173 lea eax, [__str_1]
174 pop ecx
175 mov [ecx], eax
176 lea eax, [ebp+4]
177 push eax
178 lea eax, [__str_2]
179 pop ecx
180 mov [ecx], eax
181 lea eax, [ebp+8]
182 push eax
183 lea eax, [__str_3]
184 pop ecx
185 mov [ecx], eax
186 lea eax, [ebp+12]
187 push eax
188 lea eax, [__str_4]
189 pop ecx
190 mov [ecx], eax
191 lea eax, [ebp+16]
192 push eax
193 lea eax, [__str_5]
194 pop ecx
195 mov [ecx], eax
196 lea eax, [ebp+20]
197 push eax
198 lea eax, [__str_6]
199 pop ecx
200 mov [ecx], eax
201 lea eax, [ebp+24]
202 push eax
203 lea eax, [__str_7]
204 pop ecx
205 mov [ecx], eax
206 lea eax, [ebp+28]
207 push eax
208 lea eax, [__str_8]
209 pop ecx
210 mov [ecx], eax
211 lea eax, [ebp+32]
212 push eax
213 lea eax, [__str_9]
214 pop ecx
215 mov [ecx], eax
216 lea eax, [ebp+36]
217 push eax
218 lea eax, [__str_10]
219 pop ecx
220 mov [ecx], eax
221 lea eax, [ebp+40]
222 push eax
223 lea eax, [__str_11]
224 pop ecx
225 mov [ecx], eax
226 lea eax, [ebp+44]
227 push eax
228 lea eax, [__str_12]
229 pop ecx
230 mov [ecx], eax
231 lea eax, [ebp+48]
232 push eax
233 lea eax, [__str_13]
234 pop ecx
235 mov [ecx], eax
236 lea eax, [ebp+52]
237 push eax
238 mov eax, 0
239 pop ecx
240 mov [ecx], eax
241 lea eax, [ebp-8]
242 push eax
243 mov eax, 0
244 pop ecx
245 mov [ecx], eax
246 .L10:
247 lea eax, [ebp-8]
248 mov eax, [eax]
249 push eax
250 lea eax, [ebp-4]
251 pop ecx
252 imul ecx, ecx, 4
253 add eax, ecx
254 mov eax, [eax]
255 cmp eax, 0
256 je .L11
257 mov eax, 0
258 push eax
259 lea eax, [ebp-8]
260 mov eax, [eax]
261 push eax
262 lea eax, [ebp-4]
263 pop ecx
264 imul ecx, ecx, 4
265 add eax, ecx
266 mov eax, [eax]
267 push eax
268 lea eax, [ebp+8]
269 mov eax, [eax]
270 push eax
271 lea eax, [ebp-284]
272 push eax
273 call cur
274 add esp, 8
275 add eax, 4
276 push eax
277 call strcmp2
278 add esp, 8
279 pop ecx
280 cmp eax, ecx
281 sete al
282 movzx eax, al
283 cmp eax, 0
284 je .L13
285 mov eax, 1
286 mov esp, ebp
287 pop ebp
288 ret
289 jmp .L14
290 .L13:
291 .L14:
292 .L12:
293 lea eax, [ebp-8]
294 mov ecx, eax
295 mov eax, [ecx]
296 mov edx, eax
297 add eax, 1
298 mov [ecx], eax
299 mov eax, edx
300 jmp .L10
301 .L11:
302 mov eax, 0
303 mov esp, ebp
304 pop ebp
305 ret
306 mov eax, 0
307 mov esp, ebp
308 pop ebp
309 ret
310 global main
311 main:
312 push ebp
313 mov ebp, esp
314 sub esp, 12
315 mov eax, 0
316 push eax
317 lea eax, [g_toks]
318 pop ecx
319 imul ecx, ecx, 276
320 add eax, ecx
321 push eax
322 mov eax, 1
323 pop ecx
324 mov [ecx], eax
325 mov eax, 0
326 push eax
327 mov eax, 0
328 push eax
329 lea eax, [g_toks]
330 pop ecx
331 imul ecx, ecx, 276
332 add eax, ecx
333 add eax, 4
334 pop ecx
335 add eax, ecx
336 push eax
337 mov eax, 105
338 pop ecx
339 mov [ecx], al
340 mov eax, 1
341 push eax
342 mov eax, 0
343 push eax
344 lea eax, [g_toks]
345 pop ecx
346 imul ecx, ecx, 276
347 add eax, ecx
348 add eax, 4
349 pop ecx
350 add eax, ecx
351 push eax
352 mov eax, 110
353 pop ecx
354 mov [ecx], al
355 mov eax, 2
356 push eax
357 mov eax, 0
358 push eax
359 lea eax, [g_toks]
360 pop ecx
361 imul ecx, ecx, 276
362 add eax, ecx
363 add eax, 4
364 pop ecx
365 add eax, ecx
366 push eax
367 mov eax, 116
368 pop ecx
369 mov [ecx], al
370 mov eax, 3
371 push eax
372 mov eax, 0
373 push eax
374 lea eax, [g_toks]
375 pop ecx
376 imul ecx, ecx, 276
377 add eax, ecx
378 add eax, 4
379 pop ecx
380 add eax, ecx
381 push eax
382 mov eax, 0
383 pop ecx
384 mov [ecx], al
385 mov eax, 1
386 push eax
387 lea eax, [g_toks]
388 pop ecx
389 imul ecx, ecx, 276
390 add eax, ecx
391 push eax
392 mov eax, 0
393 pop ecx
394 mov [ecx], eax
395 lea eax, [ebp-12]
396 push eax
397 lea eax, [g_toks]
398 pop ecx
399 mov [ecx], eax
400 lea eax, [ebp-12]
401 add eax, 4
402 push eax
403 mov eax, 2
404 pop ecx
405 mov [ecx], eax
406 lea eax, [ebp-12]
407 add eax, 8
408 push eax
409 mov eax, 0
410 pop ecx
411 mov [ecx], eax
412 lea eax, [ebp-12]
413 push eax
414 call is_type_start
415 add esp, 4
416 mov esp, ebp
417 pop ebp
418 ret
419 mov eax, 0
420 mov esp, ebp
421 pop ebp
422 ret
423 global _start
424 _start:
425 mov eax, [esp]
426 lea ecx, [esp+4]
427 push ecx
428 push eax
429 call main
430 mov ebx, eax
431 mov eax, 1
432 int 0x80
433 section .rodata
434 __str_0:
435 db 118
436 db 111
437 db 105
438 db 100
439 db 0
440 __str_1:
441 db 99
442 db 104
443 db 97
444 db 114
445 db 0
446 __str_2:
447 db 115
448 db 104
449 db 111
450 db 114
451 db 116
452 db 0
453 __str_3:
454 db 105
455 db 110
456 db 116
457 db 0
458 __str_4:
459 db 108
460 db 111
461 db 110
462 db 103
463 db 0
464 __str_5:
465 db 115
466 db 105
467 db 103
468 db 110
469 db 101
470 db 100
471 db 0
472 __str_6:
473 db 117
474 db 110
475 db 115
476 db 105
477 db 103
478 db 110
479 db 101
480 db 100
481 db 0
482 __str_7:
483 db 115
484 db 116
485 db 114
486 db 117
487 db 99
488 db 116
489 db 0
490 __str_8:
491 db 117
492 db 110
493 db 105
494 db 111
495 db 110
496 db 0
497 __str_9:
498 db 101
499 db 110
500 db 117
501 db 109
502 db 0
503 __str_10:
504 db 99
505 db 111
506 db 110
507 db 115
508 db 116
509 db 0
510 __str_11:
511 db 118
512 db 111
513 db 108
514 db 97
515 db 116
516 db 105
517 db 108
518 db 101
519 db 0
520 __str_12:
521 db 115
522 db 116
523 db 97
524 db 116
525 db 105
526 db 99
527 db 0
528 __str_13:
529 db 101
530 db 120
531 db 116
532 db 101
533 db 114
534 db 110
535 db 0
536 section .bss
537 g_toks:
538 resb 1104903 /* ---------------- statements ---------------- */
904
905 static node_t *parse_one_local_decl(parser_t *p, type_t *base, int is_static) {
906 char name[MAX_NAME];
907 type_t *t = parse_declarator(p, base, name, NULL, NULL);
908 sym_t *s = add_local(p, name, t, is_static);
909 node_t *n = node_new(ND_VAR_DECL);
910 safe_strcpy(n->name, name, MAX_NAME);
911 n->type = t;
912 n->is_static_local = is_static;
913 safe_strcpy(n->asm_label, s->asm_label, MAX_NAME);
914 n->frame_offset = s->frame_offset;
915 if (match(p, TK_ASSIGN)) n->init = parse_initializer(p);
916 /* Same `T name[] = { ... }` size-inference fix as parse_global_var -
917 * needed here too for e.g. a function-local `static const T ops[] =
918 * {...};` (used throughout the kernel's scripting-language parser
919 * files) - see that function's comment for why array_len otherwise
920 * silently means sizeof(name) == 0. */
921 if (t->kind == TY_ARRAY && t->array_len < 0 && n->init && n->init->kind == ND_BLOCK) {
922 int count = 0;
923 for (node_t *v = n->init->list; v; v = v->next) count++;
924 t->array_len = count;
925 }
926 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 the kernel's scripting-language parser
- * files) - 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;
+ 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);
+ node_t *n = node_new(ND_VAR_DECL);
+ safe_strcpy(n->name, name, MAX_NAME);
+ n->type = t;
+ n->is_static_local = is_static;
+ 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 the kernel's scripting-language parser
+ * files) - see that function's comment for why array_len otherwise
+ * silently means sizeof(name) == 0.
+ *
+ * Must run *before* add_local() below, not after (a real, previously-
+ * latent bug this exact ordering caused: add_local() sizes the stack
+ * slot via type_size(t), which for an unsized TY_ARRAY with
+ * array_len still -1 comes back as 0, rounded up to the "minimum
+ * 4-byte slot" fallback - so a genuinely 60-byte local array like
+ * is_type_start's own `const char *kws[] = {...}` (Phase T8's
+ * self-hosting build is what first compiled this function through
+ * CC instead of gcc) got only 4 bytes reserved, and initializing
+ * kws[1..14] then wrote straight through the saved ebp, the return
+ * address, and the caller's own stack args - see docs/toolchain.md).
+ * Inferring the size first means add_local always sees the real,
+ * final array_len. */
+ 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;
+ }
+ sym_t *s = add_local(p, name, t, is_static);
+ safe_strcpy(n->asm_label, s->asm_label, MAX_NAME);
+ n->frame_offset = s->frame_offset;
+ 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 gcc -Wall -Wextra -O2 -o build/toolchain/cc toolchain/cc/*.c 2>&1 | tail -20 ./build/toolchain/cc -Itoolchain/include --target=host -o /tmp/tits.S /tmp/tits.c && \ ./build/toolchain/as -f elf32 -o /tmp/tits.o /tmp/tits.S && \ ./build/toolchain/ld -o /tmp/tits /tmp/tits.o timeout 3 /tmp/tits echo "exit=$? (expect 1)"
exit=1 (expect 1)
$ cd /home/martin/workspace/prototypes/boot-to-claude
gcc -Wall -Wextra -O2 -o build/toolchain/as toolchain/as/*.c 2>&1 | tail -20
gcc -Wall -Wextra -O2 -o build/toolchain/ld toolchain/ld/*.c 2>&1 | tail -20
rm -rf build/stage2 && mkdir -p build/stage2
fail=0
for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c; do
base=$(echo "$f" | tr '/' '_')
./build/toolchain/cc -Itoolchain/include --target=host -o "build/stage2/${base%.c}.S" "$f" || { echo "CODEGEN FAIL: $f"; fail=1; }
done
for s in build/stage2/*.S; do
o="${s%.S}.o"
./build/toolchain/as -f elf32 -o "$o" "$s" || { echo "AS FAIL: $s"; fail=1; }
done
./build/toolchain/ld -o build/stage2/as $(ls build/stage2/toolchain_as_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
./build/toolchain/ld -o build/stage2/cc $(ls build/stage2/toolchain_cc_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
./build/toolchain/ld -o build/stage2/ld $(ls build/stage2/toolchain_ld_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
chmod +x build/stage2/as build/stage2/cc build/stage2/ld
echo "stage2 build fail=$fail"
timeout 90 env CC_BIN=./build/stage2/cc AS_BIN=./build/stage2/as LD_BIN=./build/stage2/ld ./tools/run_toolchain_tests.sh 2>&1 | tail -30stage2 build fail=0 PASS t01_basic (5) PASS t02_control (7) ./tools/run_toolchain_tests.sh: line 50: 3733340 Segmentation fault (core dumped) "$AS" -f elf32 -o "$BUILD/$name.o" "$BUILD/$name.S"
$ cd /home/martin/workspace/prototypes/boot-to-claude for f in tests/toolchain/*.c; do name=$(basename "$f" .c) ./build/stage2/cc -I toolchain/include --target=host -o /tmp/tt.S "$f" 2>/dev/null timeout 5 ./build/stage2/as -f elf32 -o /tmp/tt.o /tmp/tt.S > /tmp/asout.log 2>&1 rc=$? if [ $rc -ne 0 ]; then echo "AS FAIL($rc): $name"; fi done
/bin/bash: line 9: 3733364 Segmentation fault (core dumped) timeout 5 ./build/stage2/as -f elf32 -o /tmp/tt.o /tmp/tt.S > /tmp/asout.log 2>&1 AS FAIL(139): t02_types /bin/bash: line 9: 3733387 Segmentation fault (core dumped) timeout 5 ./build/stage2/as -f elf32 -o /tmp/tt.o /tmp/tt.S > /tmp/asout.log 2>&1 AS FAIL(139): t05_structs /bin/bash: line 9: 3733422 Segmentation fault (core dumped) timeout 5 ./build/stage2/as -f elf32 -o /tmp/tt.o /tmp/tt.S > /tmp/asout.log 2>&1 AS FAIL(139): t11_array_size /bin/bash: line 9: 3733436 Segmentation fault (core dumped) timeout 5 ./build/stage2/as -f elf32 -o /tmp/tt.o /tmp/tt.S > /tmp/asout.log 2>&1 AS FAIL(139): t12_local_array_size
$ cd /home/martin/workspace/prototypes/boot-to-claude ./build/stage2/cc -I toolchain/include --target=host -o /tmp/t02types.S tests/toolchain/t02_types.c wc -l /tmp/t02types.S cat /tmp/t02types.S
188 /tmp/t02types.S
BITS 32
section .text
global find_free
find_free:
push ebp
mov ebp, esp
sub esp, 4
lea eax, [ebp-4]
push eax
mov eax, 0
pop ecx
mov [ecx], eax
.L0:
lea eax, [ebp+8]
mov eax, [eax]
push eax
lea eax, [ebp-4]
mov eax, [eax]
pop ecx
cmp eax, ecx
setl al
movzx eax, al
cmp eax, 0
je .L1
lea eax, [ebp-4]
mov eax, [eax]
push eax
lea eax, [g_inodes]
pop ecx
imul ecx, ecx, 64
add eax, ecx
movzx eax, byte [eax]
cmp eax, 0
sete al
movzx eax, al
cmp eax, 0
je .L3
lea eax, [ebp-4]
mov eax, [eax]
mov esp, ebp
pop ebp
ret
jmp .L4
.L3:
.L4:
.L2:
lea eax, [ebp-4]
mov ecx, eax
mov eax, [ecx]
mov edx, eax
add eax, 1
mov [ecx], eax
mov eax, edx
jmp .L0
.L1:
mov eax, 1
neg eax
mov esp, ebp
pop ebp
ret
mov eax, 0
mov esp, ebp
pop ebp
ret
global set_reader
set_reader:
push ebp
mov ebp, esp
lea eax, [g_reader]
push eax
lea eax, [ebp+8]
mov eax, [eax]
pop ecx
mov [ecx], eax
mov eax, 0
mov esp, ebp
pop ebp
ret
global sum_blocks
sum_blocks:
push ebp
mov ebp, esp
sub esp, 8
lea eax, [ebp-4]
push eax
mov eax, 0
pop ecx
mov [ecx], eax
lea eax, [ebp-8]
push eax
mov eax, 0
pop ecx
mov [ecx], eax
.L5:
mov eax, 8
push eax
lea eax, [ebp-8]
mov eax, [eax]
pop ecx
cmp eax, ecx
setl al
movzx eax, al
cmp eax, 0
je .L6
lea eax, [ebp-4]
push eax
lea eax, [ebp-8]
mov eax, [eax]
push eax
lea eax, [ebp+8]
mov eax, [eax]
add eax, 32
pop ecx
imul ecx, ecx, 4
add eax, ecx
mov eax, [eax]
push eax
lea eax, [ebp-4]
mov eax, [eax]
pop ecx
add eax, ecx
pop ecx
mov [ecx], eax
lea eax, [ebp-8]
mov ecx, eax
mov eax, [ecx]
mov edx, eax
add eax, 1
mov [ecx], eax
mov eax, edx
jmp .L5
.L6:
lea eax, [ebp-4]
mov eax, [eax]
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, 8
lea eax, [ebp-4]
push eax
mov eax, 42
pop ecx
mov [ecx], eax
lea eax, [ebp-8]
push eax
mov eax, 64
push eax
call find_free
add esp, 4
pop ecx
mov [ecx], eax
lea eax, [ebp-4]
mov eax, [eax]
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 _start
_start:
mov eax, [esp]
lea ecx, [esp+4]
push ecx
push eax
call main
mov ebx, eax
mov eax, 1
int 0x80
section .bss
g_reader:
resb 4
g_inodes:
resb 4096$ cd /home/martin/workspace/prototypes/boot-to-claude timeout 5 ./build/stage2/as -f elf32 -o /tmp/t02types.o /tmp/t02types.S echo "exit=$?" gdb -q -batch -ex run -ex "info registers" -ex "x/10i \$pc-20" --args ./build/stage2/as -f elf32 -o /tmp/t02types.o /tmp/t02types.S 2>&1 | tail -25
/bin/bash: line 5: 3733540 Segmentation fault (core dumped) timeout 5 ./build/stage2/as -f elf32 -o /tmp/t02types.o /tmp/t02types.S exit=139 ecx 0x62b95920 1656314144 edx 0x92ed883 154065027 ebx 0x3 3 esp 0xffffc788 0xffffc788 ebp 0xffffc9f4 0xffffc9f4 esi 0x0 0 edi 0x0 0 eip 0x8049150 0x8049150 eflags 0x10202 [ IF RF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x0 0 0x804913c: add %al,(%eax) 0x804913e: sub %ecx,%eax 0x8049140: push %eax 0x8049141: lea 0x826d86c,%eax 0x8049147: pop %ecx 0x8049148: imul $0x4,%ecx,%ecx 0x804914e: add %ecx,%eax => 0x8049150: mov (%eax),%eax 0x8049152: pop %ecx 0x8049153: mov %eax,(%ecx)
$ cd /home/martin/workspace/prototypes/boot-to-claude for n in 10 30 60 90 120 150 170 188; do head -n $n /tmp/t02types.S > /tmp/trunc.S timeout 3 ./build/stage2/as -f elf32 -o /tmp/trunc.o /tmp/trunc.S > /dev/null 2>&1 echo "n=$n exit=$?" done
n=10 exit=0 n=30 exit=1 n=60 exit=1 n=90 exit=1 n=120 exit=1 n=150 exit=1 n=170 exit=1 /bin/bash: line 7: 3733656 Segmentation fault (core dumped) timeout 3 ./build/stage2/as -f elf32 -o /tmp/trunc.o /tmp/trunc.S > /dev/null 2>&1 n=188 exit=139
$ cd /home/martin/workspace/prototypes/boot-to-claude for n in 172 175 178 180 183 185 187; do head -n $n /tmp/t02types.S > /tmp/trunc.S timeout 3 ./build/stage2/as -f elf32 -o /tmp/trunc.o /tmp/trunc.S > /dev/null 2>&1 echo "n=$n exit=$?" done sed -n '178,188p' /tmp/t02types.S
n=172 exit=1
n=175 exit=1
n=178 exit=1
n=180 exit=1
n=183 exit=1
n=185 exit=1
/bin/bash: line 8: 3733695 Segmentation fault (core dumped) timeout 3 ./build/stage2/as -f elf32 -o /tmp/trunc.o /tmp/trunc.S > /dev/null 2>&1
n=187 exit=139
push ecx
push eax
call main
mov ebx, eax
mov eax, 1
int 0x80
section .bss
g_reader:
resb 4
g_inodes:
resb 4096$ cd /home/martin/workspace/prototypes/boot-to-claude cat -n /tmp/t02types.S | tail -8 wc -l /tmp/t02types.S
181 mov ebx, eax 182 mov eax, 1 183 int 0x80 184 section .bss 185 g_reader: 186 resb 4 187 g_inodes: 188 resb 4096 188 /tmp/t02types.S
$ cd /home/martin/workspace/prototypes/boot-to-claude for n in 185 186 187 188; do head -n $n /tmp/t02types.S > /tmp/trunc.S timeout 3 ./build/stage2/as -f elf32 -o /tmp/trunc.o /tmp/trunc.S > /tmp/trunc.log 2>&1 echo "n=$n exit=$? log:"; cat /tmp/trunc.log done
n=185 exit=1 log: as: relocation against unknown symbol 'g_inodes' n=186 exit=1 log: as: relocation against unknown symbol 'g_inodes' /bin/bash: line 7: 3733848 Segmentation fault (core dumped) timeout 3 ./build/stage2/as -f elf32 -o /tmp/trunc.o /tmp/trunc.S > /tmp/trunc.log 2>&1 n=187 exit=139 log: /bin/bash: line 7: 3733865 Segmentation fault (core dumped) timeout 3 ./build/stage2/as -f elf32 -o /tmp/trunc.o /tmp/trunc.S > /tmp/trunc.log 2>&1 n=188 exit=139 log:
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'static.*define_label' -A 20 toolchain/as/parser.c
329:static void define_label(pstate_t *ps, const char *raw_name) {
330- asm_ctx_t *ctx = ps->ctx;
331- char qname[MAX_NAME];
332- qualify_local(ps, raw_name, qname, sizeof(qname));
333- if (raw_name[0] != '.') {
334- safe_strcpy(ctx->last_global_label, raw_name, MAX_NAME);
335- }
336- symbol_t *s = sym_get_or_create(ctx, qname);
337- if (s->defined) { perr(ps, "duplicate label"); return; }
338- s->defined = 1;
339- s->section = ps->cur_section;
340- s->offset = ps->offsets[ps->cur_section];
341-}
342-
343-static section_kind_t normalize_section(const char *name) {
344- if (ieq(name, ".text") || ieq(name, ".text.entry")) return SEC_TEXT;
345- if (ieq(name, ".data")) return SEC_DATA;
346- if (ieq(name, ".bss")) return SEC_BSS;
347- if (ieq(name, ".rodata")) return SEC_RODATA;
348- return SEC_TEXT;
349-}$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'typedef struct.*pstate\|} pstate_t' -B 15 toolchain/as/parser.c | head -20
56-static int line_is_ident(line_t *ln, const char *kw) {
57- token_t t = line_peek(ln);
58- return t.kind == TK_IDENT && ieq(t.text, kw);
59-}
60-
61-/* --- state carried across the whole parse --- */
62-
63-typedef struct {
64- asm_ctx_t *ctx;
65- int bits;
66- section_kind_t cur_section;
67- uint32_t offsets[MAX_SECTIONS]; /* running location counter per section */
68- const char *filename;
69- int line_no;
70- int had_error;
71:} pstate_t;$ cd /home/martin/workspace/prototypes/boot-to-claude cat tests/toolchain/t02_types.c
#include <stdint.h>
#include <stddef.h>
typedef struct {
uint8_t used;
char name[27];
uint32_t size;
uint32_t blocks[8];
} __attribute__((packed)) inode_t;
typedef union {
int32_t i;
char *s;
} value_t;
typedef void (*read_fn_t)(uint32_t block, void *buf);
static read_fn_t g_reader;
static inode_t g_inodes[64];
int find_free(int max) {
for (int i = 0; i < max; i++) {
if (!g_inodes[i].used) {
return i;
}
}
return -1;
}
void set_reader(read_fn_t fn) {
g_reader = fn;
}
int sum_blocks(inode_t *ino) {
int total = 0;
int i = 0;
while (i < 8) {
total = total + (int)ino->blocks[i];
i++;
}
return total;
}
int main(void) {
value_t v;
v.i = 42;
int idx = find_free(64);
return idx + v.i;
}$ cd /home/martin/workspace/prototypes/boot-to-claude
cat > /tmp/tbss.S <<'EOF'
BITS 32
section .text
global main
main:
push ebp
mov ebp, esp
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
section .bss
g_reader:
resb 4
g_inodes:
resb 4096
EOF
timeout 3 ./build/stage2/as -f elf32 -o /tmp/tbss.o /tmp/tbss.S
echo "exit=$?"exit=0
$ cd /home/martin/workspace/prototypes/boot-to-claude
cat > /tmp/tbss2.S <<'EOF'
BITS 32
section .text
global find_free
find_free:
push ebp
mov ebp, esp
sub esp, 4
lea eax, [ebp-4]
push eax
mov eax, 0
pop ecx
mov [ecx], eax
.L0:
lea eax, [ebp+8]
mov eax, [eax]
push eax
lea eax, [ebp-4]
mov eax, [eax]
pop ecx
cmp eax, ecx
setl al
movzx eax, al
cmp eax, 0
je .L1
lea eax, [ebp-4]
mov eax, [eax]
push eax
lea eax, [g_inodes]
pop ecx
imul ecx, ecx, 64
add eax, ecx
movzx eax, byte [eax]
cmp eax, 0
sete al
movzx eax, al
cmp eax, 0
je .L3
lea eax, [ebp-4]
mov eax, [eax]
mov esp, ebp
pop ebp
ret
jmp .L4
.L3:
.L4:
.L2:
lea eax, [ebp-4]
mov ecx, eax
mov eax, [ecx]
mov edx, eax
add eax, 1
mov [ecx], eax
mov eax, edx
jmp .L0
.L1:
mov eax, 1
neg eax
mov esp, ebp
pop ebp
ret
global main
main:
push ebp
mov ebp, esp
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
section .bss
g_inodes:
resb 4096
EOF
timeout 3 ./build/stage2/as -f elf32 -o /tmp/tbss2.o /tmp/tbss2.S
echo "exit=$?"/bin/bash: line 170: 3736119 Segmentation fault (core dumped) timeout 3 ./build/stage2/as -f elf32 -o /tmp/tbss2.o /tmp/tbss2.S exit=139
$ cd /home/martin/workspace/prototypes/boot-to-claude sed 's/imul ecx, ecx, 64/imul ecx, ecx, 4/' /tmp/tbss2.S > /tmp/tbss3.S timeout 3 ./build/stage2/as -f elf32 -o /tmp/tbss3.o /tmp/tbss3.S echo "imul-by-4 exit=$?" sed '/imul ecx, ecx, 64/d' /tmp/tbss2.S > /tmp/tbss4.S timeout 3 ./build/stage2/as -f elf32 -o /tmp/tbss4.o /tmp/tbss4.S echo "no-imul exit=$?"
/bin/bash: line 10: 3736172 Segmentation fault (core dumped) timeout 3 ./build/stage2/as -f elf32 -o /tmp/tbss3.o /tmp/tbss3.S imul-by-4 exit=139 /bin/bash: line 14: 3736189 Segmentation fault (core dumped) timeout 3 ./build/stage2/as -f elf32 -o /tmp/tbss4.o /tmp/tbss4.S no-imul exit=139
$ cd /home/martin/workspace/prototypes/boot-to-claude
cat > /tmp/tmin.S <<'EOF'
BITS 32
section .text
global find_free
find_free:
push ebp
mov ebp, esp
sub esp, 4
lea eax, [ebp-4]
push eax
mov eax, 0
pop ecx
mov [ecx], eax
.L0:
lea eax, [ebp+8]
mov eax, [eax]
push eax
lea eax, [ebp-4]
mov eax, [eax]
pop ecx
cmp eax, ecx
setl al
movzx eax, al
cmp eax, 0
je .L1
jmp .L2
.L2:
lea eax, [ebp-4]
mov ecx, eax
mov eax, [ecx]
add eax, 1
mov [ecx], eax
jmp .L0
.L1:
mov eax, 1
neg eax
mov esp, ebp
pop ebp
ret
global main
main:
push ebp
mov ebp, esp
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
EOF
timeout 3 ./build/stage2/as -f elf32 -o /tmp/tmin.o /tmp/tmin.S
echo "exit=$?"exit=0
$ cd /home/martin/workspace/prototypes/boot-to-claude cat -n /tmp/tbss4.S | head -45
1 BITS 32
2 section .text
3 global find_free
4 find_free:
5 push ebp
6 mov ebp, esp
7 sub esp, 4
8 lea eax, [ebp-4]
9 push eax
10 mov eax, 0
11 pop ecx
12 mov [ecx], eax
13 .L0:
14 lea eax, [ebp+8]
15 mov eax, [eax]
16 push eax
17 lea eax, [ebp-4]
18 mov eax, [eax]
19 pop ecx
20 cmp eax, ecx
21 setl al
22 movzx eax, al
23 cmp eax, 0
24 je .L1
25 lea eax, [ebp-4]
26 mov eax, [eax]
27 push eax
28 lea eax, [g_inodes]
29 pop ecx
30 add eax, ecx
31 movzx eax, byte [eax]
32 cmp eax, 0
33 sete al
34 movzx eax, al
35 cmp eax, 0
36 je .L3
37 lea eax, [ebp-4]
38 mov eax, [eax]
39 mov esp, ebp
40 pop ebp
41 ret
42 jmp .L4
43 .L3:
44 .L4:
45 .L2:$ cd /home/martin/workspace/prototypes/boot-to-claude wc -l /tmp/tbss2.S for n in 20 30 40 45 46 47 48; do head -n $n /tmp/tbss2.S > /tmp/tt.S timeout 3 ./build/stage2/as -f elf32 -o /tmp/tt.o /tmp/tt.S > /tmp/tt.log 2>&1 echo "n=$n exit=$? $(cat /tmp/tt.log)" done
81 /tmp/tbss2.S n=20 exit=0 n=30 exit=1 as: relocation against unknown symbol 'find_free.L1' n=40 exit=1 as: relocation against unknown symbol 'find_free.L1' n=45 exit=1 as: relocation against unknown symbol 'find_free.L1' n=46 exit=1 as: relocation against unknown symbol 'find_free.L1' n=47 exit=1 as: relocation against unknown symbol 'find_free.L1' n=48 exit=1 as: relocation against unknown symbol 'find_free.L1'
$ cd /home/martin/workspace/prototypes/boot-to-claude
cat > /tmp/tmin2.S <<'EOF'
BITS 32
section .text
global find_free
find_free:
push ebp
mov ebp, esp
lea eax, [g_inodes]
movzx eax, byte [eax]
mov esp, ebp
pop ebp
ret
global main
main:
push ebp
mov ebp, esp
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
section .bss
g_inodes:
resb 4096
EOF
timeout 3 ./build/stage2/as -f elf32 -o /tmp/tmin2.o /tmp/tmin2.S
echo "exit=$?"/bin/bash: line 72: 3736297 Segmentation fault (core dumped) timeout 3 ./build/stage2/as -f elf32 -o /tmp/tmin2.o /tmp/tmin2.S exit=139
$ cd /home/martin/workspace/prototypes/boot-to-claude
cat > /tmp/tmin3.S <<'EOF'
BITS 32
section .text
global main
main:
push ebp
mov ebp, esp
lea eax, [g_inodes]
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
section .bss
g_inodes:
resb 4096
EOF
timeout 3 ./build/stage2/as -f elf32 -o /tmp/tmin3.o /tmp/tmin3.S
echo "lea-only exit=$?"/bin/bash: line 54: 3736318 Segmentation fault (core dumped) timeout 3 ./build/stage2/as -f elf32 -o /tmp/tmin3.o /tmp/tmin3.S lea-only exit=139
$ cd /home/martin/workspace/prototypes/boot-to-claude
sed 's/resb 4096/resb 4/' /tmp/tmin3.S > /tmp/tmin4.S
timeout 3 ./build/stage2/as -f elf32 -o /tmp/tmin4.o /tmp/tmin4.S
echo "resb4 exit=$?"
cat > /tmp/tmin5.S <<'EOF'
BITS 32
section .text
global main
main:
push ebp
mov ebp, esp
lea eax, [g_inodes]
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
section .data
g_inodes:
dd 42
EOF
timeout 3 ./build/stage2/as -f elf32 -o /tmp/tmin5.o /tmp/tmin5.S
echo "data-section exit=$?"/bin/bash: line 34: 3736340 Segmentation fault (core dumped) timeout 3 ./build/stage2/as -f elf32 -o /tmp/tmin4.o /tmp/tmin4.S resb4 exit=139 /bin/bash: line 62: 3736369 Segmentation fault (core dumped) timeout 3 ./build/stage2/as -f elf32 -o /tmp/tmin5.o /tmp/tmin5.S data-section exit=139
$ cd /home/martin/workspace/prototypes/boot-to-claude timeout 3 ./build/toolchain/as -f elf32 -o /tmp/tmin5_stage1.o /tmp/tmin5.S echo "stage1 exit=$?"
stage1 exit=0
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'static int64_t resolve_abs' -A 30 toolchain/as/encode.c
71:static int64_t resolve_abs(asm_ctx_t *ctx, int is_bin, const char *sym, int64_t disp,
72- section_kind_t patch_sec, uint32_t patch_off, int strict) {
73- if (!sym[0]) return disp;
74- symbol_t *s = sym_find(ctx, sym);
75- if (s && s->is_equ) return s->equ_value + disp; /* plain number, never org-adjusted */
76- if (is_bin) {
77- if (!s || !s->defined) {
78- if (strict) { fprintf(stderr, "as: undefined symbol '%s'\n", sym); exit(1); }
79- return disp;
80- }
81- return (int64_t)ctx->org + (int64_t)s->offset + disp;
82- }
83- /* ELF: absolute references always need a relocation - the section's
84- * final base address isn't known until link time. x86 ELF uses REL
85- * (not RELA) relocations: the addend has no dedicated field, it must
86- * be baked into the bytes at the relocated location, which is why
87- * this returns `disp` (the addend) rather than 0 - LD will add the
88- * resolved symbol value on top of whatever's already there. */
89- if (strict) {
90- add_reloc(ctx, patch_sec, patch_off, sym, (int32_t)disp, RELOC_ABS32);
91- }
92- return disp;
93-}
94-
95-/* Resolves a PC-relative call/jmp target. Same-section same-file targets
96- * fold to a fixed relative displacement directly; anything else (extern,
97- * or -f bin's "always resolvable" absolute model) is handled by the
98- * caller via this return value or a relocation. `next_ip_rel` is the
99- * SECTION-RELATIVE position right after the relative field (i.e. NOT
100- * org-adjusted - this function adds org itself when is_bin, consistently
101- * with how it computes the target, since bin-mode "addresses" are$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'static.*add_reloc\|void add_reloc' -A 15 toolchain/as/encode.c
55:static void add_reloc(asm_ctx_t *ctx, section_kind_t sec, uint32_t off, const char *sym,
56- int32_t addend, reloc_kind_t kind) {
57- if (ctx->nrelocs >= MAX_RELOCS) { fprintf(stderr, "as: too many relocations\n"); exit(1); }
58- reloc_t *r = &ctx->relocs[ctx->nrelocs++];
59- r->section = sec;
60- r->offset = off;
61- safe_strcpy(r->sym, sym, MAX_NAME);
62- r->addend = addend;
63- r->kind = kind;
64-}
65-
66-/* Resolves an absolute-address reference (symbol + disp) used as an
67- * immediate/memory-displacement value. is_bin selects -f bin semantics
68- * (single flat address space, always resolvable, no relocations) vs.
69- * -f elf32 (may need a relocation). Writes the resolved/placeholder
70- * value via e32/e16 at the call site - this just computes it. */$ cd /home/martin/workspace/prototypes/boot-to-claude gdb -q -batch -ex run -ex "info registers" -ex "x/15i \$pc-30" --args ./build/stage2/as -f elf32 -o /tmp/tmin5.o /tmp/tmin5.S 2>&1 | tail -30
ecx 0x62b94950 1656310096 edx 0x92ed883 154065027 ebx 0x3 3 esp 0xffffc788 0xffffc788 ebp 0xffffc9f4 0xffffc9f4 esi 0x0 0 edi 0x0 0 eip 0x8049150 0x8049150 eflags 0x10202 [ IF RF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x0 0 0x8049132: (bad) 0x8049133: (bad) 0x8049134: decl -0x3696a700(%ebx) 0x804913a: pop %esp 0x804913b: add %al,(%eax) 0x804913d: add %ch,(%ebx) 0x804913f: rcll $0x5,-0x73(%eax) 0x8049143: insb (%dx),%es:(%edi) 0x8049144: fsubs (%esi) 0x8049146: or %bl,0x69(%ecx) 0x8049149: leave 0x804914a: add $0x0,%al 0x804914c: add %al,(%eax) 0x804914e: add %ecx,%eax => 0x8049150: mov (%eax),%eax
$ cd /home/martin/workspace/prototypes/boot-to-claude objdump -d build/stage2/as -M intel --start-address=0x8049100 --stop-address=0x8049170 2>&1 echo "---symbol candidates---" objdump -d build/stage2/as -M intel 2>/dev/null | grep -B2 "imul.*0x4" | head -40
build/stage2/as: file format elf32-i386 ---symbol candidates---
$ cd /home/martin/workspace/prototypes/boot-to-claude gdb -q -batch \ -ex "break *0x8049150" \ -ex "run -f elf32 -o /tmp/tmin5.o /tmp/tmin5.S" \ -ex "find 0x8048000, 0x8049150, 0x55, 0x89, 0xe5" \ --args ./build/stage2/as 2>&1 | tail -20
Breakpoint 1 at 0x8049150 Breakpoint 1, 0x08049150 in ?? () Pattern not found.
$ cd /home/martin/workspace/prototypes/boot-to-claude sed -n '1,10p' toolchain/as/encode.c
#include "as.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int regnum(reg_t r) {
switch (r) {
case R_AL: case R_AX: case R_EAX: return 0;
case R_CL: case R_CX: case R_ECX: return 1;- static void add_reloc(asm_ctx_t *ctx, section_kind_t sec, uint32_t off, const char *sym,
- int32_t addend, reloc_kind_t kind) {
- if (ctx->nrelocs >= MAX_RELOCS) { fprintf(stderr, "as: too many relocations\n"); exit(1); }
- reloc_t *r = &ctx->relocs[ctx->nrelocs++];
- r->section = sec;
- r->offset = off;
- safe_strcpy(r->sym, sym, MAX_NAME);
- r->addend = addend;
- r->kind = kind;
- }
+ static void add_reloc(asm_ctx_t *ctx, section_kind_t sec, uint32_t off, const char *sym,
+ int32_t addend, reloc_kind_t kind) {
+ fprintf(stderr, "DBG: add_reloc entered nrelocs=%d sym=%s\n", ctx->nrelocs, sym);
+ if (ctx->nrelocs >= MAX_RELOCS) { fprintf(stderr, "as: too many relocations\n"); exit(1); }
+ fprintf(stderr, "DBG: about to index relocs array\n");
+ reloc_t *r = &ctx->relocs[ctx->nrelocs++];
+ fprintf(stderr, "DBG: got r, about to set fields\n");
+ r->section = sec;
+ r->offset = off;
+ safe_strcpy(r->sym, sym, MAX_NAME);
+ r->addend = addend;
+ r->kind = kind;
+ fprintf(stderr, "DBG: add_reloc done\n");
+ }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 -20 ./build/toolchain/cc -Itoolchain/include --target=host -o build/stage2/toolchain_as_encode.S toolchain/as/encode.c ./build/toolchain/as -f elf32 -o build/stage2/toolchain_as_encode.o build/stage2/toolchain_as_encode.S ./build/toolchain/ld -o build/stage2/as $(ls build/stage2/toolchain_as_*.o) $(ls build/stage2/toolchain_hostlib_*.o) chmod +x build/stage2/as timeout 3 ./build/stage2/as -f elf32 -o /tmp/tmin5.o /tmp/tmin5.S echo "exit=$?"
DBG: add_reloc entered nrelocs=0 sym=__str_0 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=1 sym=g_stderr_file DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=2 sym=fprintf DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=3 sym=__str_1 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=4 sym=g_stderr_file DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=5 sym=fprintf DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=6 sym=exit DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=7 sym=__str_2 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=8 sym=g_stderr_file DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=9 sym=fprintf DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=10 sym=__str_3 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=11 sym=g_stderr_file DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=12 sym=fprintf DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=13 sym=__str_4 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=14 sym=g_stderr_file DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=15 sym=fprintf DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=16 sym=sym_find DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=17 sym=__str_5 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=18 sym=g_stderr_file DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=19 sym=fprintf DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=20 sym=exit DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=21 sym=sym_find DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=22 sym=__str_6 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=23 sym=g_stderr_file DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=24 sym=fprintf DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=25 sym=exit DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=26 sym=__static_0 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=27 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=28 sym=__static_0 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=29 sym=__static_0 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=30 sym=__str_13 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=31 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=32 sym=__str_14 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=33 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=34 sym=__str_15 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=35 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=36 sym=__str_16 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=37 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=38 sym=__str_17 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=39 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=40 sym=__str_18 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=41 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=42 sym=__str_19 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=43 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=44 sym=__str_20 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=45 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=46 sym=__str_21 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=47 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=48 sym=__str_22 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=49 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=50 sym=__str_23 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=51 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=52 sym=__str_24 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=53 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=54 sym=__str_25 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=55 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=56 sym=__str_26 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=57 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=58 sym=__str_27 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=59 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=60 sym=__str_28 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=61 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=62 sym=__str_29 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=63 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=64 sym=__str_30 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=65 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=66 sym=__str_31 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=67 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=68 sym=__str_32 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=69 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=70 sym=__str_33 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=71 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=72 sym=__str_34 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=73 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=74 sym=__str_35 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=75 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=76 sym=__str_36 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=77 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=78 sym=__str_37 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=79 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=80 sym=__str_38 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=81 sym=strncmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=82 sym=__static_1 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=83 sym=__str_45 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=84 sym=snprintf DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=85 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=86 sym=__static_1 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=87 sym=__str_46 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=88 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=89 sym=__str_47 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=90 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=91 sym=__str_48 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=92 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=93 sym=__str_49 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=94 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=95 sym=__str_50 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=96 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=97 sym=__str_51 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=98 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=99 sym=__str_52 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=100 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=101 sym=__str_53 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=102 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=103 sym=__str_54 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=104 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=105 sym=__str_55 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=106 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=107 sym=__str_56 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=108 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=109 sym=__str_57 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=110 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=111 sym=__str_58 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=112 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=113 sym=__str_59 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=114 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=115 sym=__str_60 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=116 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=117 sym=__str_61 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=118 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=119 sym=__str_62 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=120 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=121 sym=__str_63 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=122 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=123 sym=__str_64 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=124 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=125 sym=__str_65 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=126 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=127 sym=__str_66 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=128 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=129 sym=__str_67 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=130 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=131 sym=__str_68 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=132 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=133 sym=__str_69 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=134 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=135 sym=__str_70 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=136 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=137 sym=__str_71 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=138 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=139 sym=__str_72 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=140 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=141 sym=__str_73 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=142 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=143 sym=__str_74 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=144 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=145 sym=__str_75 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=146 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=147 sym=__str_76 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=148 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=149 sym=__str_77 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=150 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=151 sym=__str_78 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=152 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=153 sym=__str_79 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=154 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=155 sym=__str_80 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=156 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=157 sym=__str_81 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=158 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=159 sym=__str_82 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=160 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=161 sym=__str_83 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=162 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=163 sym=__str_84 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=164 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=165 sym=__str_85 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=166 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=167 sym=__str_86 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=168 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=169 sym=__str_87 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=170 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=171 sym=__str_88 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=172 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=173 sym=__str_89 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=174 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=175 sym=__str_90 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=176 sym=strcmp DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=177 sym=__str_91 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=178 sym=g_stderr_file DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=179 sym=fprintf DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=180 sym=exit DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=181 sym=__str_92 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=182 sym=g_stderr_file DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=183 sym=fprintf DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=184 sym=__str_7 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=185 sym=__str_8 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=186 sym=__str_9 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=187 sym=__str_10 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=188 sym=__str_11 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=189 sym=__str_12 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=190 sym=__str_39 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=191 sym=__str_40 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=192 sym=__str_41 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=193 sym=__str_42 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=194 sym=__str_43 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=195 sym=__str_44 DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done DBG: add_reloc entered nrelocs=0 sym=g_inodes DBG: about to index relocs array DBG: got r, about to set fields DBG: add_reloc done /bin/bash: line 14: 3736619 Segmentation fault (core dumped) timeout 3 ./build/stage2/as -f elf32 -o /tmp/tmin5.o /tmp/tmin5.S exit=139
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n '^static.*\[\]\|^static.*\[[A-Za-z_0-9]*\]' toolchain/as/*.c | grep -v REGS
toolchain/as/elf_writer.c:8:static const char *SEC_NAMES[MAX_SECTIONS] = { ".text", ".data", ".bss", ".rodata" };
toolchain/as/elf_writer.c:9:static const int SEC_IS_BSS[MAX_SECTIONS] = { 0, 0, 1, 0 };
toolchain/as/elf_writer.c:10:static const int SEC_IS_EXEC[MAX_SECTIONS] = { 1, 0, 0, 0 };- fprintf(stderr, "DBG: got r, about to set fields\n"); - r->section = sec; - r->offset = off; - safe_strcpy(r->sym, sym, MAX_NAME); - r->addend = addend; - r->kind = kind; - fprintf(stderr, "DBG: add_reloc done\n"); + fprintf(stderr, "DBG: got r, about to set section\n"); + r->section = sec; + fprintf(stderr, "DBG: set section, about to set offset\n"); + r->offset = off; + fprintf(stderr, "DBG: set offset, about to strcpy sym\n"); + safe_strcpy(r->sym, sym, MAX_NAME); + fprintf(stderr, "DBG: strcpy done, about to set addend\n"); + r->addend = addend; + fprintf(stderr, "DBG: set addend, about to set kind\n"); + r->kind = kind; + fprintf(stderr, "DBG: add_reloc done\n");
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 -20
rm -rf build/stage2_dbg && mkdir -p build/stage2_dbg
for f in toolchain/as/*.c toolchain/hostlib/*.c; do
base=$(echo "$f" | tr '/' '_')
./build/toolchain/cc -Itoolchain/include --target=host -o "build/stage2_dbg/${base%.c}.S" "$f"
./build/toolchain/as -f elf32 -o "build/stage2_dbg/${base%.c}.o" "build/stage2_dbg/${base%.c}.S"
done
./build/toolchain/ld -o build/stage2_dbg/as $(ls build/stage2_dbg/toolchain_as_*.o) $(ls build/stage2_dbg/toolchain_hostlib_*.o)
chmod +x build/stage2_dbg/as
timeout 3 ./build/stage2_dbg/as -f elf32 -o /tmp/tmin5.o /tmp/tmin5.S > /tmp/full_trace.log 2>&1
echo "exit=$?"
tail -15 /tmp/full_trace.log<persisted-output> Output too large (123.3KB). Full output saved to: /home/martin/.claude/projects/-home-martin-workspace-prototypes-boot-to-claude/03468d1c-5b29-4e24-8f0d-2a7435d227ab/tool-results/bagvw7sto.txt Preview (first 2KB): DBG: add_reloc entered nrelocs=0 sym=__str_0 DBG: about to index relocs array DBG: got r, about to set section DBG: set section, about to set offset DBG: set offset, about to strcpy sym DBG: strcpy done, about to set addend DBG: set addend, about to set kind DBG: add_reloc done DBG: add_reloc entered nrelocs=1 sym=fopen DBG: about to index relocs array DBG: got r, about to set section DBG: set section, about to set offset DBG: set offset, about to strcpy sym DBG: strcpy done, about to set addend DBG: set addend, about to set kind DBG: add_reloc done DBG: add_reloc entered nrelocs=2 sym=__str_1 DBG: about to index relocs array DBG: got r, about to set section DBG: set section, about to set offset DBG: set offset, about to strcpy sym DBG: strcpy done, about to set addend DBG: set addend, about to set kind DBG: add_reloc done DBG: add_reloc entered nrelocs=3 sym=perror DBG: about to index relocs array DBG: got r, about to set section DBG: set section, about to set offset DBG: set offset, about to strcpy sym DBG: strcpy done, about to set addend DBG: set addend, about to set kind DBG: add_reloc done DBG: add_reloc entered nrelocs=4 sym=fwrite DBG: about to index relocs array DBG: got r, about to set section DBG: set section, about to set offset DBG: set offset, about to strcpy sym DBG: strcpy done, about to set addend DBG: set addend, about to set kind DBG: add_reloc done DBG: add_reloc entered nrelocs=5 sym=fclose DBG: about to index relocs array DBG: got r, about to set section DBG: set section, about to set offset DBG: set offset, about to strcpy sym DBG: strcpy done, about to set addend DBG: set addend, about to set kind DBG: add_reloc done DBG: add_reloc entered nrelocs=0 sym=strlen DBG: about to index relocs array DBG: got r, about to set section DBG: set section, about to set offset DBG: set offset, about to strcpy sym DBG: strcpy done, about to set addend DBG: set addend, about to set kind DBG: add_reloc done DBG: add_reloc entered nrelocs=1 sym=memcpy ... </persisted-output>
$ tail -20 "/home/martin/.claude/projects/-home-martin-workspace-prototypes-boot-to-claude/03468d1c-5b29-4e24-8f0d-2a7435d227ab/tool-results/bagvw7sto.txt"
DBG: set addend, about to set kind DBG: add_reloc done DBG: add_reloc entered nrelocs=10 sym=memset DBG: about to index relocs array DBG: got r, about to set section DBG: set section, about to set offset DBG: set offset, about to strcpy sym DBG: strcpy done, about to set addend DBG: set addend, about to set kind DBG: add_reloc done /bin/bash: line 23: 3736913 Segmentation fault (core dumped) timeout 3 ./build/stage2_dbg/as -f elf32 -o /tmp/tmin5.o /tmp/tmin5.S > /tmp/full_trace.log 2>&1 exit=139 DBG: add_reloc entered nrelocs=0 sym=g_inodes DBG: about to index relocs array DBG: got r, about to set section DBG: set section, about to set offset DBG: set offset, about to strcpy sym DBG: strcpy done, about to set addend DBG: set addend, about to set kind DBG: add_reloc done
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n '\[.*\]' toolchain/as/encode.c | grep -vE '^\s*[0-9]+:\s*(out|fprintf|/\*|\*)' | grep -E '\w+\[[a-z_]+\]' | grep -v 'st->ops\[' | head -30
208: if (strcmp(m, table[i].name) == 0) { *base = table[i].base; *ext = table[i].ext; return 1; }
537: snprintf(full, sizeof(full), "set%s", cc[i].suf);
539: e8(&e, 0x0F); e8(&e, cc[i].op);
743: stmt_t *st = &ctx->stmts[i];
754: uint8_t *dst = (sec == SEC_BSS) ? NULL : ctx->sec_bytes[sec] + st->offset;
761: if (st->offset + (uint32_t)len > ctx->sec_len[sec]) {
762: ctx->sec_len[sec] = st->offset + (uint32_t)len;505 : c->disp;
506 e32(&e, (uint32_t)v);
507 return e.len;
508 }
509 if (strcmp(m, "imul") == 0 && st->bits == 32 && a->kind == OPND_REG && is_reg32(a->reg)) {
510 e8(&e, 0x0F); e8(&e, 0xAF);
511 encode_rm32(&e, ctx, is_bin, b, regnum(a->reg), st->section, st->offset, strict);
512 return e.len;
513 }
514 if (strcmp(m, "idiv") == 0 && (a->kind == OPND_REG || a->kind == OPND_MEM)) {
515 e8(&e, 0xF7);
516 encode_rm32(&e, ctx, is_bin, a, 7, st->section, st->offset, strict);
517 return e.len;
518 }
519 if (strcmp(m, "not") == 0 && (a->kind == OPND_REG || a->kind == OPND_MEM)) {
520 e8(&e, 0xF7);
521 encode_rm32(&e, ctx, is_bin, a, 2, st->section, st->offset, strict);
522 return e.len;
523 }
524 if (strcmp(m, "neg") == 0 && (a->kind == OPND_REG || a->kind == OPND_MEM)) {
525 e8(&e, 0xF7);
526 encode_rm32(&e, ctx, is_bin, a, 3, st->section, st->offset, strict);
527 return e.len;
528 }
529 if (strcmp(m, "cdq") == 0) { e8(&e, 0x99); return e.len; }
530
531 if (strncmp(m, "set", 3) == 0 && a->kind == OPND_REG && is_reg8(a->reg)) {
532 static const struct { const char *suf; uint8_t op; } cc[] = {
533 { "e", 0x94 }, { "ne", 0x95 }, { "l", 0x9C }, { "le", 0x9E }, { "g", 0x9F }, { "ge", 0x9D },
534 };
535 for (size_t i = 0; i < sizeof(cc) / sizeof(cc[0]); i++) {
536 char full[16];
537 snprintf(full, sizeof(full), "set%s", cc[i].suf);
538 if (strcmp(m, full) == 0) {
539 e8(&e, 0x0F); e8(&e, cc[i].op);
540 e8(&e, (uint8_t)(0xC0 | regnum(a->reg)));
541 return e.len;
542 }
543 }
544 }
545
546 if (strcmp(m, "movzx") == 0 && is_reg32(a->reg) && is_reg16(b->reg)) {
547 /* MOVZX r32, r/m16 = 0F B7 /r, no 0x66 prefix - the opcode
548 * itself already means "32-bit destination, 16-bit source"; a
549 * leading 0x66 operand-size override reinterprets *both*
550 * operands as 16-bit, silently downgrading this to r16,r16 (a
551 * near no-op) instead of actually zero-extending into the full
552 * 32-bit register. Never previously exercised: every earlier
553 * movzx use was the byte-from-memory form (0F B6) below, not
554 * this register-to-register 16-to-32 form - only CC's new$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'g_statics\|is_static_local' toolchain/cc/codegen.c
115: * is_static_local/frame_offset/asm_label fields ND_VAR and an indirect
118: if (n->is_global || n->is_static_local) {
435: if (n->is_static_local) return; /* emitted into .data/.bss separately */
718:static node_t *g_statics[MAX_STATICS];
770: if (n->kind == ND_VAR_DECL && n->is_static_local && g_n_statics < MAX_STATICS) {
771: g_statics[g_n_statics++] = n;
875: if (g_statics[i]->init) any_data = 1; else any_bss = 1;
894: node_t *n = g_statics[i];
910: node_t *n = g_statics[i];870 if (n->kind == ND_VAR_DECL && n->is_global && !n->is_extern_decl) {
871 if (n->init) any_data = 1; else any_bss = 1;
872 }
873 }
874 for (int i = 0; i < g_n_statics; i++) {
875 if (g_statics[i]->init) any_data = 1; else any_bss = 1;
876 }
877
878 if (any_data) {
879 out("section .data\n");
880 for (node_t *n = prog; n; n = n->next) {
881 if (n->kind == ND_VAR_DECL && n->is_global && !n->is_extern_decl && n->init) {
882 /* file-scope `static` keeps internal linkage - no `global`,
883 * same reasoning as function-local statics below (two
884 * files' own file-private `static T name;` must not
885 * collide as duplicate global symbols). */
886 if (!n->is_file_static) out("global %s\n", n->asm_label);
887 out("%s:\n", n->asm_label);
888 emit_data_bytes(n->init, n->type);
889 }
890 }
891 for (int i = 0; i < g_n_statics; i++) {
892 /* Not `global`, same reasoning as __str_N above -
893 * __static_N is only numbered uniquely within one file. */
894 node_t *n = g_statics[i];
895 if (n->init) {
896 out("%s:\n", n->asm_label);
897 emit_data_bytes(n->init, n->type);
898 }
899 }
900 }
901 if (any_bss) {
902 out("section .bss\n");
903 for (node_t *n = prog; n; n = n->next) {
904 if (n->kind == ND_VAR_DECL && n->is_global && !n->is_extern_decl && !n->init) {
905 if (!n->is_file_static) out("global %s\n", n->asm_label);
906 out("%s:\n resb %d\n", n->asm_label, type_size(n->type));
907 }
908 }
909 for (int i = 0; i < g_n_statics; i++) {
910 node_t *n = g_statics[i];
911 if (!n->init) {
912 out("%s:\n resb %d\n", n->asm_label, type_size(n->type));
913 }
914 }
915 }
916 }
917 $ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'emit_scalar_bytes' -A 12 toolchain/cc/codegen.c | head -14
583:static void emit_scalar_bytes(node_t *v, type_t *t) {
584- /* A string-literal initializer for a pointer-typed field (e.g.
585- * toolchain/as/parser.c's `static const regdef_t REGS[] =
586- * {{"al",R_AL,1}, ...}` - Phase T8's self-hosting build is what
587- * first exercised a string literal inside a struct-array global
588- * initializer) needs the string's own label, not v->num - an ND_STR
589- * node never sets ->num at all, so this silently emitted `dd 0`
590- * (a NULL pointer) instead of a relocation to the string's bytes. */
591- if (v->kind == ND_STR) { out(" dd %s\n", v->asm_label); return; }
592- if (t->kind == TY_CHAR) out(" db %lld\n", (long long)v->num);
593- else out(" dd %lld\n", (long long)v->num);
594-}
595-
--$ cd /home/martin/workspace/prototypes/boot-to-claude
cat > /tmp/tcc.c <<'EOF'
#include <stdint.h>
int main(void) {
static const struct { const char *suf; uint8_t op; } cc[] = {
{ "e", 0x94 }, { "ne", 0x95 }, { "l", 0x9C }, { "le", 0x9E }, { "g", 0x9F }, { "ge", 0x9D },
};
int total = 0;
for (int i = 0; i < 6; i++) {
total += (int)cc[i].suf[0] + cc[i].op;
}
return total & 0xFF;
}
EOF
./build/toolchain/cc -Itoolchain/include --target=host -o /tmp/tcc.S /tmp/tcc.c && \
./build/toolchain/as -f elf32 -o /tmp/tcc.o /tmp/tcc.S && \
./build/toolchain/ld -o /tmp/tcc /tmp/tcc.o
/tmp/tcc
echo "exit=$?"
cat /tmp/tcc.S | grep -A 20 'section .data\|__static'DBG: add_reloc entered nrelocs=0 sym=__static_0
DBG: about to index relocs array
DBG: got r, about to set section
DBG: set section, about to set offset
DBG: set offset, about to strcpy sym
DBG: strcpy done, about to set addend
DBG: set addend, about to set kind
DBG: add_reloc done
DBG: add_reloc entered nrelocs=1 sym=__static_0
DBG: about to index relocs array
DBG: got r, about to set section
DBG: set section, about to set offset
DBG: set offset, about to strcpy sym
DBG: strcpy done, about to set addend
DBG: set addend, about to set kind
DBG: add_reloc done
DBG: add_reloc entered nrelocs=2 sym=__str_0
DBG: about to index relocs array
DBG: got r, about to set section
DBG: set section, about to set offset
DBG: set offset, about to strcpy sym
DBG: strcpy done, about to set addend
DBG: set addend, about to set kind
DBG: add_reloc done
DBG: add_reloc entered nrelocs=3 sym=__str_1
DBG: about to index relocs array
DBG: got r, about to set section
DBG: set section, about to set offset
DBG: set offset, about to strcpy sym
DBG: strcpy done, about to set addend
DBG: set addend, about to set kind
DBG: add_reloc done
DBG: add_reloc entered nrelocs=4 sym=__str_2
DBG: about to index relocs array
DBG: got r, about to set section
DBG: set section, about to set offset
DBG: set offset, about to strcpy sym
DBG: strcpy done, about to set addend
DBG: set addend, about to set kind
DBG: add_reloc done
DBG: add_reloc entered nrelocs=5 sym=__str_3
DBG: about to index relocs array
DBG: got r, about to set section
DBG: set section, about to set offset
DBG: set offset, about to strcpy sym
DBG: strcpy done, about to set addend
DBG: set addend, about to set kind
DBG: add_reloc done
DBG: add_reloc entered nrelocs=6 sym=__str_4
DBG: about to index relocs array
DBG: got r, about to set section
DBG: set section, about to set offset
DBG: set offset, about to strcpy sym
DBG: strcpy done, about to set addend
DBG: set addend, about to set kind
DBG: add_reloc done
DBG: add_reloc entered nrelocs=7 sym=__str_5
DBG: about to index relocs array
DBG: got r, about to set section
DBG: set section, about to set offset
DBG: set offset, about to strcpy sym
DBG: strcpy done, about to set addend
DBG: set addend, about to set kind
DBG: add_reloc done
exit=24
lea eax, [__static_0]
pop ecx
imul ecx, ecx, 8
add eax, ecx
add eax, 4
movzx eax, byte [eax]
push eax
mov eax, 0
push eax
lea eax, [ebp-8]
mov eax, [eax]
push eax
lea eax, [__static_0]
pop ecx
imul ecx, ecx, 8
add eax, ecx
mov eax, [eax]
pop ecx
add eax, ecx
movzx eax, byte [eax]
pop ecx
add eax, ecx
push eax
pop ecx
mov edx, [esp]
mov eax, [edx]
add eax, ecx
pop ecx
mov [ecx], eax
.L2:
lea eax, [ebp-8]
mov ecx, eax
mov eax, [ecx]
--
section .data
__static_0:
dd __str_0
db 148
resb 3
dd __str_1
db 149
resb 3
dd __str_2
db 156
resb 3
dd __str_3
db 158
resb 3
dd __str_4
db 159
resb 3
dd __str_5
db 157
resb 3$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'static int64_t resolve_abs' -A 25 toolchain/as/encode.c | tail -15
90- }
91- /* ELF: absolute references always need a relocation - the section's
92- * final base address isn't known until link time. x86 ELF uses REL
93- * (not RELA) relocations: the addend has no dedicated field, it must
94- * be baked into the bytes at the relocated location, which is why
95- * this returns `disp` (the addend) rather than 0 - LD will add the
96- * resolved symbol value on top of whatever's already there. */
97- if (strict) {
98- add_reloc(ctx, patch_sec, patch_off, sym, (int32_t)disp, RELOC_ABS32);
99- }
100- return disp;
101-}
102-
103-/* Resolves a PC-relative call/jmp target. Same-section same-file targets
104- * fold to a fixed relative displacement directly; anything else (extern,$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n '"lea"' toolchain/as/encode.c
491: if (strcmp(m, "lea") == 0 && st->bits == 32 && a->kind == OPND_REG && is_reg32(a->reg) && b->kind == OPND_MEM) {160 }
161 if (rm->reg == R_ESP) {
162 if (rm->disp == 0) {
163 e8(e, (uint8_t)(0x04 | (reg_field << 3)));
164 e8(e, 0x24);
165 } else {
166 e8(e, (uint8_t)(0x44 | (reg_field << 3)));
167 e8(e, 0x24);
168 e8(e, (uint8_t)rm->disp);
169 }
170 return;
171 }
172 if (rm->reg == R_EBP) {
173 e8(e, (uint8_t)(0x85 | (reg_field << 3)));
174 e32(e, (uint32_t)rm->disp);
175 return;
176 }
177 if (rm->reg != R_NONE) {
178 /* [reg] / [reg+disp] for any other GP register (EAX/ECX/EDX/EBX/
179 * ESI/EDI) - no SIB needed, and mod=00 with no displacement is
180 * safe here (rm=101/EBP is the only base register where mod=00
181 * means something else - see the EBP case above). Always uses
182 * the disp32 form when non-zero, same "no size-optimization"
183 * philosophy as everywhere else in this encoder. */
184 if (rm->disp == 0) {
185 e8(e, (uint8_t)(0x00 | (reg_field << 3) | regnum(rm->reg)));
186 } else {
187 e8(e, (uint8_t)(0x80 | (reg_field << 3) | regnum(rm->reg)));
188 e32(e, (uint32_t)rm->disp);
189 }
190 return;
191 }
192 /* bare label, no base register: 32-bit direct address */
193 e8(e, (uint8_t)(0x05 | (reg_field << 3)));
194 uint32_t patch_off = stmt_offset + (uint32_t)e->len;
195 int64_t addr = resolve_abs(ctx, is_bin, rm->sym, rm->disp, patch_sec, patch_off, strict);
196 e32(e, (uint32_t)addr);
197 }
198
199 /* ADD/OR/AND/SUB/XOR/CMP share one opcode block layout:$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'SEC_NAMES\[' toolchain/as/elf_writer.c
8:static const char *SEC_NAMES[MAX_SECTIONS] = { ".text", ".data", ".bss", ".rodata" };
57: name_content[s] = strtab_add(&shstrtab, SEC_NAMES[s]);
63: snprintf(nm, sizeof(nm), ".rel%s", SEC_NAMES[s]);<tool_use_error>InputValidationError: Read was called with input that could not be parsed as JSON.
You sent (first 109 of 109 bytes): {"file_path": "/home/martin/workspace/prototypes/boot-to-claude/toolchain/as/elf_writer.c", "offset": 40, 70}
Common causes: unescaped backslashes in file paths (use / or \\), unescaped control characters, or truncated output. Retry with valid JSON.</tool_use_error>40 * with relocs, then .symtab, .strtab, .shstrtab. Computed before the
41 * symbol table below, which needs shidx_content[] to translate our
42 * internal section_kind_t into the real ELF section header index -
43 * they're NOT the same number, since only sections actually used
44 * appear in the file (e.g. a .bss-only object has no .data, so .bss
45 * isn't at its "usual" index). */
46 static strtab_builder_t shstrtab;
47 memset(&shstrtab, 0, sizeof(shstrtab));
48
49 int shidx_content[MAX_SECTIONS];
50 for (int i = 0; i < MAX_SECTIONS; i++) { shidx_content[i] = -1; }
51
52 int nsh = 1;
53 uint32_t name_content[MAX_SECTIONS], name_rel[MAX_SECTIONS];
54 for (int i = 0; i < n_content; i++) {
55 int s = content_secs[i];
56 shidx_content[s] = nsh++;
57 name_content[s] = strtab_add(&shstrtab, SEC_NAMES[s]);
58 }
59 for (int i = 0; i < n_content; i++) {
60 int s = content_secs[i];
61 if (reloc_for[s]) {
62 char nm[MAX_NAME];
63 snprintf(nm, sizeof(nm), ".rel%s", SEC_NAMES[s]);
64 nsh++;
65 name_rel[s] = strtab_add(&shstrtab, nm);
66 }
67 }
68 int shidx_symtab = nsh++;
69 uint32_t name_symtab = strtab_add(&shstrtab, ".symtab");$ which rr 2>&1; rr --version 2>&1
Exit code 127 /bin/bash: line 1: rr: command not found
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n '"push"\|"pop"\|"call"\|"int"\|"mov"' toolchain/as/encode.c | head -20
326: if (strcmp(m, "mov") == 0) {
649: if (strcmp(m, "call") == 0 && a->kind == OPND_REG && is_reg32(a->reg)) {
657: if (strcmp(m, "call") == 0) {
672: if (strcmp(m, "push") == 0) {
682: if (strcmp(m, "pop") == 0) {
706: if (strcmp(m, "int") == 0) { e8(&e, 0xCD); e8(&e, (uint8_t)a->disp); return e.len; }<tool_use_error>InputValidationError: Read was called with input that could not be parsed as JSON.
You sent (first 109 of 109 bytes): {"file_path": "/home/martin/workspace/prototypes/boot-to-claude/toolchain/as/encode.c", "offset": 649, 707}
}
Common causes: unescaped backslashes in file paths (use / or \\), unescaped control characters, or truncated output. Retry with valid JSON.</tool_use_error>649 if (strcmp(m, "call") == 0 && a->kind == OPND_REG && is_reg32(a->reg)) {
650 /* call r/m32 (FF /2) - indirect call through a register, e.g. a
651 * function-pointer variable's loaded value (see cc.h's
652 * is_indirect_call / docs/toolchain.md). */
653 e8(&e, 0xFF);
654 e8(&e, (uint8_t)(0xC0 | (2 << 3) | regnum(a->reg)));
655 return e.len;
656 }
657 if (strcmp(m, "call") == 0) {
658 if (st->bits == 16) {
659 e8(&e, 0xE8);
660 int64_t rel = resolve_pcrel(ctx, is_bin, a->sym, a->disp, st->section,
661 (int64_t)st->offset + 3, st->section, st->offset + 1, strict, 2);
662 e16(&e, (uint16_t)rel);
663 } else {
664 e8(&e, 0xE8);
665 int64_t rel = resolve_pcrel(ctx, is_bin, a->sym, a->disp, st->section,
666 (int64_t)st->offset + 5, st->section, st->offset + 1, strict, 4);
667 e32(&e, (uint32_t)rel);
668 }
669 return e.len;
670 }
671
672 if (strcmp(m, "push") == 0) {
673 if (a->kind == OPND_IMM) {
674 e8(&e, 0x68);
675 int64_t v = a->has_sym ? resolve_abs(ctx, is_bin, a->sym, a->disp, st->section, st->offset + 1, strict) : a->disp;
676 e32(&e, (uint32_t)v);
677 return e.len;
678 }
679 if (is_reg32(a->reg)) { e8(&e, (uint8_t)(0x50 + regnum(a->reg))); return e.len; }
680 if (is_reg16(a->reg)) { e8(&e, (uint8_t)(0x50 + regnum(a->reg))); return e.len; }
681 }
682 if (strcmp(m, "pop") == 0) {
683 if (is_reg32(a->reg) || is_reg16(a->reg)) { e8(&e, (uint8_t)(0x58 + regnum(a->reg))); return e.len; }
684 }
685
686 if (strcmp(m, "in") == 0) {
687 if (b->kind == OPND_IMM) { e8(&e, 0xE4); e8(&e, (uint8_t)b->disp); return e.len; }
688 if (b->reg == R_DX && a->reg == R_AL) { e8(&e, 0xEC); return e.len; }
689 /* IN AX,DX needs the 0x66 operand-size override - 0xED alone,
690 * in 32-bit code, means IN EAX,DX (the default operand size).
691 * Without the prefix this silently read two 16-bit words per
692 * call (packed into EAX, high half discarded by the caller's
693 * own movzx), draining twice as many words off the ATA data
694 * port as intended and dropping every other one - see
695 * kernel/ata.c / docs/toolchain.md. */
696 if (b->reg == R_DX && a->reg == R_AX) { e8(&e, 0x66); e8(&e, 0xED); return e.len; }
697 if (b->reg == R_DX && a->reg == R_EAX) { e8(&e, 0xED); return e.len; }
698 }
699 if (strcmp(m, "out") == 0) {
700 if (a->kind == OPND_IMM) { e8(&e, 0xE6); e8(&e, (uint8_t)a->disp); return e.len; }
701 if (a->reg == R_DX && b->reg == R_AL) { e8(&e, 0xEE); return e.len; }
702 if (a->reg == R_DX && b->reg == R_AX) { e8(&e, 0x66); e8(&e, 0xEF); return e.len; }
703 if (a->reg == R_DX && b->reg == R_EAX) { e8(&e, 0xEF); return e.len; }
704 }
705
706 if (strcmp(m, "int") == 0) { e8(&e, 0xCD); e8(&e, (uint8_t)a->disp); return e.len; }
707 if (strcmp(m, "lgdt") == 0 && st->bits == 16) {
708 /* boot/stage2.S's real-mode form: a direct 16-bit absolute$ cd /home/martin/workspace/prototypes/boot-to-claude git diff toolchain/as/encode.c | head -5 git checkout -- toolchain/as/encode.c grep -n 'DBG' toolchain/as/encode.c
diff --git a/toolchain/as/encode.c b/toolchain/as/encode.c
index cbd752e..64cc156 100644
--- a/toolchain/as/encode.c
+++ b/toolchain/as/encode.c
@@ -54,13 +54,21 @@ static void e32(emitter_t *e, uint32_t v) {$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'SEC_BSS.*NULL\|dst = (sec == SEC_BSS)' toolchain/as/encode.c
(Bash completed with no output)
728
729 fprintf(stderr, "as: line %d: unsupported instruction form: %s\n", st->line_no, m);
730 exit(1);
731 }
732
733 int as_encode_pass2(asm_ctx_t *ctx) {
734 for (int i = 0; i < ctx->nstmts; i++) {
735 stmt_t *st = &ctx->stmts[i];
736 section_kind_t sec = st->section;
737 uint8_t *dst = ctx->sec_bytes[sec] + st->offset;
738 int len = stmt_encode(ctx, st, dst, 1);
739 if ((uint32_t)len != st->length) {
740 fprintf(stderr, "as: internal error: length mismatch for '%s' at line %d (pass1=%u pass2=%d)\n",
741 st->mnemonic, st->line_no, st->length, len);
742 return -1;
743 }
744 if (st->offset + (uint32_t)len > ctx->sec_len[sec]) {
745 ctx->sec_len[sec] = st->offset + (uint32_t)len;
746 }
747 }- stmt_t *st = &ctx->stmts[i]; - section_kind_t sec = st->section; - uint8_t *dst = ctx->sec_bytes[sec] + st->offset; - int len = stmt_encode(ctx, st, dst, 1); + stmt_t *st = &ctx->stmts[i]; + section_kind_t sec = st->section; + /* .bss bytes are never actually read back (the ELF/flat writers + * both skip emitting them - a loader zeroes that address range + * at load time instead), only their *count* matters. Passing + * NULL here (the same "dry-run, length-only" mode pass 1 always + * uses - see e8) skips materializing them into sec_bytes at all, + * which matters once self-hosting is in the picture (Phase T8): + * AS's own `ctx` global alone reserves ~20MB of .bss, and + * sec_bytes[section] is a fixed 64KB buffer - writing real + * zero bytes for a reservation that large overflows it. */ + uint8_t *dst = (sec == SEC_BSS) ? NULL : ctx->sec_bytes[sec] + st->offset; + int len = stmt_encode(ctx, st, dst, 1);
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 git diff --stat toolchain/as/ grep -c 'elf32_st_info\|elf32_r_sym' toolchain/as/elf_writer.c grep -n 'MAX_SYMS\|MAX_STMTS\|MAX_RELOCS' toolchain/as/as.h
toolchain/as/elf_format.h | 11 +++++++---- toolchain/as/elf_writer.c | 11 ++++++++--- toolchain/as/encode.c | 11 ++++++++++- toolchain/as/parser.c | 8 +++++++- 4 files changed, 32 insertions(+), 9 deletions(-) 1 21:#define MAX_SYMS 4096 22:#define MAX_STMTS 32768 23:#define MAX_RELOCS 4096 101: symbol_t syms[MAX_SYMS]; 104: stmt_t stmts[MAX_STMTS]; 107: reloc_t relocs[MAX_RELOCS];
- uint8_t *dst = (sec == SEC_BSS) ? NULL : ctx->sec_bytes[sec] + st->offset; - int len = stmt_encode(ctx, st, dst, 1); + uint8_t *dst = (sec == SEC_BSS) ? NULL : ctx->sec_bytes[sec] + st->offset; + fprintf(stderr, "DBG: pass2 stmt i=%d mnem=%s\n", i, st->mnemonic); + int len = stmt_encode(ctx, st, dst, 1); + fprintf(stderr, "DBG: pass2 stmt i=%d encoded len=%d\n", i, 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 -20
rm -rf build/stage2_dbg2 && mkdir -p build/stage2_dbg2
for f in toolchain/as/*.c toolchain/hostlib/*.c; do
base=$(echo "$f" | tr '/' '_')
./build/toolchain/cc -Itoolchain/include --target=host -o "build/stage2_dbg2/${base%.c}.S" "$f"
./build/toolchain/as -f elf32 -o "build/stage2_dbg2/${base%.c}.o" "build/stage2_dbg2/${base%.c}.S"
done
./build/toolchain/ld -o build/stage2_dbg2/as $(ls build/stage2_dbg2/toolchain_as_*.o) $(ls build/stage2_dbg2/toolchain_hostlib_*.o)
chmod +x build/stage2_dbg2/as
timeout 3 ./build/stage2_dbg2/as -f elf32 -o /tmp/tmin5.o /tmp/tmin5.S > /tmp/pass2_trace.log 2>&1
echo "exit=$?"
tail -20 /tmp/pass2_trace.log<persisted-output> Output too large (1.7MB). Full output saved to: /home/martin/.claude/projects/-home-martin-workspace-prototypes-boot-to-claude/03468d1c-5b29-4e24-8f0d-2a7435d227ab/tool-results/bxsj7jjef.txt Preview (first 2KB): DBG: pass2 stmt i=0 mnem=push DBG: pass2 stmt i=0 encoded len=1 DBG: pass2 stmt i=1 mnem=mov DBG: pass2 stmt i=1 encoded len=2 DBG: pass2 stmt i=2 mnem=sub DBG: pass2 stmt i=2 encoded len=6 DBG: pass2 stmt i=3 mnem=lea DBG: pass2 stmt i=3 encoded len=6 DBG: pass2 stmt i=4 mnem=push DBG: pass2 stmt i=4 encoded len=1 DBG: pass2 stmt i=5 mnem=mov DBG: pass2 stmt i=5 encoded len=5 DBG: pass2 stmt i=6 mnem=pop DBG: pass2 stmt i=6 encoded len=1 DBG: pass2 stmt i=7 mnem=mov DBG: pass2 stmt i=7 encoded len=2 DBG: pass2 stmt i=8 mnem=lea DBG: pass2 stmt i=8 encoded len=6 DBG: pass2 stmt i=9 mnem=mov DBG: pass2 stmt i=9 encoded len=2 DBG: pass2 stmt i=10 mnem=push DBG: pass2 stmt i=10 encoded len=1 DBG: pass2 stmt i=11 mnem=mov DBG: pass2 stmt i=11 encoded len=5 DBG: pass2 stmt i=12 mnem=push DBG: pass2 stmt i=12 encoded len=1 DBG: pass2 stmt i=13 mnem=lea DBG: pass2 stmt i=13 encoded len=6 DBG: pass2 stmt i=14 mnem=mov DBG: pass2 stmt i=14 encoded len=2 DBG: pass2 stmt i=15 mnem=pop DBG: pass2 stmt i=15 encoded len=1 DBG: pass2 stmt i=16 mnem=add DBG: pass2 stmt i=16 encoded len=2 DBG: pass2 stmt i=17 mnem=pop DBG: pass2 stmt i=17 encoded len=1 DBG: pass2 stmt i=18 mnem=cmp DBG: pass2 stmt i=18 encoded len=2 DBG: pass2 stmt i=19 mnem=setl DBG: pass2 stmt i=19 encoded len=3 DBG: pass2 stmt i=20 mnem=movzx DBG: pass2 stmt i=20 encoded len=3 DBG: pass2 stmt i=21 mnem=cmp DBG: pass2 stmt i=21 encoded len=6 DBG: pass2 stmt i=22 mnem=je DBG: pass2 stmt i=22 encoded len=6 DBG: pass2 stmt i=23 mnem=lea DBG: pass2 stmt i=23 encoded len=6 DBG: pass2 stmt i=24 mnem=mov DBG: pass2 stmt i=24 encoded len=2 DBG: pass2 stmt i=25 mnem=push DBG: pass2 stmt i=25 encoded len=1 DBG: pass2 stmt i=26 mnem=lea DBG: pass2 stmt i=26 encoded len=6 DBG: pass2 stmt i=27 mnem=mov DBG: pass2 stmt i=27 encoded len=2 DBG: pass2 stmt i=28 mnem=pop DBG: pass2 stmt i=28 encoded len=1 DBG: pass2 stmt i=29 mnem=add DBG: pass2 stmt i=29 encoded len=2 DBG: pass2 stmt i=30 mnem=movzx ... </persisted-output>
$ tail -40 "/home/martin/.claude/projects/-home-martin-workspace-prototypes-boot-to-claude/03468d1c-5b29-4e24-8f0d-2a7435d227ab/tool-results/bxsj7jjef.txt"
DBG: pass2 stmt i=494 mnem=lea DBG: pass2 stmt i=494 encoded len=6 DBG: pass2 stmt i=495 mnem=mov DBG: pass2 stmt i=495 encoded len=2 DBG: pass2 stmt i=496 mnem=mov DBG: pass2 stmt i=496 encoded len=2 DBG: pass2 stmt i=497 mnem=pop DBG: pass2 stmt i=497 encoded len=1 DBG: pass2 stmt i=498 mnem=ret DBG: pass2 stmt i=498 encoded len=1 DBG: pass2 stmt i=499 mnem=mov DBG: pass2 stmt i=499 encoded len=5 DBG: pass2 stmt i=500 mnem=mov DBG: pass2 stmt i=500 encoded len=2 DBG: pass2 stmt i=501 mnem=pop DBG: pass2 stmt i=501 encoded len=1 DBG: pass2 stmt i=502 mnem=ret DBG: pass2 stmt i=502 encoded len=1 /bin/bash: line 23: 3739582 Segmentation fault (core dumped) timeout 3 ./build/stage2_dbg2/as -f elf32 -o /tmp/tmin5.o /tmp/tmin5.S > /tmp/pass2_trace.log 2>&1 exit=139 DBG: pass2 stmt i=5 mnem=ret DBG: pass2 stmt i=5 encoded len=1 DBG: pass2 stmt i=6 mnem=mov DBG: pass2 stmt i=6 encoded len=3 DBG: pass2 stmt i=7 mnem=lea DBG: pass2 stmt i=7 encoded len=4 DBG: pass2 stmt i=8 mnem=push DBG: pass2 stmt i=8 encoded len=1 DBG: pass2 stmt i=9 mnem=push DBG: pass2 stmt i=9 encoded len=1 DBG: pass2 stmt i=10 mnem=call DBG: pass2 stmt i=10 encoded len=5 DBG: pass2 stmt i=11 mnem=mov DBG: pass2 stmt i=11 encoded len=2 DBG: pass2 stmt i=12 mnem=mov DBG: pass2 stmt i=12 encoded len=5 DBG: pass2 stmt i=13 mnem=int DBG: pass2 stmt i=13 encoded len=2 DBG: pass2 stmt i=14 mnem=.dd DBG: pass2 stmt i=14 encoded len=4
$ cat /tmp/tmin5.S
BITS 32
section .text
global main
main:
push ebp
mov ebp, esp
lea eax, [g_inodes]
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
section .data
g_inodes:
dd 42$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n '^int write_elf32' toolchain/as/elf_writer.c wc -l toolchain/as/elf_writer.c
26:int write_elf32(asm_ctx_t *ctx, const char *path) {
238 toolchain/as/elf_writer.c1 #include "as.h"
2 #include "elf_format.h"
3
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7
8 static const char *SEC_NAMES[MAX_SECTIONS] = { ".text", ".data", ".bss", ".rodata" };
9 static const int SEC_IS_BSS[MAX_SECTIONS] = { 0, 0, 1, 0 };
10 static const int SEC_IS_EXEC[MAX_SECTIONS] = { 1, 0, 0, 0 };
11
12 typedef struct {
13 char buf[1 << 20];
14 uint32_t len;
15 } strtab_builder_t;
16
17 static uint32_t strtab_add(strtab_builder_t *st, const char *s) {
18 if (st->len == 0) { st->buf[0] = '\0'; st->len = 1; } /* index 0 = empty string, ELF convention */
19 uint32_t off = st->len;
20 size_t n = strlen(s) + 1;
21 memcpy(st->buf + st->len, s, n);
22 st->len += (uint32_t)n;
23 return off;
24 }
25
26 int write_elf32(asm_ctx_t *ctx, const char *path) {
27 /* --- decide which content sections exist --- */
28 int content_secs[MAX_SECTIONS];
29 int n_content = 0;
30 for (int s = 0; s < MAX_SECTIONS; s++) {
31 if (ctx->sec_used[s]) content_secs[n_content++] = s;
32 }
33
34 int reloc_for[MAX_SECTIONS]; /* does section s have any relocations? */
35 for (int s = 0; s < MAX_SECTIONS; s++) reloc_for[s] = 0;
36 for (int i = 0; i < ctx->nrelocs; i++) reloc_for[ctx->relocs[i].section] = 1;
37
38 /* --- section header string table + index plan ---
39 * 0=NULL, 1..n_content=content, then one .rel.X per content section
40 * with relocs, then .symtab, .strtab, .shstrtab. Computed before the
41 * symbol table below, which needs shidx_content[] to translate our
42 * internal section_kind_t into the real ELF section header index -
43 * they're NOT the same number, since only sections actually used
44 * appear in the file (e.g. a .bss-only object has no .data, so .bss
45 * isn't at its "usual" index). */
46 static strtab_builder_t shstrtab;
47 memset(&shstrtab, 0, sizeof(shstrtab));
48
49 int shidx_content[MAX_SECTIONS];
50 for (int i = 0; i < MAX_SECTIONS; i++) { shidx_content[i] = -1; }
51
52 int nsh = 1;
53 uint32_t name_content[MAX_SECTIONS], name_rel[MAX_SECTIONS];
54 for (int i = 0; i < n_content; i++) {
55 int s = content_secs[i];
56 shidx_content[s] = nsh++;
57 name_content[s] = strtab_add(&shstrtab, SEC_NAMES[s]);
58 }
59 for (int i = 0; i < n_content; i++) {
60 int s = content_secs[i];
61 if (reloc_for[s]) {
62 char nm[MAX_NAME];
63 snprintf(nm, sizeof(nm), ".rel%s", SEC_NAMES[s]);
64 nsh++;
65 name_rel[s] = strtab_add(&shstrtab, nm);
66 }
67 }
68 int shidx_symtab = nsh++;
69 uint32_t name_symtab = strtab_add(&shstrtab, ".symtab");
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 /* Not a compound literal (CC doesn't support them - see
83 * docs/toolchain.md, Phase T3/T8): symtab is `static`, but a prior
84 * call in the same process could have left index 0 non-zero, so
85 * this can't just rely on static zero-init either. */
86 memset(&symtab[nsym], 0, sizeof(symtab[nsym]));
87 nsym++; /* index 0: null symbol */
88
89 for (int pass = 0; pass < 2; pass++) { /* pass 0 = locals, pass 1 = globals */
90 for (int i = 0; i < ctx->nsyms; i++) {
91 symbol_t *s = &ctx->syms[i];
92 if (s->is_equ) continue;
93 if (!s->defined && !s->is_extern) continue;
94 int is_glob = s->is_global || s->is_extern;
95 if ((pass == 0 && is_glob) || (pass == 1 && !is_glob)) continue;
96
97 Elf32_Sym sym = {0};
98 sym.st_name = strtab_add(&strtab, s->name);
99 sym.st_info = (unsigned char)elf32_st_info(is_glob ? STB_GLOBAL : STB_LOCAL, STT_NOTYPE);
100 if (s->defined) {
101 sym.st_shndx = (Elf32_Half)shidx_content[s->section];
102 sym.st_value = s->offset;
103 } else {
104 sym.st_shndx = SHN_UNDEF;
105 }
106 sym_elf_index[i] = nsym;
107 symtab[nsym++] = sym;
108 }
109 }
110 int first_global = 1;
111 for (int i = 1; i < nsym; i++) {
112 if ((symtab[i].st_info >> 4) == STB_LOCAL) first_global = i + 1;
113 }
114
115 /* --- relocation table entries, grouped by target section --- */
116 static Elf32_Rel rels[MAX_SECTIONS][MAX_RELOCS];
117 int nrels[MAX_SECTIONS] = {0};
118 for (int i = 0; i < ctx->nrelocs; i++) {
119 reloc_t *r = &ctx->relocs[i];
120 symbol_t *s = sym_find(ctx, r->sym);
121 if (!s) { fprintf(stderr, "as: relocation against unknown symbol '%s'\n", r->sym); return -1; }
122 int symidx = sym_elf_index[s - ctx->syms];
123 int type = r->kind == RELOC_PC32 ? R_386_PC32 : R_386_32;
124 int sec = r->section;
125 rels[sec][nrels[sec]].r_offset = r->offset;
126 rels[sec][nrels[sec]].r_info = elf32_r_info(symidx, type);
127 nrels[sec]++;
128 }
129
130 /* --- lay out the file: Ehdr, content sections' bytes, rel tables,
131 * symtab, strtab, shstrtab, then the section header table --- */
132 uint32_t off = sizeof(Elf32_Ehdr);
133 uint32_t off_content[MAX_SECTIONS];
134 for (int i = 0; i < n_content; i++) {
135 int s = content_secs[i];
136 off_content[s] = off;
137 if (!SEC_IS_BSS[s]) off += ctx->sec_len[s];
138 }
139 uint32_t off_rel[MAX_SECTIONS];
140 for (int i = 0; i < n_content; i++) {
141 int s = content_secs[i];
142 if (reloc_for[s]) { off_rel[s] = off; off += (uint32_t)nrels[s] * sizeof(Elf32_Rel); }
143 }
144 uint32_t off_symtab = off; off += (uint32_t)nsym * sizeof(Elf32_Sym);
145 uint32_t off_strtab = off; off += strtab.len;
146 uint32_t off_shstrtab = off; off += shstrtab.len;
147 uint32_t off_shdrs = (off + 3) & ~3u;
148
149 FILE *f = fopen(path, "wb");
150 if (!f) { perror("as: fopen"); return -1; }
151
152 Elf32_Ehdr eh = {0};
153 eh.e_ident[0] = ELFMAG0; eh.e_ident[1] = ELFMAG1; eh.e_ident[2] = ELFMAG2; eh.e_ident[3] = ELFMAG3;
154 eh.e_ident[4] = ELFCLASS32; eh.e_ident[5] = ELFDATA2LSB; eh.e_ident[6] = EV_CURRENT;
155 eh.e_ident[7] = ELFOSABI_SYSV;
156 eh.e_type = ET_REL;
157 eh.e_machine = EM_386;
158 eh.e_version = EV_CURRENT;
159 eh.e_shoff = off_shdrs;
160 eh.e_ehsize = sizeof(Elf32_Ehdr);
161 eh.e_shentsize = sizeof(Elf32_Shdr);
162 eh.e_shnum = (Elf32_Half)nsh;
163 eh.e_shstrndx = (Elf32_Half)shidx_shstrtab;
164 fwrite(&eh, sizeof(eh), 1, f);
165
166 for (int i = 0; i < n_content; i++) {
167 int s = content_secs[i];
168 if (!SEC_IS_BSS[s]) fwrite(ctx->sec_bytes[s], 1, ctx->sec_len[s], f);
169 }
170 for (int i = 0; i < n_content; i++) {
171 int s = content_secs[i];
172 if (reloc_for[s]) fwrite(rels[s], sizeof(Elf32_Rel), (size_t)nrels[s], f);
173 }
174 fwrite(symtab, sizeof(Elf32_Sym), (size_t)nsym, f);
175 fwrite(strtab.buf, 1, strtab.len, f);
176 fwrite(shstrtab.buf, 1, shstrtab.len, f);
177
178 while (ftell(f) < (long)off_shdrs) fputc(0, f);
179
180 Elf32_Shdr sh;
181 memset(&sh, 0, sizeof(sh));
182 fwrite(&sh, sizeof(sh), 1, f); /* NULL section */
183
184 for (int i = 0; i < n_content; i++) {
185 int s = content_secs[i];
186 memset(&sh, 0, sizeof(sh));
187 sh.sh_name = name_content[s];
188 sh.sh_type = SEC_IS_BSS[s] ? SHT_NOBITS : SHT_PROGBITS;
189 sh.sh_flags = SHF_ALLOC | (SEC_IS_EXEC[s] ? SHF_EXECINSTR : 0) | (s == SEC_TEXT ? 0 : SHF_WRITE);
190 sh.sh_offset = off_content[s];
191 sh.sh_size = ctx->sec_len[s];
192 sh.sh_addralign = 4;
193 fwrite(&sh, sizeof(sh), 1, f);
194 }
195 for (int i = 0; i < n_content; i++) {
196 int s = content_secs[i];
197 if (!reloc_for[s]) continue;
198 memset(&sh, 0, sizeof(sh));
199 sh.sh_name = name_rel[s];
200 sh.sh_type = SHT_REL;
201 sh.sh_offset = off_rel[s];
202 sh.sh_size = (uint32_t)nrels[s] * sizeof(Elf32_Rel);
203 sh.sh_link = (Elf32_Word)shidx_symtab;
204 sh.sh_info = (Elf32_Word)shidx_content[s];
205 sh.sh_addralign = 4;
206 sh.sh_entsize = sizeof(Elf32_Rel);
207 fwrite(&sh, sizeof(sh), 1, f);
208 }
209 memset(&sh, 0, sizeof(sh));
210 sh.sh_name = name_symtab;
211 sh.sh_type = SHT_SYMTAB;
212 sh.sh_offset = off_symtab;
213 sh.sh_size = (uint32_t)nsym * sizeof(Elf32_Sym);
214 sh.sh_link = (Elf32_Word)shidx_strtab;
215 sh.sh_info = (Elf32_Word)first_global;
216 sh.sh_addralign = 4;
217 sh.sh_entsize = sizeof(Elf32_Sym);
218 fwrite(&sh, sizeof(sh), 1, f);
219
220 memset(&sh, 0, sizeof(sh));
221 sh.sh_name = name_strtab;
222 sh.sh_type = SHT_STRTAB;
223 sh.sh_offset = off_strtab;
224 sh.sh_size = strtab.len;
225 sh.sh_addralign = 1;
226 fwrite(&sh, sizeof(sh), 1, f);
227
228 memset(&sh, 0, sizeof(sh));
229 sh.sh_name = name_shstrtab;
230 sh.sh_type = SHT_STRTAB;
231 sh.sh_offset = off_shstrtab;
232 sh.sh_size = shstrtab.len;
233 sh.sh_addralign = 1;
234 fwrite(&sh, sizeof(sh), 1, f);
235
236 fclose(f);
237 return 0;
238 }
239 - int write_elf32(asm_ctx_t *ctx, const char *path) {
- /* --- decide which content sections exist --- */
- int content_secs[MAX_SECTIONS];
- int n_content = 0;
- for (int s = 0; s < MAX_SECTIONS; s++) {
- if (ctx->sec_used[s]) content_secs[n_content++] = s;
- }
+ int write_elf32(asm_ctx_t *ctx, const char *path) {
+ fprintf(stderr, "DBG: write_elf32 entered\n");
+ /* --- decide which content sections exist --- */
+ int content_secs[MAX_SECTIONS];
+ int n_content = 0;
+ for (int s = 0; s < MAX_SECTIONS; s++) {
+ if (ctx->sec_used[s]) content_secs[n_content++] = s;
+ }
+ fprintf(stderr, "DBG: n_content=%d\n", n_content);The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/elf_writer.c has been updated successfully. (file state is current in your context — no need to Read it back)
- int nsh = 1;
- uint32_t name_content[MAX_SECTIONS], name_rel[MAX_SECTIONS];
- for (int i = 0; i < n_content; i++) {
- int s = content_secs[i];
- shidx_content[s] = nsh++;
- name_content[s] = strtab_add(&shstrtab, SEC_NAMES[s]);
- }
+ int nsh = 1;
+ uint32_t name_content[MAX_SECTIONS], name_rel[MAX_SECTIONS];
+ for (int i = 0; i < n_content; i++) {
+ int s = content_secs[i];
+ fprintf(stderr, "DBG: content loop i=%d s=%d\n", i, s);
+ shidx_content[s] = nsh++;
+ fprintf(stderr, "DBG: about to SEC_NAMES[%d]\n", s);
+ name_content[s] = strtab_add(&shstrtab, SEC_NAMES[s]);
+ fprintf(stderr, "DBG: strtab_add done\n");
+ }
+ fprintf(stderr, "DBG: content_secs loop 1 done\n");The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/elf_writer.c has been updated successfully. (file state is current in your context — no need to Read it back)
- uint8_t *dst = (sec == SEC_BSS) ? NULL : ctx->sec_bytes[sec] + st->offset; - fprintf(stderr, "DBG: pass2 stmt i=%d mnem=%s\n", i, st->mnemonic); - int len = stmt_encode(ctx, st, dst, 1); - fprintf(stderr, "DBG: pass2 stmt i=%d encoded len=%d\n", i, len); + uint8_t *dst = (sec == SEC_BSS) ? NULL : ctx->sec_bytes[sec] + st->offset; + int len = stmt_encode(ctx, st, dst, 1);
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 -20
rm -rf build/stage2_dbg3 && mkdir -p build/stage2_dbg3
for f in toolchain/as/*.c toolchain/hostlib/*.c; do
base=$(echo "$f" | tr '/' '_')
./build/toolchain/cc -Itoolchain/include --target=host -o "build/stage2_dbg3/${base%.c}.S" "$f"
./build/toolchain/as -f elf32 -o "build/stage2_dbg3/${base%.c}.o" "build/stage2_dbg3/${base%.c}.S"
done
./build/toolchain/ld -o build/stage2_dbg3/as $(ls build/stage2_dbg3/toolchain_as_*.o) $(ls build/stage2_dbg3/toolchain_hostlib_*.o)
chmod +x build/stage2_dbg3/as
timeout 3 ./build/stage2_dbg3/as -f elf32 -o /tmp/tmin5.o /tmp/tmin5.S > /tmp/elfw_trace.log 2>&1
echo "exit=$?"
tail -20 /tmp/elfw_trace.logDBG: write_elf32 entered DBG: n_content=2 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content loop i=1 s=3 DBG: about to SEC_NAMES[3] DBG: strtab_add done DBG: content_secs loop 1 done DBG: write_elf32 entered DBG: n_content=4 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content loop i=1 s=1 DBG: about to SEC_NAMES[1] DBG: strtab_add done DBG: content loop i=2 s=2 DBG: about to SEC_NAMES[2] DBG: strtab_add done DBG: content loop i=3 s=3 DBG: about to SEC_NAMES[3] DBG: strtab_add done DBG: content_secs loop 1 done DBG: write_elf32 entered DBG: n_content=3 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content loop i=1 s=1 DBG: about to SEC_NAMES[1] DBG: strtab_add done DBG: content loop i=2 s=3 DBG: about to SEC_NAMES[3] DBG: strtab_add done DBG: content_secs loop 1 done DBG: write_elf32 entered DBG: n_content=1 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content_secs loop 1 done DBG: write_elf32 entered DBG: n_content=3 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content loop i=1 s=2 DBG: about to SEC_NAMES[2] DBG: strtab_add done DBG: content loop i=2 s=3 DBG: about to SEC_NAMES[3] DBG: strtab_add done DBG: content_secs loop 1 done DBG: write_elf32 entered DBG: n_content=3 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content loop i=1 s=1 DBG: about to SEC_NAMES[1] DBG: strtab_add done DBG: content loop i=2 s=3 DBG: about to SEC_NAMES[3] DBG: strtab_add done DBG: content_secs loop 1 done DBG: write_elf32 entered DBG: n_content=1 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content_secs loop 1 done DBG: write_elf32 entered DBG: n_content=1 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content_secs loop 1 done DBG: write_elf32 entered DBG: n_content=3 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content loop i=1 s=1 DBG: about to SEC_NAMES[1] DBG: strtab_add done DBG: content loop i=2 s=3 DBG: about to SEC_NAMES[3] DBG: strtab_add done DBG: content_secs loop 1 done DBG: write_elf32 entered DBG: n_content=2 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content loop i=1 s=2 DBG: about to SEC_NAMES[2] DBG: strtab_add done DBG: content_secs loop 1 done DBG: write_elf32 entered DBG: n_content=1 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content_secs loop 1 done /bin/bash: line 23: 3739878 Segmentation fault (core dumped) timeout 3 ./build/stage2_dbg3/as -f elf32 -o /tmp/tmin5.o /tmp/tmin5.S > /tmp/elfw_trace.log 2>&1 exit=139 DBG: write_elf32 entered DBG: n_content=2 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content loop i=1 s=1 DBG: about to SEC_NAMES[1] DBG: strtab_add done DBG: content_secs loop 1 done
- for (int i = 0; i < n_content; i++) {
- int s = content_secs[i];
- if (reloc_for[s]) {
- char nm[MAX_NAME];
- snprintf(nm, sizeof(nm), ".rel%s", SEC_NAMES[s]);
- nsh++;
- name_rel[s] = strtab_add(&shstrtab, nm);
- }
- }
+ fprintf(stderr, "DBG: reloc-name loop starting\n");
+ for (int i = 0; i < n_content; i++) {
+ int s = content_secs[i];
+ fprintf(stderr, "DBG: reloc-name i=%d s=%d reloc_for[s]=%d\n", i, s, reloc_for[s]);
+ if (reloc_for[s]) {
+ char nm[MAX_NAME];
+ snprintf(nm, sizeof(nm), ".rel%s", SEC_NAMES[s]);
+ fprintf(stderr, "DBG: nm=%s\n", nm);
+ nsh++;
+ name_rel[s] = strtab_add(&shstrtab, nm);
+ fprintf(stderr, "DBG: reloc-name strtab_add done\n");
+ }
+ }
+ fprintf(stderr, "DBG: reloc-name loop done\n");The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/elf_writer.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 -20
rm -rf build/stage2_dbg4 && mkdir -p build/stage2_dbg4
for f in toolchain/as/*.c toolchain/hostlib/*.c; do
base=$(echo "$f" | tr '/' '_')
./build/toolchain/cc -Itoolchain/include --target=host -o "build/stage2_dbg4/${base%.c}.S" "$f"
./build/toolchain/as -f elf32 -o "build/stage2_dbg4/${base%.c}.o" "build/stage2_dbg4/${base%.c}.S"
done
./build/toolchain/ld -o build/stage2_dbg4/as $(ls build/stage2_dbg4/toolchain_as_*.o) $(ls build/stage2_dbg4/toolchain_hostlib_*.o)
chmod +x build/stage2_dbg4/as
timeout 3 ./build/stage2_dbg4/as -f elf32 -o /tmp/tmin5.o /tmp/tmin5.S > /tmp/elfw_trace2.log 2>&1
echo "exit=$?"
tail -20 /tmp/elfw_trace2.logDBG: write_elf32 entered DBG: n_content=2 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content loop i=1 s=3 DBG: about to SEC_NAMES[3] DBG: strtab_add done DBG: content_secs loop 1 done DBG: reloc-name loop starting DBG: reloc-name i=0 s=0 reloc_for[s]=1 DBG: nm=.rel.text DBG: reloc-name strtab_add done DBG: reloc-name i=1 s=3 reloc_for[s]=0 DBG: reloc-name loop done DBG: write_elf32 entered DBG: n_content=4 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content loop i=1 s=1 DBG: about to SEC_NAMES[1] DBG: strtab_add done DBG: content loop i=2 s=2 DBG: about to SEC_NAMES[2] DBG: strtab_add done DBG: content loop i=3 s=3 DBG: about to SEC_NAMES[3] DBG: strtab_add done DBG: content_secs loop 1 done DBG: reloc-name loop starting DBG: reloc-name i=0 s=0 reloc_for[s]=1 DBG: nm=.rel.text DBG: reloc-name strtab_add done DBG: reloc-name i=1 s=1 reloc_for[s]=1 DBG: nm=.rel.data DBG: reloc-name strtab_add done DBG: reloc-name i=2 s=2 reloc_for[s]=0 DBG: reloc-name i=3 s=3 reloc_for[s]=0 DBG: reloc-name loop done DBG: write_elf32 entered DBG: n_content=3 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content loop i=1 s=1 DBG: about to SEC_NAMES[1] DBG: strtab_add done DBG: content loop i=2 s=3 DBG: about to SEC_NAMES[3] DBG: strtab_add done DBG: content_secs loop 1 done DBG: reloc-name loop starting DBG: reloc-name i=0 s=0 reloc_for[s]=1 DBG: nm=.rel.text DBG: reloc-name strtab_add done DBG: reloc-name i=1 s=1 reloc_for[s]=1 DBG: nm=.rel.data DBG: reloc-name strtab_add done DBG: reloc-name i=2 s=3 reloc_for[s]=0 DBG: reloc-name loop done DBG: write_elf32 entered DBG: n_content=1 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content_secs loop 1 done DBG: reloc-name loop starting DBG: reloc-name i=0 s=0 reloc_for[s]=1 DBG: nm=.rel.text DBG: reloc-name strtab_add done DBG: reloc-name loop done DBG: write_elf32 entered DBG: n_content=3 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content loop i=1 s=2 DBG: about to SEC_NAMES[2] DBG: strtab_add done DBG: content loop i=2 s=3 DBG: about to SEC_NAMES[3] DBG: strtab_add done DBG: content_secs loop 1 done DBG: reloc-name loop starting DBG: reloc-name i=0 s=0 reloc_for[s]=1 DBG: nm=.rel.text DBG: reloc-name strtab_add done DBG: reloc-name i=1 s=2 reloc_for[s]=0 DBG: reloc-name i=2 s=3 reloc_for[s]=0 DBG: reloc-name loop done DBG: write_elf32 entered DBG: n_content=3 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content loop i=1 s=1 DBG: about to SEC_NAMES[1] DBG: strtab_add done DBG: content loop i=2 s=3 DBG: about to SEC_NAMES[3] DBG: strtab_add done DBG: content_secs loop 1 done DBG: reloc-name loop starting DBG: reloc-name i=0 s=0 reloc_for[s]=1 DBG: nm=.rel.text DBG: reloc-name strtab_add done DBG: reloc-name i=1 s=1 reloc_for[s]=1 DBG: nm=.rel.data DBG: reloc-name strtab_add done DBG: reloc-name i=2 s=3 reloc_for[s]=0 DBG: reloc-name loop done DBG: write_elf32 entered DBG: n_content=1 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content_secs loop 1 done DBG: reloc-name loop starting DBG: reloc-name i=0 s=0 reloc_for[s]=0 DBG: reloc-name loop done DBG: write_elf32 entered DBG: n_content=1 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content_secs loop 1 done DBG: reloc-name loop starting DBG: reloc-name i=0 s=0 reloc_for[s]=0 DBG: reloc-name loop done DBG: write_elf32 entered DBG: n_content=3 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content loop i=1 s=1 DBG: about to SEC_NAMES[1] DBG: strtab_add done DBG: content loop i=2 s=3 DBG: about to SEC_NAMES[3] DBG: strtab_add done DBG: content_secs loop 1 done DBG: reloc-name loop starting DBG: reloc-name i=0 s=0 reloc_for[s]=1 DBG: nm=.rel.text DBG: reloc-name strtab_add done DBG: reloc-name i=1 s=1 reloc_for[s]=0 DBG: reloc-name i=2 s=3 reloc_for[s]=0 DBG: reloc-name loop done DBG: write_elf32 entered DBG: n_content=2 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content loop i=1 s=2 DBG: about to SEC_NAMES[2] DBG: strtab_add done DBG: content_secs loop 1 done DBG: reloc-name loop starting DBG: reloc-name i=0 s=0 reloc_for[s]=1 DBG: nm=.rel.text DBG: reloc-name strtab_add done DBG: reloc-name i=1 s=2 reloc_for[s]=0 DBG: reloc-name loop done DBG: write_elf32 entered DBG: n_content=1 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content_secs loop 1 done DBG: reloc-name loop starting DBG: reloc-name i=0 s=0 reloc_for[s]=0 DBG: reloc-name loop done /bin/bash: line 23: 3742101 Segmentation fault (core dumped) timeout 3 ./build/stage2_dbg4/as -f elf32 -o /tmp/tmin5.o /tmp/tmin5.S > /tmp/elfw_trace2.log 2>&1 exit=139 DBG: write_elf32 entered DBG: n_content=2 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content loop i=1 s=1 DBG: about to SEC_NAMES[1] DBG: strtab_add done DBG: content_secs loop 1 done DBG: reloc-name loop starting DBG: reloc-name i=0 s=0 reloc_for[s]=1 DBG: nm=.rel.text DBG: reloc-name strtab_add done DBG: reloc-name i=1 s=1 reloc_for[s]=0 DBG: reloc-name loop done
- for (int pass = 0; pass < 2; pass++) { /* pass 0 = locals, pass 1 = globals */
- for (int i = 0; i < ctx->nsyms; i++) {
- symbol_t *s = &ctx->syms[i];
- if (s->is_equ) continue;
- if (!s->defined && !s->is_extern) continue;
- int is_glob = s->is_global || s->is_extern;
- if ((pass == 0 && is_glob) || (pass == 1 && !is_glob)) continue;
-
- Elf32_Sym sym = {0};
- sym.st_name = strtab_add(&strtab, s->name);
- sym.st_info = (unsigned char)elf32_st_info(is_glob ? STB_GLOBAL : STB_LOCAL, STT_NOTYPE);
- if (s->defined) {
- sym.st_shndx = (Elf32_Half)shidx_content[s->section];
- sym.st_value = s->offset;
- } else {
- sym.st_shndx = SHN_UNDEF;
- }
- sym_elf_index[i] = nsym;
- symtab[nsym++] = sym;
- }
- }
+ fprintf(stderr, "DBG: symtab loop starting nsyms=%d\n", ctx->nsyms);
+ for (int pass = 0; pass < 2; pass++) { /* pass 0 = locals, pass 1 = globals */
+ for (int i = 0; i < ctx->nsyms; i++) {
+ symbol_t *s = &ctx->syms[i];
+ fprintf(stderr, "DBG: symtab pass=%d i=%d name=%s\n", pass, i, s->name);
+ if (s->is_equ) continue;
+ if (!s->defined && !s->is_extern) continue;
+ int is_glob = s->is_global || s->is_extern;
+ if ((pass == 0 && is_glob) || (pass == 1 && !is_glob)) continue;
+
+ Elf32_Sym sym = {0};
+ sym.st_name = strtab_add(&strtab, s->name);
+ sym.st_info = (unsigned char)elf32_st_info(is_glob ? STB_GLOBAL : STB_LOCAL, STT_NOTYPE);
+ if (s->defined) {
+ fprintf(stderr, "DBG: defined, section=%d shidx_content[section]=%d\n", s->section, shidx_content[s->section]);
+ sym.st_shndx = (Elf32_Half)shidx_content[s->section];
+ sym.st_value = s->offset;
+ } else {
+ sym.st_shndx = SHN_UNDEF;
+ }
+ sym_elf_index[i] = nsym;
+ symtab[nsym++] = sym;
+ fprintf(stderr, "DBG: symtab entry done nsym=%d\n", nsym);
+ }
+ }
+ fprintf(stderr, "DBG: symtab loop done nsym=%d\n", nsym);The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/elf_writer.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 -20
rm -rf build/stage2_dbg5 && mkdir -p build/stage2_dbg5
for f in toolchain/as/*.c toolchain/hostlib/*.c; do
base=$(echo "$f" | tr '/' '_')
./build/toolchain/cc -Itoolchain/include --target=host -o "build/stage2_dbg5/${base%.c}.S" "$f"
./build/toolchain/as -f elf32 -o "build/stage2_dbg5/${base%.c}.o" "build/stage2_dbg5/${base%.c}.S"
done
./build/toolchain/ld -o build/stage2_dbg5/as $(ls build/stage2_dbg5/toolchain_as_*.o) $(ls build/stage2_dbg5/toolchain_hostlib_*.o)
chmod +x build/stage2_dbg5/as
timeout 3 ./build/stage2_dbg5/as -f elf32 -o /tmp/tmin5.o /tmp/tmin5.S > /tmp/elfw_trace3.log 2>&1
echo "exit=$?"
tail -15 /tmp/elfw_trace3.log<persisted-output> Output too large (346.3KB). Full output saved to: /home/martin/.claude/projects/-home-martin-workspace-prototypes-boot-to-claude/03468d1c-5b29-4e24-8f0d-2a7435d227ab/tool-results/bqbhgmo06.txt Preview (first 2KB): DBG: write_elf32 entered DBG: n_content=2 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content loop i=1 s=3 DBG: about to SEC_NAMES[3] DBG: strtab_add done DBG: content_secs loop 1 done DBG: reloc-name loop starting DBG: reloc-name i=0 s=0 reloc_for[s]=1 DBG: nm=.rel.text DBG: reloc-name strtab_add done DBG: reloc-name i=1 s=3 reloc_for[s]=0 DBG: reloc-name loop done DBG: symtab loop starting nsyms=19 DBG: symtab pass=0 i=0 name=fopen DBG: symtab pass=0 i=1 name=perror DBG: symtab pass=0 i=2 name=fwrite DBG: symtab pass=0 i=3 name=fclose DBG: symtab pass=0 i=4 name=g_stdout_file DBG: symtab pass=0 i=5 name=g_stderr_file DBG: symtab pass=0 i=6 name=safe_strcpy DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=2 DBG: symtab pass=0 i=7 name=safe_strcpy.L0 DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=3 DBG: symtab pass=0 i=8 name=safe_strcpy.L3 DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=4 DBG: symtab pass=0 i=9 name=safe_strcpy.L4 DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=5 DBG: symtab pass=0 i=10 name=safe_strcpy.L2 DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=6 DBG: symtab pass=0 i=11 name=safe_strcpy.L1 DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=7 DBG: symtab pass=0 i=12 name=write_bin DBG: symtab pass=0 i=13 name=write_bin.L5 DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=8 DBG: symtab pass=0 i=14 name=write_bin.L6 DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=9 DBG: symtab pass=0 i=15 name=write_bin.L7 DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=10 DBG: symtab pass=0 i=16 name=write_bin.L8 DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=11 DBG: symtab pass=0 i=17 name=__str_0 ... </persisted-output>
$ tail -40 "/home/martin/.claude/projects/-home-martin-workspace-prototypes-boot-to-claude/03468d1c-5b29-4e24-8f0d-2a7435d227ab/tool-results/bqbhgmo06.txt"
DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=40 DBG: symtab pass=1 i=25 name=strcpy.L19 DBG: symtab pass=1 i=26 name=strcpy.L20 DBG: symtab pass=1 i=27 name=strchr DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=41 DBG: symtab pass=1 i=28 name=strchr.L21 DBG: symtab pass=1 i=29 name=strchr.L23 DBG: symtab pass=1 i=30 name=strchr.L24 DBG: symtab pass=1 i=31 name=strchr.L22 DBG: symtab pass=1 i=32 name=strchr.L25 DBG: symtab pass=1 i=33 name=strchr.L26 DBG: symtab pass=1 i=34 name=strrchr DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=42 DBG: symtab pass=1 i=35 name=strrchr.L27 DBG: symtab pass=1 i=36 name=strrchr.L29 DBG: symtab pass=1 i=37 name=strrchr.L30 DBG: symtab pass=1 i=38 name=strrchr.L28 DBG: symtab pass=1 i=39 name=strrchr.L31 DBG: symtab pass=1 i=40 name=strrchr.L32 DBG: symtab loop done nsym=42 /bin/bash: line 23: 3742234 Segmentation fault (core dumped) timeout 3 ./build/stage2_dbg5/as -f elf32 -o /tmp/tmin5.o /tmp/tmin5.S > /tmp/elfw_trace3.log 2>&1 exit=139 DBG: reloc-name loop done DBG: symtab loop starting nsyms=3 DBG: symtab pass=0 i=0 name=main DBG: symtab pass=0 i=1 name=_start DBG: symtab pass=0 i=2 name=g_inodes DBG: defined, section=1 shidx_content[section]=2 DBG: symtab entry done nsym=2 DBG: symtab pass=1 i=0 name=main DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=3 DBG: symtab pass=1 i=1 name=_start DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=4 DBG: symtab pass=1 i=2 name=g_inodes DBG: symtab loop done nsym=4
- /* --- relocation table entries, grouped by target section --- */
- static Elf32_Rel rels[MAX_SECTIONS][MAX_RELOCS];
- int nrels[MAX_SECTIONS] = {0};
- for (int i = 0; i < ctx->nrelocs; i++) {
- reloc_t *r = &ctx->relocs[i];
- symbol_t *s = sym_find(ctx, r->sym);
- if (!s) { fprintf(stderr, "as: relocation against unknown symbol '%s'\n", r->sym); return -1; }
- int symidx = sym_elf_index[s - ctx->syms];
- int type = r->kind == RELOC_PC32 ? R_386_PC32 : R_386_32;
- int sec = r->section;
- rels[sec][nrels[sec]].r_offset = r->offset;
- rels[sec][nrels[sec]].r_info = elf32_r_info(symidx, type);
- nrels[sec]++;
- }
+ fprintf(stderr, "DBG: after symtab loop, first_global=%d\n", first_global);
+ /* --- relocation table entries, grouped by target section --- */
+ static Elf32_Rel rels[MAX_SECTIONS][MAX_RELOCS];
+ int nrels[MAX_SECTIONS] = {0};
+ fprintf(stderr, "DBG: rel loop starting nrelocs=%d\n", ctx->nrelocs);
+ for (int i = 0; i < ctx->nrelocs; i++) {
+ reloc_t *r = &ctx->relocs[i];
+ fprintf(stderr, "DBG: rel i=%d sym=%s\n", i, r->sym);
+ symbol_t *s = sym_find(ctx, r->sym);
+ if (!s) { fprintf(stderr, "as: relocation against unknown symbol '%s'\n", r->sym); return -1; }
+ fprintf(stderr, "DBG: found sym, computing symidx\n");
+ int symidx = sym_elf_index[s - ctx->syms];
+ fprintf(stderr, "DBG: symidx=%d\n", symidx);
+ int type = r->kind == RELOC_PC32 ? R_386_PC32 : R_386_32;
+ int sec = r->section;
+ fprintf(stderr, "DBG: sec=%d nrels[sec]=%d\n", sec, nrels[sec]);
+ rels[sec][nrels[sec]].r_offset = r->offset;
+ rels[sec][nrels[sec]].r_info = elf32_r_info(symidx, type);
+ nrels[sec]++;
+ fprintf(stderr, "DBG: rel i=%d done\n", i);
+ }
+ fprintf(stderr, "DBG: rel loop done\n");The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/elf_writer.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 -20
rm -rf build/stage2_dbg6 && mkdir -p build/stage2_dbg6
for f in toolchain/as/*.c toolchain/hostlib/*.c; do
base=$(echo "$f" | tr '/' '_')
./build/toolchain/cc -Itoolchain/include --target=host -o "build/stage2_dbg6/${base%.c}.S" "$f"
./build/toolchain/as -f elf32 -o "build/stage2_dbg6/${base%.c}.o" "build/stage2_dbg6/${base%.c}.S"
done
./build/toolchain/ld -o build/stage2_dbg6/as $(ls build/stage2_dbg6/toolchain_as_*.o) $(ls build/stage2_dbg6/toolchain_hostlib_*.o)
chmod +x build/stage2_dbg6/as
timeout 3 ./build/stage2_dbg6/as -f elf32 -o /tmp/tmin5.o /tmp/tmin5.S > /tmp/elfw_trace4.log 2>&1
echo "exit=$?"
tail -20 /tmp/elfw_trace4.log<persisted-output> Output too large (406.9KB). Full output saved to: /home/martin/.claude/projects/-home-martin-workspace-prototypes-boot-to-claude/03468d1c-5b29-4e24-8f0d-2a7435d227ab/tool-results/bg5ezhaku.txt Preview (first 2KB): DBG: write_elf32 entered DBG: n_content=2 DBG: content loop i=0 s=0 DBG: about to SEC_NAMES[0] DBG: strtab_add done DBG: content loop i=1 s=3 DBG: about to SEC_NAMES[3] DBG: strtab_add done DBG: content_secs loop 1 done DBG: reloc-name loop starting DBG: reloc-name i=0 s=0 reloc_for[s]=1 DBG: nm=.rel.text DBG: reloc-name strtab_add done DBG: reloc-name i=1 s=3 reloc_for[s]=0 DBG: reloc-name loop done DBG: symtab loop starting nsyms=19 DBG: symtab pass=0 i=0 name=fopen DBG: symtab pass=0 i=1 name=perror DBG: symtab pass=0 i=2 name=fwrite DBG: symtab pass=0 i=3 name=fclose DBG: symtab pass=0 i=4 name=g_stdout_file DBG: symtab pass=0 i=5 name=g_stderr_file DBG: symtab pass=0 i=6 name=safe_strcpy DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=2 DBG: symtab pass=0 i=7 name=safe_strcpy.L0 DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=3 DBG: symtab pass=0 i=8 name=safe_strcpy.L3 DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=4 DBG: symtab pass=0 i=9 name=safe_strcpy.L4 DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=5 DBG: symtab pass=0 i=10 name=safe_strcpy.L2 DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=6 DBG: symtab pass=0 i=11 name=safe_strcpy.L1 DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=7 DBG: symtab pass=0 i=12 name=write_bin DBG: symtab pass=0 i=13 name=write_bin.L5 DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=8 DBG: symtab pass=0 i=14 name=write_bin.L6 DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=9 DBG: symtab pass=0 i=15 name=write_bin.L7 DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=10 DBG: symtab pass=0 i=16 name=write_bin.L8 DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=11 DBG: symtab pass=0 i=17 name=__str_0 ... </persisted-output>
$ tail -25 "/home/martin/.claude/projects/-home-martin-workspace-prototypes-boot-to-claude/03468d1c-5b29-4e24-8f0d-2a7435d227ab/tool-results/bg5ezhaku.txt"
DBG: after symtab loop, first_global=34 DBG: rel loop starting nrelocs=0 DBG: rel loop done /bin/bash: line 23: 3742450 Segmentation fault (core dumped) timeout 3 ./build/stage2_dbg6/as -f elf32 -o /tmp/tmin5.o /tmp/tmin5.S > /tmp/elfw_trace4.log 2>&1 exit=139 DBG: reloc-name i=1 s=1 reloc_for[s]=0 DBG: reloc-name loop done DBG: symtab loop starting nsyms=3 DBG: symtab pass=0 i=0 name=main DBG: symtab pass=0 i=1 name=_start DBG: symtab pass=0 i=2 name=g_inodes DBG: defined, section=1 shidx_content[section]=2 DBG: symtab entry done nsym=2 DBG: symtab pass=1 i=0 name=main DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=3 DBG: symtab pass=1 i=1 name=_start DBG: defined, section=0 shidx_content[section]=1 DBG: symtab entry done nsym=4 DBG: symtab pass=1 i=2 name=g_inodes DBG: symtab loop done nsym=4 DBG: after symtab loop, first_global=2 DBG: rel loop starting nrelocs=1 DBG: rel i=0 sym=g_inodes DBG: found sym, computing symidx
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'static void codegen_binop' -A 40 toolchain/cc/codegen.c | head -50
160:static void codegen_binop(node_t *n) {
161- int a_is_ptr = n->a->type->kind == TY_PTR || n->a->type->kind == TY_ARRAY;
162- codegen_expr(n->b);
163- out(" push eax\n");
164- codegen_expr(n->a);
165- out(" pop ecx\n");
166- /* Pointer arithmetic: scale whichever side is the integer operand.
167- * eax=a, ecx=b at this point (per the evaluation order above). */
168- if ((n->op == TK_PLUS || n->op == TK_MINUS) && (n->type->kind == TY_PTR || n->type->kind == TY_ARRAY)) {
169- int sz = type_size(n->type->base);
170- if (a_is_ptr) {
171- if (sz > 1) out(" imul ecx, ecx, %d\n", sz);
172- } else {
173- /* eax=int, ecx=pointer - scale eax then swap via the stack
174- * (no xchg instruction in this ISA) so eax ends up holding
175- * the pointer and ecx the scaled offset, matching every
176- * other case's eax-is-left-operand convention below. */
177- if (sz > 1) out(" imul eax, eax, %d\n", sz);
178- out(" push eax\n mov eax, ecx\n pop ecx\n");
179- }
180- }
181-
182- switch (n->op) {
183- case TK_PLUS: out(" add eax, ecx\n"); return;
184- case TK_MINUS: out(" sub eax, ecx\n"); return;
185- case TK_STAR: out(" imul eax, ecx\n"); return;
186- case TK_SLASH: out(" cdq\n idiv ecx\n"); return;
187- case TK_PERCENT: out(" cdq\n idiv ecx\n mov eax, edx\n"); return;
188- case TK_AMP: out(" and eax, ecx\n"); return;
189- case TK_PIPE: out(" or eax, ecx\n"); return;
190- case TK_CARET: out(" xor eax, ecx\n"); return;
191- /* eax=value, ecx=count already, from the evaluation order above -
192- * the shift-count-in-CL hardware requirement is satisfied
193- * directly, no register shuffling needed. */
194- case TK_SHL: out(" shl eax, cl\n"); return;
195- case TK_SHR: out(" shr eax, cl\n"); return;
196- case TK_EQ: out(" cmp eax, ecx\n sete al\n movzx eax, al\n"); return;
197- case TK_NEQ: out(" cmp eax, ecx\n setne al\n movzx eax, al\n"); return;
198- case TK_LT: out(" cmp eax, ecx\n setl al\n movzx eax, al\n"); return;
199- case TK_LE: out(" cmp eax, ecx\n setle al\n movzx eax, al\n"); return;
200- case TK_GT: out(" cmp eax, ecx\n setg al\n movzx eax, al\n"); return;$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'static node_t \*make_binop' -A 10 toolchain/cc/parser.c
130:static node_t *make_binop(tok_kind_t op, node_t *a, node_t *b); /* for va_start/va_arg desugaring */
131-static type_t *parse_array_dims(parser_t *p, type_t *base); /* for multi-dim array param decay */
132-
133-/* Array bounds and enum values in this codebase are often macro
134- * constants that expand to a small arithmetic expression (e.g.
135- * `TINYFS_INODE_BLOCK` -> `(1 + 1)`), not always a single literal
136- * token, so bounds need real constant folding rather than reading one
137- * token's ->num. */
138-static int64_t eval_const_node(parser_t *p, node_t *n) {
139- switch (n->kind) {
140- case ND_NUM: return n->num;
--
776:static node_t *make_binop(tok_kind_t op, node_t *a, node_t *b) {
777- node_t *n = node_new(ND_BINOP);
778- n->op = op;
779- n->a = a;
780- n->b = b;
781- type_t *at = decay(a->type), *bt = decay(b->type);
782- if ((op == TK_PLUS || op == TK_MINUS) && is_ptr(at)) n->type = at;
783- else if (op == TK_PLUS && is_ptr(bt)) n->type = bt;
784- else n->type = g_int_type;
785- return n;
786-}- static node_t *make_binop(tok_kind_t op, node_t *a, node_t *b) {
- node_t *n = node_new(ND_BINOP);
- n->op = op;
- n->a = a;
- n->b = b;
- type_t *at = decay(a->type), *bt = decay(b->type);
- if ((op == TK_PLUS || op == TK_MINUS) && is_ptr(at)) n->type = at;
- else if (op == TK_PLUS && is_ptr(bt)) n->type = bt;
- else n->type = g_int_type;
- return n;
- }
+ static node_t *make_binop(tok_kind_t op, node_t *a, node_t *b) {
+ node_t *n = node_new(ND_BINOP);
+ n->op = op;
+ n->a = a;
+ n->b = b;
+ type_t *at = decay(a->type), *bt = decay(b->type);
+ /* `ptr - ptr` (e.g. toolchain/as/elf_writer.c's `s - ctx->syms`, an
+ * array-position computation - Phase T8's self-hosting build is
+ * what first exercised this) yields ptrdiff_t, an *integer* element
+ * count, not another pointer - must be checked before the general
+ * "either side is a pointer" cases below, which would otherwise
+ * mislabel it as pointer-typed and make codegen_binop's scaling
+ * logic multiply an address by the element size instead of dividing
+ * the byte difference by it (see docs/toolchain.md). */
+ if (op == TK_MINUS && is_ptr(at) && is_ptr(bt)) n->type = g_int_type;
+ else if ((op == TK_PLUS || op == TK_MINUS) && is_ptr(at)) n->type = at;
+ else if (op == TK_PLUS && is_ptr(bt)) n->type = bt;
+ else n->type = g_int_type;
+ 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)
160 static void codegen_binop(node_t *n) {
161 int a_is_ptr = n->a->type->kind == TY_PTR || n->a->type->kind == TY_ARRAY;
162 codegen_expr(n->b);
163 out(" push eax\n");
164 codegen_expr(n->a);
165 out(" pop ecx\n");
166 /* Pointer arithmetic: scale whichever side is the integer operand.
167 * eax=a, ecx=b at this point (per the evaluation order above). */
168 if ((n->op == TK_PLUS || n->op == TK_MINUS) && (n->type->kind == TY_PTR || n->type->kind == TY_ARRAY)) {
169 int sz = type_size(n->type->base);
170 if (a_is_ptr) {
171 if (sz > 1) out(" imul ecx, ecx, %d\n", sz);
172 } else {
173 /* eax=int, ecx=pointer - scale eax then swap via the stack
174 * (no xchg instruction in this ISA) so eax ends up holding
175 * the pointer and ecx the scaled offset, matching every
176 * other case's eax-is-left-operand convention below. */
177 if (sz > 1) out(" imul eax, eax, %d\n", sz);
178 out(" push eax\n mov eax, ecx\n pop ecx\n");
179 }
180 }
181 - static void codegen_binop(node_t *n) {
- int a_is_ptr = n->a->type->kind == TY_PTR || n->a->type->kind == TY_ARRAY;
- codegen_expr(n->b);
- out(" push eax\n");
- codegen_expr(n->a);
- out(" pop ecx\n");
- /* Pointer arithmetic: scale whichever side is the integer operand.
- * eax=a, ecx=b at this point (per the evaluation order above). */
- if ((n->op == TK_PLUS || n->op == TK_MINUS) && (n->type->kind == TY_PTR || n->type->kind == TY_ARRAY)) {
+ static void codegen_binop(node_t *n) {
+ int a_is_ptr = n->a->type->kind == TY_PTR || n->a->type->kind == TY_ARRAY;
+ int b_is_ptr = n->b->type->kind == TY_PTR || n->b->type->kind == TY_ARRAY;
+ codegen_expr(n->b);
+ out(" push eax\n");
+ codegen_expr(n->a);
+ out(" pop ecx\n");
+ /* ptr - ptr (e.g. elf_writer.c's `s - ctx->syms`, an array-position
+ * computation): the *byte* difference divided by the element size,
+ * per make_binop's comment - eax=a, ecx=b already (see the
+ * evaluation order above). Must come before the general pointer-
+ * arithmetic scaling below: n->type is plain int here (make_binop
+ * never labels this result as a pointer), so that code wouldn't
+ * fire anyway, but this makes the actual division explicit rather
+ * than silently falling through to a plain integer subtraction. */
+ if (n->op == TK_MINUS && a_is_ptr && b_is_ptr) {
+ int sz = type_size(n->a->type->base);
+ out(" sub eax, ecx\n");
+ if (sz > 1) out(" cdq\n mov ecx, %d\n idiv ecx\n", sz);
+ return;
+ }
+ /* Pointer arithmetic: scale whichever side is the integer operand.
+ * eax=a, ecx=b at this point (per the evaluation order above). */
+ if ((n->op == TK_PLUS || n->op == TK_MINUS) && (n->type->kind == TY_PTR || n->type->kind == TY_ARRAY)) {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 git diff toolchain/as/elf_writer.c | head -5 git checkout -- toolchain/as/elf_writer.c grep -c 'elf32_st_info\|memset(&symtab\[nsym\]' toolchain/as/elf_writer.c
diff --git a/toolchain/as/elf_writer.c b/toolchain/as/elf_writer.c
index 8401932..97eca24 100644
--- a/toolchain/as/elf_writer.c
+++ b/toolchain/as/elf_writer.c
@@ -24,12 +24,14 @@ static uint32_t strtab_add(strtab_builder_t *st, const char *s) {
0$ cd /home/martin/workspace/prototypes/boot-to-claude sed -i 's/ELF32_ST_INFO(/elf32_st_info(/g; s/ELF32_R_INFO(/elf32_r_info(/g' toolchain/as/elf_writer.c grep -n 'elf32_st_info\|elf32_r_info' toolchain/as/elf_writer.c
94: sym.st_info = (unsigned char)elf32_st_info(is_glob ? STB_GLOBAL : STB_LOCAL, STT_NOTYPE); 121: rels[sec][nrels[sec]].r_info = elf32_r_info(symidx, type);
- symtab[nsym++] = (Elf32_Sym){0}; /* index 0: null symbol */
+ /* Not a compound literal (CC doesn't support them - see
+ * docs/toolchain.md, Phase T3/T8): symtab is `static`, but a prior
+ * call in the same process could have left index 0 non-zero, so
+ * this can't just rely on static zero-init either. */
+ memset(&symtab[nsym], 0, sizeof(symtab[nsym]));
+ nsym++; /* index 0: null symbol */The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/as/elf_writer.c has been updated successfully. (note: the file had been modified on disk since you last read it — the edit applied cleanly, but the file contains other changes not in your context. Read it before edits that depend on surrounding content.)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'DBG' toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c
(Bash completed with no output)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'sec == SEC_BSS' toolchain/as/encode.c echo "---" git diff --stat toolchain/
746: uint8_t *dst = (sec == SEC_BSS) ? NULL : ctx->sec_bytes[sec] + st->offset; --- toolchain/as/elf_format.h | 11 +-- toolchain/as/elf_writer.c | 11 ++- toolchain/as/encode.c | 11 ++- toolchain/as/parser.c | 8 ++- toolchain/cc/builtins.c | 13 ++++ toolchain/cc/cc.h | 2 +- toolchain/cc/codegen.c | 66 +++++++++++++++++ toolchain/cc/lexer.c | 43 +++++++++-- toolchain/cc/parser.c | 174 ++++++++++++++++++++++++++++++++++++++------- toolchain/include/stdint.h | 10 +++ toolchain/ld/elf_format.h | 11 +-- toolchain/ld/elf_reader.c | 4 +- toolchain/ld/ld.h | 11 ++- toolchain/ld/link_flat.c | 8 ++- 14 files changed, 328 insertions(+), 55 deletions(-)
$ cd /home/martin/workspace/prototypes/boot-to-claude
rm -rf build/stage2_dbg* build/stage2
gcc -Wall -Wextra -O2 -o build/toolchain/cc toolchain/cc/*.c 2>&1 | tail -20
gcc -Wall -Wextra -O2 -o build/toolchain/as toolchain/as/*.c 2>&1 | tail -20
gcc -Wall -Wextra -O2 -o build/toolchain/ld toolchain/ld/*.c 2>&1 | tail -20
echo "stage1 rebuilt"
mkdir -p build/stage2
fail=0
for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c; do
base=$(echo "$f" | tr '/' '_')
./build/toolchain/cc -Itoolchain/include --target=host -o "build/stage2/${base%.c}.S" "$f" || { echo "CODEGEN FAIL: $f"; fail=1; }
done
for s in build/stage2/*.S; do
o="${s%.S}.o"
./build/toolchain/as -f elf32 -o "$o" "$s" || { echo "AS FAIL: $s"; fail=1; }
done
./build/toolchain/ld -o build/stage2/as $(ls build/stage2/toolchain_as_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
./build/toolchain/ld -o build/stage2/cc $(ls build/stage2/toolchain_cc_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
./build/toolchain/ld -o build/stage2/ld $(ls build/stage2/toolchain_ld_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
chmod +x build/stage2/as build/stage2/cc build/stage2/ld
echo "stage2 build fail=$fail"
timeout 3 ./build/stage2/as -f elf32 -o /tmp/tmin5.o /tmp/tmin5.S
echo "tmin5 exit=$?"stage1 rebuilt stage2 build fail=0 tmin5 exit=0
$ cd /home/martin/workspace/prototypes/boot-to-claude timeout 90 env CC_BIN=./build/stage2/cc AS_BIN=./build/stage2/as LD_BIN=./build/stage2/ld ./tools/run_toolchain_tests.sh 2>&1 | tail -30
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) ./tools/run_toolchain_tests.sh: line 50: 3742935 Segmentation fault (core dumped) "$BUILD/$name" PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) PASS t10_short (6) ./tools/run_toolchain_tests.sh: line 50: 3742971 Segmentation fault (core dumped) "$BUILD/$name" FAIL t11_array_size: expected 155, got 139 ./tools/run_toolchain_tests.sh: line 50: 3742981 Segmentation fault (core dumped) "$BUILD/$name" FAIL t12_local_array_size: expected 4, got 139 PASS t13_struct_copy (212) PASS t14_struct_return (44) PASS t15_escapes (158) run_toolchain_tests: FAIL
$ cd /home/martin/workspace/prototypes/boot-to-claude cat tests/toolchain/t11_array_size.c echo "---" ./build/stage2/cc -I toolchain/include --target=host -o /tmp/t11.S tests/toolchain/t11_array_size.c && \ ./build/stage2/as -f elf32 -o /tmp/t11.o /tmp/t11.S && \ ./build/stage2/ld -o /tmp/t11 /tmp/t11.o timeout 3 /tmp/t11 echo "exit=$?"
/* `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 */
}
---
/bin/bash: line 14: 3743012 Segmentation fault (core dumped) timeout 3 /tmp/t11
exit=139$ cd /home/martin/workspace/prototypes/boot-to-claude gdb -q -batch -ex run -ex "info registers" -ex "x/10i \$pc-20" --args /tmp/t11 2>&1 | tail -25 cat /tmp/t11.S
/tmp/t11: Permission denied.
No executable file specified.
Use the "file" or "exec-file" command.
The program has no registers now.
No registers.
BITS 32
section .text
global main
main:
push ebp
mov ebp, esp
sub esp, 8
lea eax, [ebp-4]
push eax
mov eax, 0
pop ecx
mov [ecx], eax
lea eax, [ebp-8]
push eax
mov eax, 0
pop ecx
mov [ecx], eax
.L0:
mov eax, 1
push eax
mov eax, 5
pop ecx
cdq
idiv ecx
push eax
lea eax, [ebp-8]
mov eax, [eax]
pop ecx
cmp eax, ecx
setl al
movzx eax, al
cmp eax, 0
je .L1
lea eax, [ebp-4]
push eax
lea eax, [ebp-8]
mov eax, [eax]
push eax
lea eax, [table]
pop ecx
add eax, ecx
movzx eax, byte [eax]
push eax
pop ecx
mov edx, [esp]
mov eax, [edx]
add eax, ecx
pop ecx
mov [ecx], eax
.L2:
lea eax, [ebp-8]
mov ecx, eax
mov eax, [ecx]
mov edx, eax
add eax, 1
mov [ecx], eax
mov eax, edx
jmp .L0
.L1:
mov eax, 1
push eax
mov eax, 5
pop ecx
cdq
idiv ecx
push eax
lea eax, [ebp-4]
mov eax, [eax]
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
section .data
table:
db 10
db 20
db 30
db 40
db 50$ cd /home/martin/workspace/prototypes/boot-to-claude ls -la /tmp/t11 chmod +x /tmp/t11 timeout 3 /tmp/t11 echo "exit=$? (expect 155)"
--wxrw--wt 1 martin martin 305 Jul 19 05:24 /tmp/t11 /bin/bash: line 8: 3743050 Segmentation fault (core dumped) timeout 3 /tmp/t11 exit=139 (expect 155)
$ printf "decimal 755 = octal %o\n" 755 stat -c '%a' /tmp/t11 2>/dev/null || stat -f '%Lp' /tmp/t11
decimal 755 = octal 1363 1373
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n '0755\|chmod' toolchain/ld/link_static_exec.c
90: chmod(path, 0755);
85 while (cur < data_off) { fputc(0, f); cur++; }
86 fwrite(ctx->merged_data[SEC_DATA], 1, ctx->merged_size[SEC_DATA], f);
87 /* .bss: no file content - covered by p_memsz > p_filesz, zero-filled by the loader */
88
89 fclose(f);
90 chmod(path, 0755);
91 return 0;
92 }
93 - fclose(f); - chmod(path, 0755); - return 0; + fclose(f); + /* 0755 octal == 493 decimal - written in decimal since CC's lexer + * has no octal-literal support (see lexer.c's numeric-literal + * reader): a leading-zero literal like `0755` is silently read as + * plain decimal 755 (== octal 1363, a nonsense rwx pattern, mostly + * missing owner-read - see docs/toolchain.md, Phase T8). */ + chmod(path, 493); + return 0;
The file /home/martin/workspace/prototypes/boot-to-claude/toolchain/ld/link_static_exec.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/ld toolchain/ld/*.c 2>&1 | tail -20
rm -rf build/stage2 && mkdir -p build/stage2
fail=0
for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c; do
base=$(echo "$f" | tr '/' '_')
./build/toolchain/cc -Itoolchain/include --target=host -o "build/stage2/${base%.c}.S" "$f" || { echo "CODEGEN FAIL: $f"; fail=1; }
done
for s in build/stage2/*.S; do
o="${s%.S}.o"
./build/toolchain/as -f elf32 -o "$o" "$s" || { echo "AS FAIL: $s"; fail=1; }
done
./build/toolchain/ld -o build/stage2/as $(ls build/stage2/toolchain_as_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
./build/toolchain/ld -o build/stage2/cc $(ls build/stage2/toolchain_cc_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
./build/toolchain/ld -o build/stage2/ld $(ls build/stage2/toolchain_ld_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
chmod +x build/stage2/as build/stage2/cc build/stage2/ld
echo "stage2 build fail=$fail"
timeout 90 env CC_BIN=./build/stage2/cc AS_BIN=./build/stage2/as LD_BIN=./build/stage2/ld ./tools/run_toolchain_tests.sh 2>&1 | tail -25stage2 build fail=0 PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) ./tools/run_toolchain_tests.sh: line 50: 3743375 Segmentation fault (core dumped) "$BUILD/$name" PASS t06_switch (139) PASS t07_comma (53) PASS t08_forward_struct (6) PASS t09_local_init (81) PASS t10_short (6) ./tools/run_toolchain_tests.sh: line 50: 3743409 Segmentation fault (core dumped) "$BUILD/$name" FAIL t11_array_size: expected 155, got 139 ./tools/run_toolchain_tests.sh: line 50: 3743439 Segmentation fault (core dumped) "$BUILD/$name" FAIL t12_local_array_size: expected 4, got 139 PASS t13_struct_copy (212) PASS t14_struct_return (44) PASS t15_escapes (158) run_toolchain_tests: FAIL
$ cd /home/martin/workspace/prototypes/boot-to-claude rm -f /tmp/t11 ./build/stage2/cc -I toolchain/include --target=host -o /tmp/t11.S tests/toolchain/t11_array_size.c && \ ./build/stage2/as -f elf32 -o /tmp/t11.o /tmp/t11.S && \ ./build/stage2/ld -o /tmp/t11 /tmp/t11.o ls -la /tmp/t11 timeout 3 /tmp/t11 echo "exit=$?" gdb -q -batch -ex run -ex "info registers" -ex "x/10i \$pc-20" --args /tmp/t11 2>&1 | tail -25
-rwxr-xr-x 1 martin martin 305 Jul 19 05:26 /tmp/t11 /bin/bash: line 15: 3743562 Segmentation fault (core dumped) timeout 3 /tmp/t11 exit=139 ecx 0xffffcae4 -13596 edx 0x0 0 ebx 0x0 0 esp 0xf5ffcac8 0xf5ffcac8 ebp 0xffffcad0 0xffffcad0 esi 0x0 0 edi 0x0 0 eip 0x804805f 0x804805f eflags 0x10202 [ IF RF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x0 0 0x804804b: add %al,(%edi) 0x804804d: add %al,(%eax) 0x804804f: add %al,(%eax) 0x8048051: adc %al,(%eax) 0x8048053: add %dl,-0x75(%ebp) 0x8048056: in (%dx),%al 0x8048057: sub $0xa000008,%esp 0x804805d: adc $0x1e,%al => 0x804805f: sub %dh,(%edx) 0x8048061: (bad)
$ cd /home/martin/workspace/prototypes/boot-to-claude objdump -d -M intel /tmp/t11.o 2>&1 | head -20
/tmp/t11.o: file format elf32-i386 Disassembly of section .text: 00000000 <main>: 0: 55 push ebp 1: 8b ec mov ebp,esp 3: 81 ec 08 00 00 0a sub esp,0xa000008 9: 14 1e adc al,0x1e b: 28 32 sub BYTE PTR [edx],dh d: ff (bad) e: ff 50 b8 call DWORD PTR [eax-0x48] 11: 00 00 add BYTE PTR [eax],al 13: 00 00 add BYTE PTR [eax],al 15: 59 pop ecx 16: 89 01 mov DWORD PTR [ecx],eax 18: 8d 85 f8 ff ff ff lea eax,[ebp-0x8] 1e: 50 push eax
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'alu_op_lookup\|OPND_IMM.*32-bit ALU\|generic 32-bit ALU' toolchain/as/encode.c | head -5 sed -n '340,420p' toolchain/as/encode.c
237: /* --- generic 32-bit ALU ops (add/or/and/sub/xor/cmp): reg<->reg,
return e.len;
}
if (a->kind == OPND_REG && b->kind == OPND_IMM && is_reg16(a->reg) && st->bits == 16) {
e8(&e, (uint8_t)(0xB8 + regnum(a->reg)));
int64_t v = b->has_sym ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + 1, strict) : b->disp;
e16(&e, (uint16_t)v);
return e.len;
}
if (a->kind == OPND_REG && b->kind == OPND_IMM && is_reg16(a->reg) && st->bits == 32) {
e8(&e, 0x66);
e8(&e, (uint8_t)(0xB8 + regnum(a->reg)));
int64_t v = b->has_sym ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + 2, strict) : b->disp;
e16(&e, (uint16_t)v);
return e.len;
}
if (a->kind == OPND_REG && b->kind == OPND_IMM && is_reg32(a->reg)) {
e8(&e, (uint8_t)(0xB8 + regnum(a->reg)));
int64_t v = b->has_sym
? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + 1, strict)
: b->disp;
e32(&e, (uint32_t)v);
return e.len;
}
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) {
e8(&e, 0x8B);
e8(&e, (uint8_t)(0x06 | (regnum(a->reg) << 3)));
e16(&e, (uint16_t)resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + (uint32_t)e.len, strict));
return e.len;
}
if (st->bits == 16 && a->kind == OPND_MEM && !a->reg && b->kind == OPND_REG && is_reg16(b->reg)) {
e8(&e, 0x89);
e8(&e, (uint8_t)(0x06 | (regnum(b->reg) << 3)));
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 (st->bits == 16 && a->kind == OPND_REG && is_reg32(a->reg) && b->kind == OPND_MEM && !b->reg) {
e8(&e, 0x66); e8(&e, 0x8B);
e8(&e, (uint8_t)(0x06 | (regnum(a->reg) << 3)));
e16(&e, (uint16_t)resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + (uint32_t)e.len, strict));
return e.len;
}
if (st->bits == 16 && a->kind == OPND_MEM && !a->reg && b->kind == OPND_REG && is_reg32(b->reg)) {
e8(&e, 0x66); e8(&e, 0x89);
e8(&e, (uint8_t)(0x06 | (regnum(b->reg) << 3)));
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 (st->bits == 16 && a->kind == OPND_MEM && !a->reg && b->kind == OPND_IMM && a->size_override == 2) {
e8(&e, 0xC7);
e8(&e, (uint8_t)0x06);
e16(&e, (uint16_t)resolve_abs(ctx, is_bin, a->sym, a->disp, st->section, st->offset + (uint32_t)e.len, strict));
int64_t v = b->has_sym ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + (uint32_t)e.len + 2, strict) : b->disp;
e16(&e, (uint16_t)v);
return e.len;
}
if (st->bits == 16 && a->kind == OPND_MEM && !a->reg && b->kind == OPND_IMM && a->size_override == 4) {237 /* --- generic 32-bit ALU ops (add/or/and/sub/xor/cmp): reg<->reg,
238 * reg<->mem(ebp/esp/label), reg<->imm32 - what CC's codegen emits.
239 * Gated on bits==32 so this can never shadow the 16-bit real-mode
240 * forms below (which have their own, narrower matches). */
241 {
242 int base, ext;
243 if (st->bits == 32 && alu_lookup(m, &base, &ext)) {
244 if (a->kind == OPND_REG && is_reg32(a->reg) && (b->kind == OPND_REG || b->kind == OPND_MEM)) {
245 e8(&e, (uint8_t)(base + 3)); /* r32, r/m32 */
246 encode_rm32(&e, ctx, is_bin, b, regnum(a->reg), st->section, st->offset, strict);
247 return e.len;
248 }
249 if ((a->kind == OPND_REG || a->kind == OPND_MEM) && b->kind == OPND_REG && is_reg32(b->reg)) {
250 e8(&e, (uint8_t)(base + 1)); /* r/m32, r32 */
251 encode_rm32(&e, ctx, is_bin, a, regnum(b->reg), st->section, st->offset, strict);
252 return e.len;
253 }
254 if ((a->kind == OPND_REG || a->kind == OPND_MEM) && b->kind == OPND_IMM) {
255 e8(&e, 0x81); /* r/m32, imm32 (always full-size - no imm8 sign-extend optimization) */
256 encode_rm32(&e, ctx, is_bin, a, ext, st->section, st->offset, strict);
257 int64_t v = b->has_sym
258 ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + (uint32_t)e.len, strict)
259 : b->disp;
260 e32(&e, (uint32_t)v);
261 return e.len;
262 }
263 }
264 }
265
266 if (strcmp(m, "xor") == 0 || strcmp(m, "or") == 0 || strcmp(m, "sub") == 0 || strcmp(m, "add") == 0) {
267 int ext = strcmp(m, "add") == 0 ? 0 : strcmp(m, "or") == 0 ? 1 : strcmp(m, "sub") == 0 ? 5 : 6; /* xor */
268 if (a->kind == OPND_REG && b->kind == OPND_REG && is_reg32(a->reg) && is_reg32(b->reg)) {
269 e8(&e, (uint8_t)(0x01 + ext * 8));
270 e8(&e, (uint8_t)(0xC0 | (regnum(b->reg) << 3) | regnum(a->reg)));
271 return e.len;
272 }
273 if (a->kind == OPND_REG && b->kind == OPND_REG && is_reg16(a->reg) && is_reg16(b->reg)) {
274 e8(&e, (uint8_t)(0x01 + ext * 8));
275 e8(&e, (uint8_t)(0xC0 | (regnum(b->reg) << 3) | regnum(a->reg)));
276 return e.len;
277 }
278 if (a->kind == OPND_REG && b->kind == OPND_REG && is_reg8(a->reg) && is_reg8(b->reg)) {
279 e8(&e, (uint8_t)(0x00 + ext * 8));
280 e8(&e, (uint8_t)(0xC0 | (regnum(b->reg) << 3) | regnum(a->reg)));
281 return e.len;
282 }
283 if (a->kind == OPND_REG && a->reg == R_AL && b->kind == OPND_IMM) {
284 e8(&e, (uint8_t)(0x04 + ext * 8));
285 e8(&e, (uint8_t)b->disp);
286 return e.len;
287 }
288 if (a->kind == OPND_REG && a->reg == R_EAX && b->kind == OPND_IMM && st->bits == 16) {
289 e8(&e, 0x66);
290 e8(&e, (uint8_t)(0x05 + ext * 8));
291 e32(&e, (uint32_t)b->disp);
292 return e.len;
293 }
294 if (st->bits == 16 && a->kind == OPND_MEM && !a->reg && b->kind == OPND_REG && is_reg32(b->reg)) {
295 e8(&e, 0x66);
296 e8(&e, (uint8_t)(0x01 + ext * 8));
297 e8(&e, (uint8_t)(0x06 | (regnum(b->reg) << 3)));
298 e16(&e, (uint16_t)resolve_abs(ctx, is_bin, a->sym, a->disp, st->section, st->offset + (uint32_t)e.len, strict));
299 return e.len;
300 }
301 if (st->bits == 16 && a->kind == OPND_MEM && !a->reg && b->kind == OPND_REG && is_reg16(b->reg)) {
302 e8(&e, (uint8_t)(0x01 + ext * 8));
303 e8(&e, (uint8_t)(0x06 | (regnum(b->reg) << 3)));
304 e16(&e, (uint16_t)resolve_abs(ctx, is_bin, a->sym, a->disp, st->section, st->offset + (uint32_t)e.len, strict));
305 return e.len;
306 }
307 }
308
309 if (strcmp(m, "cmp") == 0) {
310 if (a->kind == OPND_REG && is_reg16(a->reg) && b->kind == OPND_IMM) {
311 e8(&e, 0x81);
312 e8(&e, (uint8_t)(0xF8 | regnum(a->reg)));
313 e16(&e, (uint16_t)b->disp);
314 return e.len;
315 }
316 }
317
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);$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'static void encode_rm32' -A 15 toolchain/as/encode.c
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;$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'sub esp' /tmp/t11.S sed -n '3p' /tmp/t11.S | xxd | head -3
7: sub esp, 8 00000000: 676c 6f62 616c 206d 6169 6e0a global main.
$ cd /home/martin/workspace/prototypes/boot-to-claude sed -n '7p' /tmp/t11.S | xxd
00000000: 2020 2020 7375 6220 6573 702c 2038 0a sub esp, 8.
$ cd /home/martin/workspace/prototypes/boot-to-claude sed -n '1,70p' toolchain/as/lexer.c
#include "as.h"
#include <ctype.h>
#include <string.h>
void lexer_init(lexer_t *lx, const char *src) {
lx->src = src;
lx->pos = 0;
lx->len = strlen(src);
}
static int peekc(lexer_t *lx) {
return lx->pos < lx->len ? (unsigned char)lx->src[lx->pos] : -1;
}
static int peekc2(lexer_t *lx) {
return lx->pos + 1 < lx->len ? (unsigned char)lx->src[lx->pos + 1] : -1;
}
static int is_ident_start(int c) {
return isalpha(c) || c == '_' || c == '.';
}
static int is_ident_cont(int c) {
return isalnum(c) || c == '_';
}
static token_t make(tok_kind_t k) {
token_t t;
memset(&t, 0, sizeof(t));
t.kind = k;
return t;
}
static int64_t parse_number(const char *text, int len) {
if (len > 2 && text[0] == '0' && (text[1] == 'x' || text[1] == 'X')) {
int64_t v = 0;
for (int i = 2; i < len; i++) {
char c = text[i];
v *= 16;
if (c >= '0' && c <= '9') v += c - '0';
else if (c >= 'a' && c <= 'f') v += 10 + (c - 'a');
else if (c >= 'A' && c <= 'F') v += 10 + (c - 'A');
}
return v;
}
if (len > 1 && (text[len - 1] == 'b' || text[len - 1] == 'B')) {
int all01 = 1;
for (int i = 0; i < len - 1; i++) {
if (text[i] != '0' && text[i] != '1') { all01 = 0; break; }
}
if (all01) {
int64_t v = 0;
for (int i = 0; i < len - 1; i++) {
v = v * 2 + (text[i] - '0');
}
return v;
}
}
int64_t v = 0;
for (int i = 0; i < len; i++) {
v = v * 10 + (text[i] - '0');
}
return v;
}
token_t lexer_next(lexer_t *lx) {
for (;;) {
int c = peekc(lx);
if (c == ' ' || c == '\t' || c == '\r') { lx->pos++; continue; }
if (c == ';') {
while (peekc(lx) != -1 && peekc(lx) != '\n') lx->pos++;$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'TK_NUMBER\|\.number =' toolchain/as/lexer.c
96: token_t t = make(TK_NUMBER); 98: t.number = parse_number(lx->src + start, n);
254 if ((a->kind == OPND_REG || a->kind == OPND_MEM) && b->kind == OPND_IMM) {
255 e8(&e, 0x81); /* r/m32, imm32 (always full-size - no imm8 sign-extend optimization) */
256 encode_rm32(&e, ctx, is_bin, a, ext, st->section, st->offset, strict);
257 int64_t v = b->has_sym
258 ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + (uint32_t)e.len, strict)
259 : b->disp;
260 e32(&e, (uint32_t)v);
261 return e.len;
262 }
263 }- if ((a->kind == OPND_REG || a->kind == OPND_MEM) && b->kind == OPND_IMM) {
- e8(&e, 0x81); /* r/m32, imm32 (always full-size - no imm8 sign-extend optimization) */
- encode_rm32(&e, ctx, is_bin, a, ext, st->section, st->offset, strict);
- int64_t v = b->has_sym
- ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + (uint32_t)e.len, strict)
- : b->disp;
- e32(&e, (uint32_t)v);
- return e.len;
- }
+ if ((a->kind == OPND_REG || a->kind == OPND_MEM) && b->kind == OPND_IMM) {
+ e8(&e, 0x81); /* r/m32, imm32 (always full-size - no imm8 sign-extend optimization) */
+ encode_rm32(&e, ctx, is_bin, a, ext, st->section, st->offset, strict);
+ int64_t v = b->has_sym
+ ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + (uint32_t)e.len, strict)
+ : b->disp;
+ fprintf(stderr, "DBG: alu-imm mnem=%s b->disp=%lld v=%lld strict=%d out=%d\n",
+ m, (long long)b->disp, (long long)v, strict, e.out != 0);
+ e32(&e, (uint32_t)v);
+ 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 -20 ./build/toolchain/cc -Itoolchain/include --target=host -o build/stage2/toolchain_as_encode.S toolchain/as/encode.c ./build/toolchain/as -f elf32 -o build/stage2/toolchain_as_encode.o build/stage2/toolchain_as_encode.S ./build/toolchain/ld -o build/stage2/as $(ls build/stage2/toolchain_as_*.o) $(ls build/stage2/toolchain_hostlib_*.o) chmod +x build/stage2/as timeout 3 ./build/stage2/as -f elf32 -o /tmp/t11.o /tmp/t11.S > /tmp/alu_trace.log 2>&1 echo "exit=$?" grep -A1 -B1 'mnem=sub' /tmp/alu_trace.log | head -20
<persisted-output> Output too large (152.8KB). Full output saved to: /home/martin/.claude/projects/-home-martin-workspace-prototypes-boot-to-claude/03468d1c-5b29-4e24-8f0d-2a7435d227ab/tool-results/bqq5cr16s.txt Preview (first 2KB): DBG: alu-imm mnem=sub b->disp=4 v=4 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=0 v=0 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=0 v=0 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=0 v=0 strict=0 out=0 DBG: alu-imm mnem=add b->disp=1 v=1 strict=0 out=0 DBG: alu-imm mnem=sub b->disp=4 v=4 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=1 v=1 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=9 v=9 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=17 v=17 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=2 v=2 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=10 v=10 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=18 v=18 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=3 v=3 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=11 v=11 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=19 v=19 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=4 v=4 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=12 v=12 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=20 v=20 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=5 v=5 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=13 v=13 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=21 v=21 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=6 v=6 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=14 v=14 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=22 v=22 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=7 v=7 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=15 v=15 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=23 v=23 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=8 v=8 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=16 v=16 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=24 v=24 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=25 v=25 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=26 v=26 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=27 v=27 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=28 v=28 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=29 v=29 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=30 v=30 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=31 v=31 strict=0 out=0 DBG: alu-imm mnem=cmp b->disp=0 v=0 strict=0 out=0 ... </persisted-output>
$ grep -n 'mnem=sub' "/home/martin/.claude/projects/-home-martin-workspace-prototypes-boot-to-claude/03468d1c-5b29-4e24-8f0d-2a7435d227ab/tool-results/bqq5cr16s.txt" | tail -20
1:DBG: alu-imm mnem=sub b->disp=4 v=4 strict=0 out=0 6:DBG: alu-imm mnem=sub b->disp=4 v=4 strict=0 out=0 56:DBG: alu-imm mnem=sub b->disp=4 v=4 strict=0 out=0 69:DBG: alu-imm mnem=sub b->disp=4 v=4 strict=0 out=0 92:DBG: alu-imm mnem=sub b->disp=20 v=20 strict=0 out=0 121:DBG: alu-imm mnem=sub b->disp=8 v=8 strict=0 out=0 159:DBG: alu-imm mnem=sub b->disp=4 v=4 strict=0 out=0 166:DBG: alu-imm mnem=sub b->disp=180 v=180 strict=0 out=0 1490:DBG: alu-imm mnem=sub b->disp=20 v=20 strict=0 out=0 1510:DBG: alu-imm mnem=sub b->disp=4 v=4 strict=1 out=1 1515:DBG: alu-imm mnem=sub b->disp=4 v=4 strict=1 out=1 1565:DBG: alu-imm mnem=sub b->disp=4 v=4 strict=1 out=1 1578:DBG: alu-imm mnem=sub b->disp=4 v=4 strict=1 out=1 1601:DBG: alu-imm mnem=sub b->disp=20 v=20 strict=1 out=1 1630:DBG: alu-imm mnem=sub b->disp=8 v=8 strict=1 out=1 1668:DBG: alu-imm mnem=sub b->disp=4 v=4 strict=1 out=1 1675:DBG: alu-imm mnem=sub b->disp=180 v=180 strict=1 out=1 2999:DBG: alu-imm mnem=sub b->disp=20 v=20 strict=1 out=1 3020:DBG: alu-imm mnem=sub b->disp=8 v=8 strict=0 out=0 3023:DBG: alu-imm mnem=sub b->disp=8 v=8 strict=1 out=1
$ cd /home/martin/workspace/prototypes/boot-to-claude sed -n '38,46p' toolchain/as/encode.c
} emitter_t;
static void e8(emitter_t *e, uint8_t b) {
if (e->out) e->out[e->len] = b;
e->len++;
}
static void e16(emitter_t *e, uint16_t v) { e8(e, v & 0xFF); e8(e, (v >> 8) & 0xFF); }
static void e32(emitter_t *e, uint32_t v) {
e8(e, v & 0xFF); e8(e, (v >> 8) & 0xFF); e8(e, (v >> 16) & 0xFF); e8(e, (v >> 24) & 0xFF);- if ((a->kind == OPND_REG || a->kind == OPND_MEM) && b->kind == OPND_IMM) {
- e8(&e, 0x81); /* r/m32, imm32 (always full-size - no imm8 sign-extend optimization) */
- encode_rm32(&e, ctx, is_bin, a, ext, st->section, st->offset, strict);
- int64_t v = b->has_sym
- ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + (uint32_t)e.len, strict)
- : b->disp;
- fprintf(stderr, "DBG: alu-imm mnem=%s b->disp=%lld v=%lld strict=%d out=%d\n",
- m, (long long)b->disp, (long long)v, strict, e.out != 0);
- e32(&e, (uint32_t)v);
- return e.len;
- }
+ if ((a->kind == OPND_REG || a->kind == OPND_MEM) && b->kind == OPND_IMM) {
+ e8(&e, 0x81); /* r/m32, imm32 (always full-size - no imm8 sign-extend optimization) */
+ encode_rm32(&e, ctx, is_bin, a, ext, st->section, st->offset, strict);
+ int64_t v = b->has_sym
+ ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + (uint32_t)e.len, strict)
+ : b->disp;
+ if (strict && e.out && v == 8 && ext == 5) {
+ fprintf(stderr, "DBG: pre-e32 e.len=%d\n", e.len);
+ }
+ e32(&e, (uint32_t)v);
+ if (strict && e.out && v == 8 && ext == 5) {
+ fprintf(stderr, "DBG: post-e32 e.len=%d bytes=%d,%d,%d,%d,%d,%d\n", e.len,
+ e.out[0], e.out[1], e.out[2], e.out[3], e.out[4], e.out[5]);
+ }
+ 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 -20 ./build/toolchain/cc -Itoolchain/include --target=host -o build/stage2/toolchain_as_encode.S toolchain/as/encode.c ./build/toolchain/as -f elf32 -o build/stage2/toolchain_as_encode.o build/stage2/toolchain_as_encode.S ./build/toolchain/ld -o build/stage2/as $(ls build/stage2/toolchain_as_*.o) $(ls build/stage2/toolchain_hostlib_*.o) chmod +x build/stage2/as timeout 3 ./build/stage2/as -f elf32 -o /tmp/t11.o /tmp/t11.S > /tmp/e32_trace.log 2>&1 echo "exit=$?" grep -A1 'pre-e32\|post-e32' /tmp/e32_trace.log | tail -20
DBG: pre-e32 e.len=2 DBG: post-e32 e.len=6 bytes=129,236,8,0,0,0 exit=0 DBG: pre-e32 e.len=2 DBG: post-e32 e.len=6 bytes=129,236,8,0,0,0
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'static node_t \*parse_global_var' -A 40 toolchain/cc/parser.c | head -45
1074:static node_t *parse_global_var(parser_t *p, const char *name, type_t *t, int is_extern, int is_static) {
1075- add_global(p, name, t);
1076- node_t *n = node_new(ND_VAR_DECL);
1077- safe_strcpy(n->name, name, MAX_NAME);
1078- n->type = t;
1079- n->is_global = 1;
1080- n->is_extern_decl = is_extern;
1081- n->is_file_static = is_static;
1082- safe_strcpy(n->asm_label, name, MAX_NAME);
1083- if (match(p, TK_ASSIGN)) n->init = parse_initializer(p);
1084- /* `T name[] = { ... };` - array_len was left at -1 by parse_declarator
1085- * (no size between the brackets), and type_size() treats a non-
1086- * positive array_len as size 0 - silently making sizeof(name) come
1087- * out as 0 instead of the real, initializer-inferred length. Fix it
1088- * up now that the initializer list is available to count. */
1089- if (t->kind == TY_ARRAY && t->array_len < 0 && n->init && n->init->kind == ND_BLOCK) {
1090- int count = 0;
1091- for (node_t *v = n->init->list; v; v = v->next) count++;
1092- t->array_len = count;
1093- }
1094- return n;
1095-}
1096-
1097-static node_t *parse_func(parser_t *p, const char *name, type_t *functy,
1098- char param_names[][MAX_NAME], int n_params, int is_static) {
1099- sym_t *fs = add_global(p, name, functy);
1100-
1101- if (match(p, TK_SEMI)) return NULL; /* prototype only */
1102-
1103- p->locals = NULL;
1104- p->frame_size = 0;
1105- p->cur_func_ret = functy->base;
1106-
1107- node_t *n = node_new(ND_FUNC_DEF);
1108- safe_strcpy(n->name, name, MAX_NAME);
1109- n->type = functy->base;
1110- n->is_global = !is_static; /* controls whether codegen emits `global` - C's static limits linkage */
1111- safe_strcpy(n->asm_label, name, MAX_NAME);
1112-
1113- /* cdecl: EBP+8, +12, ... for params, in order - except when the
1114- * return type is a struct/union, which doesn't fit in EAX. That$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'static sym_t \*add_global' -A 10 toolchain/cc/parser.c
92:static sym_t *add_global(parser_t *p, const char *name, type_t *type) {
93- sym_t *s = calloc(1, sizeof(sym_t));
94- safe_strcpy(s->name, name, MAX_NAME);
95- s->type = type;
96- s->is_global = 1;
97- safe_strcpy(s->asm_label, name, MAX_NAME);
98- s->next = p->globals;
99- p->globals = s;
100- return s;
101-}
102-$ cd /home/martin/workspace/prototypes/boot-to-claude ./build/toolchain/cc -Itoolchain/include --target=host -o /tmp/t11_s1.S tests/toolchain/t11_array_size.c && \ ./build/toolchain/as -f elf32 -o /tmp/t11_s1.o /tmp/t11_s1.S && \ ./build/toolchain/ld -o /tmp/t11_s1 /tmp/t11_s1.o /tmp/t11_s1 echo "stage1 exit=$? (expect 155)"
DBG: pre-e32 e.len=2 DBG: post-e32 e.len=6 bytes=129,236,8,0,0,0 stage1 exit=155 (expect 155)
$ cd /home/martin/workspace/prototypes/boot-to-claude # Stage1 cc+as producing .o, then STAGE2 ld linking ./build/toolchain/ld --help 2>&1 | head -2 rm -f /tmp/t11_mix ./build/stage2/ld -o /tmp/t11_mix /tmp/t11_s1.o chmod +x /tmp/t11_mix /tmp/t11_mix echo "stage1cc+as, stage2ld exit=$? (expect 155)"
ld: unrecognized option '--help' stage1cc+as, stage2ld exit=155 (expect 155)
$ cd /home/martin/workspace/prototypes/boot-to-claude rm -f /tmp/t11_mix2.o /tmp/t11_mix2 ./build/stage2/as -f elf32 -o /tmp/t11_mix2.o /tmp/t11_s1.S ./build/toolchain/ld -o /tmp/t11_mix2 /tmp/t11_mix2.o chmod +x /tmp/t11_mix2 /tmp/t11_mix2 echo "stage1cc, stage2as, stage1ld exit=$? (expect 155)"
DBG: pre-e32 e.len=2 DBG: post-e32 e.len=6 bytes=129,236,8,0,0,0 /bin/bash: line 12: 3746108 Segmentation fault (core dumped) /tmp/t11_mix2 stage1cc, stage2as, stage1ld exit=139 (expect 155)
$ cd /home/martin/workspace/prototypes/boot-to-claude ./build/toolchain/as -f elf32 -o /tmp/t11_ref.o /tmp/t11_s1.S cmp /tmp/t11_ref.o /tmp/t11_mix2.o ls -la /tmp/t11_ref.o /tmp/t11_mix2.o cmp -l /tmp/t11_ref.o /tmp/t11_mix2.o | head -20
DBG: pre-e32 e.len=2 DBG: post-e32 e.len=6 bytes=129,236,8,0,0,0 /tmp/t11_ref.o /tmp/t11_mix2.o differ: byte 61, line 1 -rw-r----- 1 martin martin 764 Jul 19 05:30 /tmp/t11_mix2.o -rw-r----- 1 martin martin 764 Jul 19 05:30 /tmp/t11_ref.o 61 0 12 62 215 24 63 205 36 64 374 50 65 377 62
$ cd /home/martin/workspace/prototypes/boot-to-claude xxd -s 44 -l 40 /tmp/t11_ref.o echo "---" xxd -s 44 -l 40 /tmp/t11_mix2.o
0000002c: 0000 2800 0700 0600 558b ec81 ec08 0000 ..(.....U....... 0000003c: 008d 85fc ffff ff50 b800 0000 0059 8901 .......P.....Y.. 0000004c: 8d85 f8ff ffff 50b8 ......P. --- 0000002c: 0000 2800 0700 0600 558b ec81 ec08 0000 ..(.....U....... 0000003c: 0a14 1e28 32ff ff50 b800 0000 0059 8901 ...(2..P.....Y.. 0000004c: 8d85 f8ff ffff 50b8 ......P.
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'static member_t \*parse_struct_members' -A 40 toolchain/cc/parser.c | head -45
171:static member_t *parse_struct_members(parser_t *p);
172-
173-static int is_type_start(parser_t *p) {
174- if (!check(p, TK_IDENT)) return 0;
175- const char *kws[] = { "void", "char", "short", "int", "long", "signed", "unsigned",
176- "struct", "union", "enum", "const", "volatile", "static", "extern", NULL };
177- for (int i = 0; kws[i]; i++) if (strcmp(cur(p).text, kws[i]) == 0) return 1;
178- return is_typedef_name(p, cur(p).text);
179-}
180-
181-/* Parses base type specifiers + storage class; returns the type and sets
182- * is_static and is_extern. Does not consume any declarator. */
183-static type_t *parse_type_spec(parser_t *p, int *is_static, int *is_extern) {
184- int saw_void = 0, saw_char = 0, saw_short = 0, saw_int = 0, saw_long = 0, saw_unsigned = 0, saw_signed = 0;
185- type_t *named = NULL;
186- if (is_static) *is_static = 0;
187- if (is_extern) *is_extern = 0;
188-
189- for (;;) {
190- if (match_kw(p, "static")) { if (is_static) *is_static = 1; continue; }
191- if (match_kw(p, "extern")) { if (is_extern) *is_extern = 1; continue; }
192- if (match_kw(p, "const") || match_kw(p, "volatile") || match_kw(p, "inline")) continue;
193- if (match_kw(p, "void")) { saw_void = 1; continue; }
194- if (match_kw(p, "char")) { saw_char = 1; continue; }
195- if (match_kw(p, "short")) { saw_short = 1; continue; }
196- if (match_kw(p, "int")) { saw_int = 1; continue; }
197- /* long == int on this 32-bit target (both 4 bytes) - no `long
198- * long`/`unsigned long` anywhere in the codebase (see
199- * docs/toolchain.md), so a same-size alias is enough. */
200- if (match_kw(p, "long")) { saw_long = 1; continue; }
201- if (match_kw(p, "signed")) { saw_signed = 1; continue; }
202- if (match_kw(p, "unsigned")) { saw_unsigned = 1; continue; }
203- if (is_kw(p, "struct") || is_kw(p, "union")) {
204- int is_union = is_kw(p, "union");
205- advance(p);
206- char tagname[MAX_NAME] = {0};
207- if (check(p, TK_IDENT)) { safe_strcpy(tagname, cur(p).text, MAX_NAME); advance(p); }
208- type_t *st;
209- tag_t *existing = tagname[0] ? tag_find(p, tagname) : NULL;
210- if (check(p, TK_LBRACE)) {
211- advance(p);
--
327:static member_t *parse_struct_members(parser_t *p) {
328- member_t *head = NULL, *tail = NULL;
329- while (!check(p, TK_RBRACE)) {327 static member_t *parse_struct_members(parser_t *p) {
328 member_t *head = NULL, *tail = NULL;
329 while (!check(p, TK_RBRACE)) {
330 type_t *base = parse_type_spec(p, NULL, NULL);
331 for (;;) {
332 type_t *mt = base;
333 char name[MAX_NAME];
334 while (match(p, TK_STAR)) mt = ptr_to(mt);
335 token_t id = expect(p, TK_IDENT);
336 safe_strcpy(name, id.text, MAX_NAME);
337 while (match(p, TK_LBRACKET)) {
338 int n = -1;
339 if (!check(p, TK_RBRACKET)) n = (int)parse_const_expr(p);
340 expect(p, TK_RBRACKET);
341 type_t *arr = type_new(TY_ARRAY);
342 arr->base = mt;
343 arr->array_len = n;
344 mt = arr;
345 }
346 member_t *m = calloc(1, sizeof(member_t));
347 safe_strcpy(m->name, name, MAX_NAME);
348 m->type = mt;
349 if (tail) tail->next = m; else head = m;
350 tail = m;
351 if (!match(p, TK_COMMA)) break;
352 }
353 expect(p, TK_SEMI);
354 }
355 return head;
356 }- token_t id = expect(p, TK_IDENT);
- safe_strcpy(name, id.text, MAX_NAME);
- while (match(p, TK_LBRACKET)) {
- int n = -1;
- if (!check(p, TK_RBRACKET)) n = (int)parse_const_expr(p);
- expect(p, TK_RBRACKET);
- type_t *arr = type_new(TY_ARRAY);
- arr->base = mt;
- arr->array_len = n;
- mt = arr;
- }
- member_t *m = calloc(1, sizeof(member_t));
+ token_t id = expect(p, TK_IDENT);
+ safe_strcpy(name, id.text, MAX_NAME);
+ /* Same bug as parse_declarator originally had (see
+ * parse_array_dims's own comment, Phase T8): wrapping `mt`
+ * in a new array type on *every* bracket group nests them
+ * in the wrong order for 2+ dimensions - `T x[8][65536]`
+ * needs the *first* bracket as the outermost dimension, not
+ * the last. as.h's own `uint8_t sec_bytes[MAX_SECTIONS]
+ * [65536]` is exactly this: the inverted nesting still adds
+ * up to the same *total* size (multiplication commutes),
+ * so it silently miscompiled only the *indexing* -
+ * `sec_bytes[SEC_DATA]` landed 8 bytes into the buffer
+ * instead of 65536, inside SEC_TEXT's own byte range. */
+ if (check(p, TK_LBRACKET)) mt = parse_array_dims(p, mt);
+ member_t *m = calloc(1, sizeof(member_t));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 git diff toolchain/as/encode.c | grep -c '^\+.*DBG\|^\+.*fprintf.*pre-e32\|^\+.*fprintf.*post-e32'
Exit code 2
ugrep: error: error at position 5
(?m)^+.*DBG|^+.*fprintf.*pre-e32|^+.*fprintf.*post-e32
\___invalid syntax$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'pre-e32\|post-e32\|DBG' toolchain/as/encode.c
261: fprintf(stderr, "DBG: pre-e32 e.len=%d\n", e.len); 265: fprintf(stderr, "DBG: post-e32 e.len=%d bytes=%d,%d,%d,%d,%d,%d\n", e.len,
254 if ((a->kind == OPND_REG || a->kind == OPND_MEM) && b->kind == OPND_IMM) {
255 e8(&e, 0x81); /* r/m32, imm32 (always full-size - no imm8 sign-extend optimization) */
256 encode_rm32(&e, ctx, is_bin, a, ext, st->section, st->offset, strict);
257 int64_t v = b->has_sym
258 ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + (uint32_t)e.len, strict)
259 : b->disp;
260 if (strict && e.out && v == 8 && ext == 5) {
261 fprintf(stderr, "DBG: pre-e32 e.len=%d\n", e.len);
262 }
263 e32(&e, (uint32_t)v);
264 if (strict && e.out && v == 8 && ext == 5) {
265 fprintf(stderr, "DBG: post-e32 e.len=%d bytes=%d,%d,%d,%d,%d,%d\n", e.len,
266 e.out[0], e.out[1], e.out[2], e.out[3], e.out[4], e.out[5]);
267 }
268 return e.len;- int64_t v = b->has_sym
- ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + (uint32_t)e.len, strict)
- : b->disp;
- if (strict && e.out && v == 8 && ext == 5) {
- fprintf(stderr, "DBG: pre-e32 e.len=%d\n", e.len);
- }
- e32(&e, (uint32_t)v);
- if (strict && e.out && v == 8 && ext == 5) {
- fprintf(stderr, "DBG: post-e32 e.len=%d bytes=%d,%d,%d,%d,%d,%d\n", e.len,
- e.out[0], e.out[1], e.out[2], e.out[3], e.out[4], e.out[5]);
- }
- return e.len;
+ int64_t v = b->has_sym
+ ? resolve_abs(ctx, is_bin, b->sym, b->disp, st->section, st->offset + (uint32_t)e.len, strict)
+ : b->disp;
+ e32(&e, (uint32_t)v);
+ 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 'DBG' toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c echo "clean check done"
clean check done
$ cd /home/martin/workspace/prototypes/boot-to-claude
rm -rf build/stage2
gcc -Wall -Wextra -O2 -o build/toolchain/cc toolchain/cc/*.c 2>&1 | tail -20
gcc -Wall -Wextra -O2 -o build/toolchain/as toolchain/as/*.c 2>&1 | tail -20
gcc -Wall -Wextra -O2 -o build/toolchain/ld toolchain/ld/*.c 2>&1 | tail -20
echo "stage1 rebuilt"
mkdir -p build/stage2
fail=0
for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c; do
base=$(echo "$f" | tr '/' '_')
./build/toolchain/cc -Itoolchain/include --target=host -o "build/stage2/${base%.c}.S" "$f" || { echo "CODEGEN FAIL: $f"; fail=1; }
done
for s in build/stage2/*.S; do
o="${s%.S}.o"
./build/toolchain/as -f elf32 -o "$o" "$s" || { echo "AS FAIL: $s"; fail=1; }
done
./build/toolchain/ld -o build/stage2/as $(ls build/stage2/toolchain_as_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
./build/toolchain/ld -o build/stage2/cc $(ls build/stage2/toolchain_cc_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
./build/toolchain/ld -o build/stage2/ld $(ls build/stage2/toolchain_ld_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
chmod +x build/stage2/as build/stage2/cc build/stage2/ld
echo "stage2 build fail=$fail"
timeout 90 env CC_BIN=./build/stage2/cc AS_BIN=./build/stage2/as LD_BIN=./build/stage2/ld ./tools/run_toolchain_tests.sh 2>&1 | tail -25stage1 rebuilt stage2 build fail=0 PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) as: relocation against unknown symbol 'Z'
$ cd /home/martin/workspace/prototypes/boot-to-claude cat tests/toolchain/t06_switch.c | head -20 ./build/stage2/cc -I toolchain/include --target=host -o /tmp/t06.S tests/toolchain/t06_switch.c ./build/stage2/as -f elf32 -o /tmp/t06.o /tmp/t06.S echo "as exit=$?"
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;
}
as: relocation against unknown symbol 'Z'
as exit=1$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'Z\b' /tmp/t06.S | head -20
107: jmp Z
95 /* Resolves a PC-relative call/jmp target. Same-section same-file targets
96 * fold to a fixed relative displacement directly; anything else (extern,
97 * or -f bin's "always resolvable" absolute model) is handled by the
98 * caller via this return value or a relocation. `next_ip_rel` is the
99 * SECTION-RELATIVE position right after the relative field (i.e. NOT
100 * org-adjusted - this function adds org itself when is_bin, consistently
101 * with how it computes the target, since bin-mode "addresses" are
102 * `org + offset` throughout). */
103 static int64_t resolve_pcrel(asm_ctx_t *ctx, int is_bin, const char *sym, int64_t disp,
104 section_kind_t cur_sec, int64_t next_ip_rel,
105 section_kind_t patch_sec, uint32_t patch_off, int strict,
106 int field_bytes) {
107 if (!sym[0]) {
108 /* Bare constant target address, e.g. `jmp KERNEL_LOAD_ADDR`
109 * where KERNEL_LOAD_ADDR is an EQU - disp is already the
110 * absolute target, no symbol lookup needed. */
111 int64_t next_ip = is_bin ? (int64_t)ctx->org + next_ip_rel : next_ip_rel;
112 return disp - next_ip;
113 }
114 symbol_t *s = sym_find(ctx, sym);
115 if (is_bin) {
116 if (!s || !s->defined) {
117 if (strict) { fprintf(stderr, "as: undefined symbol '%s'\n", sym); exit(1); }
118 return 0;
119 }
120 int64_t target = (int64_t)ctx->org + (int64_t)s->offset + disp;
121 int64_t next_ip = (int64_t)ctx->org + next_ip_rel;
122 return target - next_ip;
123 }
124 if (s && s->defined && s->section == cur_sec) {
125 return ((int64_t)s->offset + disp) - next_ip_rel;
126 }
127 /* Same REL-vs-RELA reasoning as resolve_abs: the standard x86 addend
128 * for a rel32 call/jmp is -field_bytes (the CPU measures the
129 * displacement from the end of the instruction), and that addend
130 * must be baked into the bytes, not left as 0. */
131 int32_t addend = (int32_t)(disp - field_bytes);
132 if (strict) {
133 add_reloc(ctx, patch_sec, patch_off, sym, addend, RELOC_PC32);
134 }
135 return addend;
136 }
137
138 /* --- generic 32-bit ModRM(+SIB+disp) encoder ---
139 * Used by CC-generated code (always 32-bit: kernel and host targets are
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) {
195 static const struct { const char *name; int base, ext; } table[] = {
196 { "add", 0x00, 0 }, { "or", 0x08, 1 }, { "and", 0x20, 4 },
197 { "sub", 0x28, 5 }, { "xor", 0x30, 6 }, { "cmp", 0x38, 7 },
198 };
199 for (size_t i = 0; i < sizeof(table) / sizeof(table[0]); i++) {
200 if (strcmp(m, table[i].name) == 0) { *base = table[i].base; *ext = table[i].ext; return 1; }
201 }
202 return 0;
203 }
204
205 /* --- the encoder --- */
206
207 int stmt_encode(asm_ctx_t *ctx, stmt_t *st, uint8_t *out, int strict) {
208 emitter_t e = { out, 0 };
209 const char *m = st->mnemonic;$ cd /home/martin/workspace/prototypes/boot-to-claude sed -n '95,115p' /tmp/t06.S
movzx eax, al
cmp eax, 0
je .L7
lea eax, [ebp-8]
mov eax, [eax]
mov [ebp-12], eax
mov eax, [ebp-12]
cmp eax, 2
je .L10
mov eax, [ebp-12]
cmp eax, 4
je .L11
jmp Z
.L10:
jmp .L8
.L11:
jmp .L9
.L9:
lea eax, [ebp-4]
push eax
lea eax, [ebp-8]$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n 'ND_SWITCH\|default_label' toolchain/cc/codegen.c | head -10
546: case ND_SWITCH: {
558: char default_label[MAX_NAME] = "";
561: if (s->kind == ND_DEFAULT) safe_strcpy(default_label, s->asm_label, MAX_NAME);
569: out(" jmp %s\n", default_label[0] ? default_label : lend);596 static void emit_data_bytes(node_t *init, type_t *t);
597
598 static void emit_scalar_bytes(node_t *v, type_t *t) {
599 /* A string-literal initializer for a pointer-typed field (e.g.
600 * toolchain/as/parser.c's `static const regdef_t REGS[] =
601 * {{"al",R_AL,1}, ...}` - Phase T8's self-hosting build is what
602 * first exercised a string literal inside a struct-array global
603 * initializer) needs the string's own label, not v->num - an ND_STR
604 * node never sets ->num at all, so this silently emitted `dd 0`
605 * (a NULL pointer) instead of a relocation to the string's bytes. */
606 if (v->kind == ND_STR) { out(" dd %s\n", v->asm_label); return; }
607 if (t->kind == TY_CHAR) out(" db %lld\n", (long long)v->num);
608 else out(" dd %lld\n", (long long)v->num);
609 }
610
611 static void emit_data_bytes(node_t *init, type_t *t) {
612 if (!init) {
613 out(" resb %d\n", type_size(t));
614 return;
615 }
616 if (t->kind == TY_ARRAY && init->kind == ND_BLOCK) {
617 int count = 0;
618 for (node_t *v = init->list; v; v = v->next) { emit_data_bytes(v, t->base); count++; }
619 int pad = t->array_len - count;
620 for (int i = 0; i < pad; i++) out(" resb %d\n", type_size(t->base));
621 return;
622 }
623 if ((t->kind == TY_STRUCT || t->kind == TY_UNION) && init->kind == ND_BLOCK) {
624 /* Positional: walk declared members alongside the initializer
625 * list (no designated-initializer support - see
626 * docs/toolchain.md). A union brace-init like value_t's
627 * `{ VAL_INT, { 0 } }` naturally works here too: its one element
628 * targets the first member at offset 0, and the trailing-padding
629 * check below fills out the rest of the union's size. */
630 member_t *m = t->members;- 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) {
+ static void emit_data_bytes(node_t *init, type_t *t) {
+ if (!init) {
+ out(" resb %d\n", type_size(t));
+ return;
+ }
+ /* `char x[N] = "literal"` (no braces - distinct from `char x[] =
+ * {'a','b',0}`): real C semantics copy the string's own bytes
+ * (including its NUL) into x's storage, zero-padding any remaining
+ * space - not store a pointer, which is what falling through to
+ * emit_scalar_bytes's ND_STR case (a few lines down, meant for
+ * pointer-typed fields like a struct's `const char *name`) would do
+ * instead. First hit by AS's own `char default_label[MAX_NAME] =
+ * "";` (Phase T8's self-hosting build). */
+ if (t->kind == TY_ARRAY && init->kind == ND_STR) {
+ int n = (int)strlen(init->str) + 1;
+ for (int i = 0; i < n; i++) out(" db %d\n", (unsigned char)init->str[i]);
+ int pad = type_size(t) - n;
+ if (pad > 0) out(" resb %d\n", pad);
+ return;
+ }
+ if (t->kind == TY_ARRAY && init->kind == ND_BLOCK) {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 void codegen_init_local' -A 20 toolchain/cc/codegen.c | head -25
41:static void codegen_init_local(int base_offset, type_t *t, node_t *init);
42-static void codegen_call_or_builtin(node_t *n);
43-
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 (t->kind == TY_STRUCT || t->kind == TY_UNION) return; /* same - see store_value's struct-copy branch */
53- if (is_byte_type(t)) {
54- out(" movzx eax, byte [eax]\n");
55- } else if (is_short_type(t)) {
56- /* mov ax, [eax] reads through the same register that holds the
57- * address (ax is eax's low 16 bits) - fine, the CPU reads the
58- * memory operand before writing the result back. movzx then
59- * clears the high 16 bits, which the mov left as whatever
60- * garbage eax's upper half held before. */
61- out(" mov ax, [eax]\n movzx eax, ax\n");
--
684:static void codegen_init_local(int base_offset, type_t *t, node_t *init) {
685- /* A short brace-init list (fewer values than the aggregate has
686- * elements/members - e.g. `line_t ln = {0};` for a struct with three684 static void codegen_init_local(int base_offset, type_t *t, node_t *init) {
685 /* A short brace-init list (fewer values than the aggregate has
686 * elements/members - e.g. `line_t ln = {0};` for a struct with three
687 * members, one of them a whole array) leaves every uncovered byte at
688 * whatever garbage was already on the stack, not zero - the same gap
689 * emit_data_bytes already closes for *global* aggregates via a
690 * trailing `resb`, but nothing did for a local one until now (see
691 * docs/toolchain.md, Phase T8: this exact gap, on this exact
692 * pattern, sent as/parser.c's own line-tokenizing loop into an
693 * infinite loop reading uninitialized bounds/EOF-check fields). Zero
694 * the whole range up front (redundant on nested recursive calls, but
695 * harmless and simplest) so every element/member not explicitly
696 * listed below is correctly zero rather than garbage. */
697 if ((t->kind == TY_ARRAY || t->kind == TY_STRUCT || t->kind == TY_UNION) && init->kind == ND_BLOCK) {
698 codegen_zero_range(base_offset, type_size(t));
699 }
700 if (t->kind == TY_ARRAY && init->kind == ND_BLOCK) {
701 int off = 0;
702 for (node_t *v = init->list; v; v = v->next) {
703 codegen_init_local(base_offset + off, t->base, v);
704 off += type_size(t->base);
705 }
706 return;
707 }
708 if ((t->kind == TY_STRUCT || t->kind == TY_UNION) && init->kind == ND_BLOCK) {
709 member_t *m = t->members;
710 node_t *v = init->list;
711 for (; m && v; m = m->next, v = v->next) {
712 codegen_init_local(base_offset + m->offset, m->type, v);
713 }- if ((t->kind == TY_ARRAY || t->kind == TY_STRUCT || t->kind == TY_UNION) && init->kind == ND_BLOCK) {
- codegen_zero_range(base_offset, type_size(t));
- }
- if (t->kind == TY_ARRAY && init->kind == ND_BLOCK) {
+ if ((t->kind == TY_ARRAY || t->kind == TY_STRUCT || t->kind == TY_UNION) && init->kind == ND_BLOCK) {
+ codegen_zero_range(base_offset, type_size(t));
+ }
+ /* `char x[N] = "literal"` (no braces): same real-C-semantics gap as
+ * emit_data_bytes's own ND_STR branch (see its comment) - falling
+ * through to the plain-scalar case below would treat this as a
+ * pointer store (the string's *address*, via codegen_expr's ND_STR
+ * case) instead of copying the string's bytes into x's own stack
+ * storage. First hit by codegen.c's own `char default_label[MAX_NAME]
+ * = "";` inside its ND_SWITCH case (Phase T8's self-hosting build):
+ * only the first 4 bytes of default_label ever got written (with an
+ * unrelated pointer value, not zero), so `default_label[0]` read as
+ * non-zero garbage and `jmp %s` emitted a jump to a garbage label
+ * name instead of ever falling back to `lend`. */
+ if (t->kind == TY_ARRAY && init->kind == ND_STR) {
+ codegen_zero_range(base_offset, type_size(t));
+ int n = (int)strlen(init->str) + 1;
+ for (int i = 0; i < n; i++) {
+ out(" lea eax, [ebp%+d]\n", base_offset + i);
+ out(" mov byte [eax], %d\n", (unsigned char)init->str[i]);
+ }
+ return;
+ }
+ if (t->kind == TY_ARRAY && init->kind == ND_BLOCK) {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
rm -rf build/stage2
gcc -Wall -Wextra -O2 -o build/toolchain/cc toolchain/cc/*.c 2>&1 | tail -20
gcc -Wall -Wextra -O2 -o build/toolchain/as toolchain/as/*.c 2>&1 | tail -20
gcc -Wall -Wextra -O2 -o build/toolchain/ld toolchain/ld/*.c 2>&1 | tail -20
echo "stage1 rebuilt"
mkdir -p build/stage2
fail=0
for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c toolchain/hostlib/*.c; do
base=$(echo "$f" | tr '/' '_')
./build/toolchain/cc -Itoolchain/include --target=host -o "build/stage2/${base%.c}.S" "$f" || { echo "CODEGEN FAIL: $f"; fail=1; }
done
for s in build/stage2/*.S; do
o="${s%.S}.o"
./build/toolchain/as -f elf32 -o "$o" "$s" || { echo "AS FAIL: $s"; fail=1; }
done
./build/toolchain/ld -o build/stage2/as $(ls build/stage2/toolchain_as_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
./build/toolchain/ld -o build/stage2/cc $(ls build/stage2/toolchain_cc_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
./build/toolchain/ld -o build/stage2/ld $(ls build/stage2/toolchain_ld_*.o) $(ls build/stage2/toolchain_hostlib_*.o) || fail=1
chmod +x build/stage2/as build/stage2/cc build/stage2/ld
echo "stage2 build fail=$fail"
timeout 90 env CC_BIN=./build/stage2/cc AS_BIN=./build/stage2/as LD_BIN=./build/stage2/ld ./tools/run_toolchain_tests.sh 2>&1 | tail -25stage1 rebuilt stage2 build fail=0 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 rm -rf build/toolchain-stage1 build/toolchain-stage2 build/toolchain-stage2-obj build/toolchain-stage3 build/toolchain-stage3-obj build/stage2 timeout 300 ./tools/bootstrap_selfhost.sh build 2>&1 | tail -40
bootstrap: stage1 (host gcc) bootstrap: stage2 (via stage1) bootstrap: stage3 (via stage2) bootstrap: verifying stage2 == stage3 as: identical cc: identical ld: identical bootstrap: PASS - self-hosted toolchain installed at build/toolchain
$ cd /home/martin/workspace/prototypes/boot-to-claude ls -la build/toolchain/ file build/toolchain/as build/toolchain/cc build/toolchain/ld echo "--- toolchain test battery against final installed toolchain ---" timeout 90 env CC_BIN=./build/toolchain/cc AS_BIN=./build/toolchain/as LD_BIN=./build/toolchain/ld ./tools/run_toolchain_tests.sh 2>&1 | tail -20
total 256 drwxr-x--- 2 martin martin 4096 Jul 21 07:06 . drwxr-x--- 12 martin martin 4096 Jul 21 07:07 .. -rwxr-x--- 1 martin martin 88148 Jul 21 07:07 as -rwxr-x--- 1 martin martin 102856 Jul 21 07:07 cc -rwxr-x--- 1 martin martin 29816 Jul 21 07:07 ld build/toolchain/as: ELF 32-bit LSB executable, Intel i386, version 1 (SYSV), statically linked, no section header build/toolchain/cc: ELF 32-bit LSB executable, Intel i386, version 1 (SYSV), statically linked, no section header build/toolchain/ld: ELF 32-bit LSB executable, Intel i386, version 1 (SYSV), statically linked, no section header --- toolchain test battery against final installed toolchain --- 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 >/dev/null timeout 120 make 2>&1 | tail -40
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
65 BUILD_SUBDIRS := $(BUILD) $(BUILD)/toolchain $(BUILD)/fs $(BUILD)/shell \ 66 $(BUILD)/lang $(BUILD)/lang/script $(BUILD)/lang/compiler 67 68 $(BUILD_SUBDIRS): 69 mkdir -p $@ 70 71 # --- bootstrap: build our own AS/CC/LD from source, once, with the host's gcc --- 72 73 TOOLCHAIN_AS_SRCS := $(wildcard toolchain/as/*.c) 74 TOOLCHAIN_CC_SRCS := $(wildcard toolchain/cc/*.c) 75 TOOLCHAIN_LD_SRCS := $(wildcard toolchain/ld/*.c) 76 77 $(BUILD)/toolchain/as: $(TOOLCHAIN_AS_SRCS) | $(BUILD)/toolchain 78 $(BOOTSTRAP_CC) -Wall -Wextra -O2 -o $@ $(TOOLCHAIN_AS_SRCS) 79 80 $(BUILD)/toolchain/cc: $(TOOLCHAIN_CC_SRCS) | $(BUILD)/toolchain 81 $(BOOTSTRAP_CC) -Wall -Wextra -O2 -o $@ $(TOOLCHAIN_CC_SRCS) 82 83 $(BUILD)/toolchain/ld: $(TOOLCHAIN_LD_SRCS) | $(BUILD)/toolchain 84 $(BOOTSTRAP_CC) -Wall -Wextra -O2 -o $@ $(TOOLCHAIN_LD_SRCS)
- # --- 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)
+ # --- bootstrap: the full self-hosting chain (Phase T8) ---
+ # tools/bootstrap_selfhost.sh does all three stages itself: Stage1 via
+ # the host's real compiler (BOOTSTRAP_CC, used nowhere else - this is
+ # its only invocation in the whole build), Stage2 by compiling
+ # toolchain/{as,cc,ld}'s own source with Stage1, Stage3 by compiling it
+ # again with Stage2 - and requires Stage2 and Stage3 to be byte-
+ # identical before installing that verified result here. Every target
+ # below this point (kernel, mkfs_tinyfs, the disk image) is therefore
+ # built by a toolchain that has proven it can reproduce its own binary,
+ # not just "compiled once by something else that tolerated its source".
+
+ TOOLCHAIN_AS_SRCS := $(wildcard toolchain/as/*.c)
+ TOOLCHAIN_CC_SRCS := $(wildcard toolchain/cc/*.c)
+ TOOLCHAIN_LD_SRCS := $(wildcard toolchain/ld/*.c)
+ TOOLCHAIN_HOSTLIB_SRCS := $(wildcard toolchain/hostlib/*.c)
+
+ $(BUILD)/toolchain/as $(BUILD)/toolchain/cc $(BUILD)/toolchain/ld &: \
+ $(TOOLCHAIN_AS_SRCS) $(TOOLCHAIN_CC_SRCS) $(TOOLCHAIN_LD_SRCS) $(TOOLCHAIN_HOSTLIB_SRCS) | $(BUILD)/toolchain
+ BOOTSTRAP_CC=$(BOOTSTRAP_CC) tools/bootstrap_selfhost.sh $(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)
$ cd /home/martin/workspace/prototypes/boot-to-claude make clean >/dev/null timeout 300 make 2>&1 | tail -60
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 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 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 2>&1 | tail -40
run_tests: FAIL - timed out waiting for 'SELFTEST PASS' --- serial log --- qemu-system-i386: terminating on signal 15 from pid 1549379 (bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude cat build/serial.log 2>/dev/null | head -60 echo "---" ls -la build/kernel.bin
--- -rw-r----- 1 martin martin 69828 Jul 21 07:08 build/kernel.bin
$ cd /home/martin/workspace/prototypes/boot-to-claude timeout 8 qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial stdio -no-reboot 2>&1 | head -60
Exit code 143 Terminated
$ cd /home/martin/workspace/prototypes/boot-to-claude mkdir -p /tmp/stage1_only gcc -Wall -Wextra -O2 -o /tmp/stage1_only/as toolchain/as/*.c gcc -Wall -Wextra -O2 -o /tmp/stage1_only/cc toolchain/cc/*.c gcc -Wall -Wextra -O2 -o /tmp/stage1_only/ld toolchain/ld/*.c mkdir -p /tmp/kbuild for f in 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; do base=$(basename "$f" .c) /tmp/stage1_only/cc -Ikernel -Itoolchain/include -o /tmp/kbuild/$base.S "$f" && \ /tmp/stage1_only/as -f elf32 -o /tmp/kbuild/$base.o /tmp/kbuild/$base.S done /tmp/stage1_only/as -f elf32 -o /tmp/kbuild/entry.o kernel/entry.S /tmp/stage1_only/as -f elf32 -o /tmp/kbuild/idt_stubs.o kernel/idt_stubs.S /tmp/stage1_only/ld -T kernel/link.ld --format bin -o /tmp/kbuild/kernel.bin /tmp/kbuild/entry.o /tmp/kbuild/idt_stubs.o /tmp/kbuild/kmain.o /tmp/kbuild/console.o /tmp/kbuild/serial.o /tmp/kbuild/gdt.o /tmp/kbuild/idt.o /tmp/kbuild/string.o /tmp/kbuild/memory.o /tmp/kbuild/selftest.o /tmp/kbuild/ata.o /tmp/kbuild/tinyfs.o /tmp/kbuild/tinyfs_disk.o /tmp/kbuild/keyboard.o /tmp/kbuild/shell.o /tmp/kbuild/lexer.o /tmp/kbuild/ast.o /tmp/kbuild/parser.o /tmp/kbuild/eval.o /tmp/kbuild/parser.o /tmp/kbuild/codegen.o /tmp/kbuild/vm.o 2>&1 | tail -20 ls -la /tmp/kbuild/kernel.bin build/kernel.bin
Exit code 2 ld: duplicate symbol 'tc_parser_init' (from /tmp/kbuild/parser.o) ls: cannot access '/tmp/kbuild/kernel.bin': No such file or directory -rw-r----- 1 martin martin 69828 Jul 21 07:08 build/kernel.bin
$ cd /home/martin/workspace/prototypes/boot-to-claude
rm -rf /tmp/kbuild
mkdir -p /tmp/kbuild/fs /tmp/kbuild/shell /tmp/kbuild/lang/script /tmp/kbuild/lang/compiler
for f in 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; do
rel=${f#kernel/}
out=/tmp/kbuild/${rel%.c}
/tmp/stage1_only/cc -Ikernel -Itoolchain/include -o "$out.S" "$f" && \
/tmp/stage1_only/as -f elf32 -o "$out.o" "$out.S"
done
/tmp/stage1_only/as -f elf32 -o /tmp/kbuild/entry.o kernel/entry.S
/tmp/stage1_only/as -f elf32 -o /tmp/kbuild/idt_stubs.o kernel/idt_stubs.S
/tmp/stage1_only/ld -T kernel/link.ld --format bin -o /tmp/kbuild/kernel.bin \
/tmp/kbuild/entry.o /tmp/kbuild/idt_stubs.o /tmp/kbuild/kmain.o /tmp/kbuild/console.o /tmp/kbuild/serial.o \
/tmp/kbuild/gdt.o /tmp/kbuild/idt.o /tmp/kbuild/string.o /tmp/kbuild/memory.o /tmp/kbuild/selftest.o \
/tmp/kbuild/ata.o /tmp/kbuild/fs/tinyfs.o /tmp/kbuild/fs/tinyfs_disk.o /tmp/kbuild/keyboard.o /tmp/kbuild/shell/shell.o \
/tmp/kbuild/lang/lexer.o /tmp/kbuild/lang/script/ast.o /tmp/kbuild/lang/script/parser.o /tmp/kbuild/lang/script/eval.o \
/tmp/kbuild/lang/compiler/parser.o /tmp/kbuild/lang/compiler/codegen.o /tmp/kbuild/lang/vm.o
echo "link exit=$?"
ls -la /tmp/kbuild/kernel.bin build/kernel.bin
cmp /tmp/kbuild/kernel.bin build/kernel.bin && echo IDENTICAL || echo DIFFERSlink exit=0 -rw-r----- 1 martin martin 69828 Jul 21 07:09 /tmp/kbuild/kernel.bin -rw-r----- 1 martin martin 69828 Jul 21 07:08 build/kernel.bin IDENTICAL
$ cd /home/martin/workspace/prototypes/boot-to-claude git stash make clean >/dev/null timeout 60 make >/dev/null 2>&1 timeout 60 ./tools/run_tests.sh 2>&1 | tail -10 git stash pop
Saved working directory and index state WIP on main: 1d62d14 Phase T7: full switch-over to the self-hosted toolchain
run_tests: PASS
qemu-system-i386: terminating on signal 15 from pid 1549943 (bash)
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: Makefile
modified: toolchain/as/elf_format.h
modified: toolchain/as/elf_writer.c
modified: toolchain/as/encode.c
modified: toolchain/as/parser.c
modified: toolchain/cc/builtins.c
modified: toolchain/cc/cc.h
modified: toolchain/cc/codegen.c
modified: toolchain/cc/lexer.c
modified: toolchain/cc/parser.c
modified: toolchain/include/stdint.h
modified: toolchain/ld/elf_format.h
modified: toolchain/ld/elf_reader.c
modified: toolchain/ld/ld.h
modified: toolchain/ld/link_flat.c
modified: toolchain/ld/link_static_exec.c
Untracked files:
(use "git add <file>..." to include in what will be committed)
toolchain/hostlib/
toolchain/include/ctype.h
toolchain/include/hostsys.h
toolchain/include/stdarg.h
toolchain/include/stdio.h
toolchain/include/stdlib.h
toolchain/include/string.h
toolchain/include/sys/
tools/bootstrap_selfhost.sh
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (667772bdf5c618530bab0f6a4b815f61e7c32664)$ cd /home/martin/workspace/prototypes/boot-to-claude mkdir -p /tmp/oldtools /tmp/newtools git show HEAD:toolchain/as/bin_writer.c > /tmp/old_as_bin_writer.c 2>/dev/null for f in toolchain/as/*.c toolchain/cc/*.c toolchain/ld/*.c; do mkdir -p "/tmp/oldsrc/$(dirname "$f")" git show "HEAD:$f" > "/tmp/oldsrc/$f" 2>/dev/null done gcc -Wall -Wextra -O2 -o /tmp/oldtools/as /tmp/oldsrc/toolchain/as/*.c gcc -Wall -Wextra -O2 -o /tmp/oldtools/cc /tmp/oldsrc/toolchain/cc/*.c gcc -Wall -Wextra -O2 -o /tmp/oldtools/ld /tmp/oldsrc/toolchain/ld/*.c echo "old tools built" gcc -Wall -Wextra -O2 -o /tmp/newtools/as toolchain/as/*.c gcc -Wall -Wextra -O2 -o /tmp/newtools/cc toolchain/cc/*.c gcc -Wall -Wextra -O2 -o /tmp/newtools/ld toolchain/ld/*.c echo "new tools built"
/tmp/oldsrc/toolchain/as/bin_writer.c:1:10: fatal error: as.h: No such file or directory
1 | #include "as.h"
| ^~~~~~
compilation terminated.
/tmp/oldsrc/toolchain/as/elf_writer.c:1:10: fatal error: as.h: No such file or directory
1 | #include "as.h"
| ^~~~~~
compilation terminated.
/tmp/oldsrc/toolchain/as/encode.c:1:10: fatal error: as.h: No such file or directory
1 | #include "as.h"
| ^~~~~~
compilation terminated.
/tmp/oldsrc/toolchain/as/lexer.c:1:10: fatal error: as.h: No such file or directory
1 | #include "as.h"
| ^~~~~~
compilation terminated.
/tmp/oldsrc/toolchain/as/main.c:1:10: fatal error: as.h: No such file or directory
1 | #include "as.h"
| ^~~~~~
compilation terminated.
/tmp/oldsrc/toolchain/as/parser.c:1:10: fatal error: as.h: No such file or directory
1 | #include "as.h"
| ^~~~~~
compilation terminated.
/tmp/oldsrc/toolchain/cc/builtins.c:1:10: fatal error: cc.h: No such file or directory
1 | #include "cc.h"
| ^~~~~~
compilation terminated.
/tmp/oldsrc/toolchain/cc/codegen.c:1:10: fatal error: cc.h: No such file or directory
1 | #include "cc.h"
| ^~~~~~
compilation terminated.
/tmp/oldsrc/toolchain/cc/lexer.c:1:10: fatal error: cc.h: No such file or directory
1 | #include "cc.h"
| ^~~~~~
compilation terminated.
/tmp/oldsrc/toolchain/cc/main.c:1:10: fatal error: cc.h: No such file or directory
1 | #include "cc.h"
| ^~~~~~
compilation terminated.
/tmp/oldsrc/toolchain/cc/parser.c:1:10: fatal error: cc.h: No such file or directory
1 | #include "cc.h"
| ^~~~~~
compilation terminated.
/tmp/oldsrc/toolchain/cc/preprocessor.c:1:10: fatal error: cc.h: No such file or directory
1 | #include "cc.h"
| ^~~~~~
compilation terminated.
/tmp/oldsrc/toolchain/cc/types.c:1:10: fatal error: cc.h: No such file or directory
1 | #include "cc.h"
| ^~~~~~
compilation terminated.
/tmp/oldsrc/toolchain/ld/elf_reader.c:1:10: fatal error: ld.h: No such file or directory
1 | #include "ld.h"
| ^~~~~~
compilation terminated.
/tmp/oldsrc/toolchain/ld/link_flat.c:1:10: fatal error: ld.h: No such file or directory
1 | #include "ld.h"
| ^~~~~~
compilation terminated.
/tmp/oldsrc/toolchain/ld/link_static_exec.c:1:10: fatal error: ld.h: No such file or directory
1 | #include "ld.h"
| ^~~~~~
compilation terminated.
/tmp/oldsrc/toolchain/ld/main.c:1:10: fatal error: ld.h: No such file or directory
1 | #include "ld.h"
| ^~~~~~
compilation terminated.
old tools built
new tools built$ cd /home/martin/workspace/prototypes/boot-to-claude for f in $(git show HEAD --stat=1000 2>/dev/null; git ls-tree -r HEAD --name-only -- toolchain/as toolchain/cc toolchain/ld); do mkdir -p "/tmp/oldsrc/$(dirname "$f")" git show "HEAD:$f" > "/tmp/oldsrc/$f" 2>/dev/null done ls /tmp/oldsrc/toolchain/as /tmp/oldsrc/toolchain/cc /tmp/oldsrc/toolchain/ld gcc -Wall -Wextra -O2 -o /tmp/oldtools/as /tmp/oldsrc/toolchain/as/*.c 2>&1 | tail -10 gcc -Wall -Wextra -O2 -o /tmp/oldtools/cc /tmp/oldsrc/toolchain/cc/*.c 2>&1 | tail -10 gcc -Wall -Wextra -O2 -o /tmp/oldtools/ld /tmp/oldsrc/toolchain/ld/*.c 2>&1 | tail -10 echo "old tools rebuilt"
/bin/bash: line 13: /tmp/oldsrc/toolchain: Is a directory mkdir: Already exists /bin/bash: line 13: /tmp/oldsrc/gcc/nasm/ld/objcopy: Not a directory /bin/bash: line 13: /tmp/oldsrc//: Is a directory /bin/bash: line 13: /tmp/oldsrc//: Is a directory mkdir: Already exists /bin/bash: line 13: /tmp/oldsrc/struct/union: Not a directory mkdir: Already exists /bin/bash: line 13: /tmp/oldsrc/kernel/lang/script/eval.c: Not a directory mkdir: Already exists /bin/bash: line 13: /tmp/oldsrc/tests/toolchain/t06_switch.c: Not a directory mkdir: Already exists /bin/bash: line 13: /tmp/oldsrc/tests/toolchain/t07_comma.c: Not a directory mkdir: Already exists /bin/bash: line 13: /tmp/oldsrc/tests/toolchain/t08_forward_struct.c: Not a directory mkdir: Already exists /bin/bash: line 13: /tmp/oldsrc/tests/toolchain/t09_local_init.c: Not a directory mkdir: Already exists /bin/bash: line 13: /tmp/oldsrc/tests/toolchain/t10_short.c: Not a directory mkdir: Already exists /bin/bash: line 13: /tmp/oldsrc/tests/toolchain/t11_array_size.c: Not a directory mkdir: Already exists /bin/bash: line 13: /tmp/oldsrc/tests/toolchain/t12_local_array_size.c: Not a directory mkdir: Already exists /bin/bash: line 13: /tmp/oldsrc/tests/toolchain/t13_struct_copy.c: Not a directory mkdir: Already exists /bin/bash: line 13: /tmp/oldsrc/tests/toolchain/t14_struct_return.c: Not a directory mkdir: Already exists /bin/bash: line 13: /tmp/oldsrc/tests/toolchain/t15_escapes.c: Not a directory /bin/bash: line 13: /tmp/oldsrc/++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------------------: File name too long /tmp/oldsrc/toolchain/as: as.h bin_writer.c elf_format.h elf_writer.c encode.c lexer.c main.c parser.c /tmp/oldsrc/toolchain/cc: builtins.c cc.h codegen.c lexer.c main.c parser.c preprocessor.c types.c /tmp/oldsrc/toolchain/ld: elf_format.h elf_reader.c ld.h link_flat.c link_static_exec.c main.c old tools rebuilt
$ cd /home/martin/workspace/prototypes/boot-to-claude
ls -la /tmp/oldtools/
# rebuild kernel with OLD as+cc+ld to confirm baseline
rm -rf /tmp/kbuild_old && mkdir -p /tmp/kbuild_old/fs /tmp/kbuild_old/shell /tmp/kbuild_old/lang/script /tmp/kbuild_old/lang/compiler
for f in 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; do
rel=${f#kernel/}; out=/tmp/kbuild_old/${rel%.c}
/tmp/oldtools/cc -Ikernel -Itoolchain/include -o "$out.S" "$f" && /tmp/oldtools/as -f elf32 -o "$out.o" "$out.S"
done
/tmp/oldtools/as -f elf32 -o /tmp/kbuild_old/entry.o kernel/entry.S
/tmp/oldtools/as -f elf32 -o /tmp/kbuild_old/idt_stubs.o kernel/idt_stubs.S
/tmp/oldtools/ld -T kernel/link.ld --format bin -o /tmp/kbuild_old/kernel.bin \
/tmp/kbuild_old/entry.o /tmp/kbuild_old/idt_stubs.o /tmp/kbuild_old/kmain.o /tmp/kbuild_old/console.o /tmp/kbuild_old/serial.o \
/tmp/kbuild_old/gdt.o /tmp/kbuild_old/idt.o /tmp/kbuild_old/string.o /tmp/kbuild_old/memory.o /tmp/kbuild_old/selftest.o \
/tmp/kbuild_old/ata.o /tmp/kbuild_old/fs/tinyfs.o /tmp/kbuild_old/fs/tinyfs_disk.o /tmp/kbuild_old/keyboard.o /tmp/kbuild_old/shell/shell.o \
/tmp/kbuild_old/lang/lexer.o /tmp/kbuild_old/lang/script/ast.o /tmp/kbuild_old/lang/script/parser.o /tmp/kbuild_old/lang/script/eval.o \
/tmp/kbuild_old/lang/compiler/parser.o /tmp/kbuild_old/lang/compiler/codegen.o /tmp/kbuild_old/lang/vm.o
echo "link exit=$?"
cmp /tmp/kbuild_old/kernel.bin build/kernel.bin && echo SAME || echo DIFFERStotal 148 drwxr-x--- 2 martin martin 100 Jul 21 07:10 . drwxrwxrwt 308 root root 21540 Jul 21 07:10 .. -rwxr-x--- 1 martin martin 51504 Jul 21 07:10 as -rwxr-x--- 1 martin martin 69832 Jul 21 07:10 cc -rwxr-x--- 1 martin martin 21864 Jul 21 07:10 ld link exit=0 SAME
$ cd /home/martin/workspace/prototypes/boot-to-claude git status --short | head -5 make clean >/dev/null timeout 300 make 2>&1 | tail -10 echo "=== build done ===" timeout 60 ./tools/run_tests.sh 2>&1 | tail -15
M Makefile M toolchain/as/elf_format.h M toolchain/as/elf_writer.c M toolchain/as/encode.c M toolchain/as/parser.c 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 === build done === run_tests: FAIL - timed out waiting for 'SELFTEST PASS' --- serial log --- qemu-system-i386: terminating on signal 15 from pid 1551976 (bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude cp build/kernel.bin /tmp/kernel_t8.bin cp build/boot.bin /tmp/boot_t8.bin cp build/stage2.bin /tmp/stage2_t8.bin cp build/disk.img /tmp/disk_t8.img cp build/mkfs_tinyfs /tmp/mkfs_t8 git stash make clean >/dev/null timeout 120 make >/dev/null 2>&1 cp build/kernel.bin /tmp/kernel_t7.bin cp build/boot.bin /tmp/boot_t7.bin cp build/stage2.bin /tmp/stage2_t7.bin cp build/disk.img /tmp/disk_t7.img cp build/mkfs_tinyfs /tmp/mkfs_t7 git stash pop echo "=== comparisons ===" cmp /tmp/kernel_t7.bin /tmp/kernel_t8.bin && echo "kernel.bin: SAME" || echo "kernel.bin: DIFFERS" cmp /tmp/boot_t7.bin /tmp/boot_t8.bin && echo "boot.bin: SAME" || echo "boot.bin: DIFFERS" cmp /tmp/stage2_t7.bin /tmp/stage2_t8.bin && echo "stage2.bin: SAME" || echo "stage2.bin: DIFFERS" cmp /tmp/mkfs_t7 /tmp/mkfs_t8 && echo "mkfs_tinyfs: SAME" || echo "mkfs_tinyfs: DIFFERS" cmp /tmp/disk_t7.img /tmp/disk_t8.img && echo "disk.img: SAME" || echo "disk.img: DIFFERS"
Saved working directory and index state WIP on main: 1d62d14 Phase T7: full switch-over to the self-hosted toolchain
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: Makefile
modified: toolchain/as/elf_format.h
modified: toolchain/as/elf_writer.c
modified: toolchain/as/encode.c
modified: toolchain/as/parser.c
modified: toolchain/cc/builtins.c
modified: toolchain/cc/cc.h
modified: toolchain/cc/codegen.c
modified: toolchain/cc/lexer.c
modified: toolchain/cc/parser.c
modified: toolchain/include/stdint.h
modified: toolchain/ld/elf_format.h
modified: toolchain/ld/elf_reader.c
modified: toolchain/ld/ld.h
modified: toolchain/ld/link_flat.c
modified: toolchain/ld/link_static_exec.c
Untracked files:
(use "git add <file>..." to include in what will be committed)
toolchain/hostlib/
toolchain/include/ctype.h
toolchain/include/hostsys.h
toolchain/include/stdarg.h
toolchain/include/stdio.h
toolchain/include/stdlib.h
toolchain/include/string.h
toolchain/include/sys/
tools/bootstrap_selfhost.sh
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (2f5028bcf4f6a2d712e2c14ee0469c0be78b482b)
=== comparisons ===
/tmp/kernel_t7.bin /tmp/kernel_t8.bin differ: byte 2, line 1
kernel.bin: DIFFERS
/tmp/boot_t7.bin /tmp/boot_t8.bin differ: byte 77, line 1
boot.bin: DIFFERS
stage2.bin: SAME
mkfs_tinyfs: SAME
/tmp/disk_t7.img /tmp/disk_t8.img differ: byte 77, line 1
disk.img: DIFFERS$ cd /home/martin/workspace/prototypes/boot-to-claude ls -la /tmp/boot_t7.bin /tmp/boot_t8.bin cmp -l /tmp/boot_t7.bin /tmp/boot_t8.bin
Exit code 1 -rw-r----- 1 martin martin 512 Jul 21 07:11 /tmp/boot_t7.bin -rw-r----- 1 martin martin 512 Jul 21 07:11 /tmp/boot_t8.bin 77 0 1
$ cd /home/martin/workspace/prototypes/boot-to-claude xxd -s 60 -l 40 /tmp/boot_t7.bin echo "---" xxd -s 60 -l 40 /tmp/boot_t8.bin echo "--- boot.S ---" cat boot/boot.S
0000003c: f4e9 fcff 1000 0400 007e 0000 0100 0000 .........~......
0000004c: 0000 0000 626f 6f74 2000 6469 736b 2065 ....boot .disk e
0000005c: 7272 0000 0000 0000 rr......
---
0000003c: f4e9 fcff 1000 0400 007e 0000 0100 0000 .........~......
0000004c: 0100 0000 626f 6f74 2000 6469 736b 2065 ....boot .disk e
0000005c: 7272 0000 0000 0000 rr......
--- boot.S ---
; Stage 1 MBR boot sector.
; BIOS loads this at 0x7c00 and jumps to it in 16-bit real mode.
; Job: load stage2 (STAGE2_SECTORS sectors starting at LBA 1) to 0x0000:0x7e00
; and jump to it. Kept deliberately minimal - all real work happens in stage2.
BITS 16
ORG 0x7c00
STAGE2_SECTORS equ 4 ; must match the padding at the end of stage2.S
start:
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7c00
mov si, msg_boot
call print_str16
mov si, dap
mov ah, 0x42 ; INT13h extended read (LBA)
mov dl, 0x80 ; first hard disk (matches qemu -drive raw image)
int 0x13
jc disk_error
jmp 0x0000:0x7e00
print_str16:
pusha
.loop:
lodsb
or al, al
jz .done
mov ah, 0x0e ; BIOS teletype output
mov bh, 0
int 0x10
jmp .loop
.done:
popa
ret
disk_error:
mov si, msg_err
call print_str16
.hang:
hlt
jmp .hang
align 4
dap: ; disk address packet for INT13h AH=0x42
db 0x10 ; packet size
db 0 ; reserved
dw STAGE2_SECTORS ; sectors to read
dw 0x7e00 ; destination offset
dw 0x0000 ; destination segment
dq 1 ; starting LBA (sector right after the MBR)
msg_boot: db "boot ", 0
msg_err: db "disk err", 0
times 510-($-$$) db 0
dw 0xAA55$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n '">> 32"\|>> 32' toolchain/as/encode.c grep -rn '\bdq\b' toolchain/as/*.c boot/*.S kernel/*.S 2>/dev/null
224: else { e32(&e, (uint32_t)v); e32(&e, (uint32_t)(v >> 32)); }
boot/boot.S:57: dq 1 ; starting LBA (sector right after the MBR)
boot/stage2.S:116:dap_lba: dq 0
boot/stage2.S:125: dq 0 ; null descriptor
toolchain/as/parser.c:351:/* --- data directive value lists (db/dw/dd/dq), split one scalar per stmt --- */
toolchain/as/parser.c:479: if (ieq(head.text, "dq")) { parse_data_directive(ps, ln, ".dq"); return; }
toolchain/as/encode.c:216: if (strcmp(m, ".db") == 0 || strcmp(m, ".dw") == 0 || strcmp(m, ".dd") == 0 || strcmp(m, ".dq") == 0) {216 if (strcmp(m, ".db") == 0 || strcmp(m, ".dw") == 0 || strcmp(m, ".dd") == 0 || strcmp(m, ".dq") == 0) {
217 int width = strcmp(m, ".db") == 0 ? 1 : strcmp(m, ".dw") == 0 ? 2 : strcmp(m, ".dd") == 0 ? 4 : 8;
218 int64_t v = a->has_sym
219 ? resolve_abs(ctx, is_bin, a->sym, a->disp, st->section, st->offset, strict)
220 : a->disp;
221 if (width == 1) e8(&e, (uint8_t)v);
222 else if (width == 2) e16(&e, (uint16_t)v);
223 else if (width == 4) e32(&e, (uint32_t)v);
224 else { e32(&e, (uint32_t)v); e32(&e, (uint32_t)(v >> 32)); }
225 return e.len;- if (width == 1) e8(&e, (uint8_t)v);
- else if (width == 2) e16(&e, (uint16_t)v);
- else if (width == 4) e32(&e, (uint32_t)v);
- else { e32(&e, (uint32_t)v); e32(&e, (uint32_t)(v >> 32)); }
- return e.len;
+ if (width == 1) e8(&e, (uint8_t)v);
+ else if (width == 2) e16(&e, (uint16_t)v);
+ else if (width == 4) e32(&e, (uint32_t)v);
+ else {
+ /* `dq` (e.g. boot/boot.S's disk-address-packet LBA field):
+ * not `v >> 32` - under self-hosting (Phase T8), AS's own
+ * `int64_t` is CC's 32-bit alias (see toolchain/include/
+ * stdint.h), and x86's SHR/SAR masks a 32-bit shift count to
+ * 5 bits, so a *compiled* `v >> 32` silently becomes `v >> 0`
+ * (a no-op) instead of 0 - every `dq` in this codebase is a
+ * small non-negative constant (never real 64-bit data, see
+ * docs/toolchain.md), so the high word is always exactly a
+ * sign-extension of v, computed without any 32-bit shift. */
+ e32(&e, (uint32_t)v);
+ e32(&e, v < 0 ? 0xFFFFFFFFu : 0);
+ }
+ 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 -rn '>> 32\|>>32' toolchain/*.c toolchain/*/*.c kernel/*.c kernel/*/*.c 2>/dev/null
Exit code 2 toolchain/as/encode.c:226: * not `v >> 32` - under self-hosting (Phase T8), AS's own toolchain/as/encode.c:229: * 5 bits, so a *compiled* `v >> 32` silently becomes `v >> 0`
$ cd /home/martin/workspace/prototypes/boot-to-claude make clean >/dev/null timeout 300 make 2>&1 | tail -10 echo "=== comparing to T7 ===" cmp /tmp/kernel_t7.bin build/kernel.bin && echo "kernel.bin: SAME" || echo "kernel.bin: DIFFERS" cmp /tmp/boot_t7.bin build/boot.bin && echo "boot.bin: SAME" || echo "boot.bin: DIFFERS"
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 === comparing to T7 === /tmp/kernel_t7.bin build/kernel.bin differ: byte 2, line 1 kernel.bin: DIFFERS boot.bin: SAME
$ cd /home/martin/workspace/prototypes/boot-to-claude xxd -l 16 /tmp/kernel_t7.bin echo "---" xxd -l 16 build/kernel.bin echo "--- entry.S head ---" head -20 kernel/entry.S
00000000: bc48 5002 00bf 4810 0200 b91c e802 002b .HP...H........+
---
00000000: bcc4 5002 00bf c410 0200 b998 e802 002b ..P............+
--- entry.S head ---
; 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$ cd /home/martin/workspace/prototypes/boot-to-claude ls -la /tmp/kernel_t7.bin build/kernel.bin timeout 60 ./tools/run_tests.sh 2>&1 | tail -20
-rw-r----- 1 martin martin 69704 Jul 21 07:11 /tmp/kernel_t7.bin -rw-r----- 1 martin martin 69828 Jul 21 07:13 build/kernel.bin run_tests: PASS qemu-system-i386: terminating on signal 15 from pid 1552766 (bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude rm -rf build/toolchain-stage1 build/toolchain-stage2 build/toolchain-stage2-obj build/toolchain-stage3 build/toolchain-stage3-obj timeout 300 ./tools/bootstrap_selfhost.sh build 2>&1 | tail -10 echo "=== toolchain tests ===" timeout 90 env CC_BIN=./build/toolchain/cc AS_BIN=./build/toolchain/as LD_BIN=./build/toolchain/ld ./tools/run_toolchain_tests.sh 2>&1 | tail -20
bootstrap: stage1 (host gcc) bootstrap: stage2 (via stage1) bootstrap: stage3 (via stage2) bootstrap: verifying stage2 == stage3 as: identical cc: identical ld: identical bootstrap: PASS - self-hosted toolchain installed at build/toolchain === 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
$ cd /home/martin/workspace/prototypes/boot-to-claude make clean >/dev/null timeout 300 make >/dev/null 2>&1 echo "build exit=$?" timeout 60 ./tools/run_tests.sh 2>&1 | tail -15
build exit=0 run_tests: PASS qemu-system-i386: terminating on signal 15 from pid 1553455 (bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude cat .gitignore 2>/dev/null | head -5 git status --short --ignored | grep -c '^!!' ls build/ | head -20
build/ *.o *.bin *.img *.tbc 1 ata.o boot.bin console.o disk.img entry.o fs gdt.o host_mkfs_tinyfs.S host_mkfs_tinyfs.o host_string.S host_string.o host_tinyfs.S host_tinyfs.o idt.o idt_stubs.o kernel.bin keyboard.o kmain.o lang memory.o
$ cd /home/martin/workspace/prototypes/boot-to-claude ls build/
ata.o boot.bin console.o disk.img entry.o fs gdt.o host_mkfs_tinyfs.S host_mkfs_tinyfs.o host_string.S host_string.o host_tinyfs.S host_tinyfs.o idt.o idt_stubs.o kernel.bin keyboard.o kmain.o lang memory.o mkfs_tinyfs selftest.o serial.log serial.o shell stage2.bin string.o toolchain toolchain-stage1 toolchain-stage2 toolchain-stage2-obj toolchain-stage3 toolchain-stage3-obj
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n '^## ' docs/toolchain.md wc -l docs/toolchain.md
9:## AS (assembler) 129:## CC (compiler) 259:## Kernel source adaptation (Phase T3) 293:## LD (linker, Phase T5) 423:## Full switch-over (Phase T7) 599:## Bootstrap 602 docs/toolchain.md
595 containing a 'b' (`build hello.tc hello.tbc`), consistently, regardless 596 of keystroke timing - which ruled out a timing race and pointed at the 597 character itself. 598 599 ## Bootstrap 600 601 _To be filled in once Phase T8 lands: the Stage0/1/2/3 procedure and how
- ## Bootstrap
-
- _To be filled in once Phase T8 lands: the Stage0/1/2/3 procedure and how
+ ## Bootstrap (Phase T8 - self-hosting)
+
+ `tools/bootstrap_selfhost.sh` is the whole procedure, and it's what the
+ Makefile's `$(BUILD)/toolchain/{as,cc,ld}` rule actually runs:
+
+ 1. **Stage1**: the host's real compiler (`BOOTSTRAP_CC`, `gcc` by
+ default - its only invocation anywhere in the build) compiles
+ `toolchain/{as,cc,ld}`'s source directly, the same one-shot build
+ Phase T7 already did.
+ 2. **Stage2**: Stage1's own `cc`/`as`/`ld` compile that *exact same
+ source* - including `toolchain/hostlib/`, see below - into a second
+ set of binaries.
+ 3. **Stage3**: Stage2 compiles the same source a third time.
+ 4. **Verify**: Stage2 and Stage3 must be byte-identical (`cmp`) for all
+ three tools. This is the standard GCC/LLVM fixed-point proof that a
+ compiler genuinely compiles itself, not just "happened to survive
+ being compiled once by something that tolerated its source."
+
+ On success, Stage2 (== Stage3) is installed at `$(BUILD)/toolchain/
+ {as,cc,ld}` - the exact path `AS`/`CC`/`LD` already pointed at since
+ Phase T7 - so every kernel, boot-loader, and host-tool build after this
+ one is genuinely self-hosted, not merely bootstrapped by an external
+ compiler once and then reused.
+
+ ### hostlib: a minimal libc for AS/CC/LD's own source
+
+ Phase T7's builtins (`__sys_open`/`read`/`write`/`lseek`/`close`/`exit`)
+ were enough for `mkfs_tinyfs`, which was already deliberately written
+ libc-free. `toolchain/{as,cc,ld}`'s own source was never written that
+ way - it uses ordinary `fopen`/`fprintf`/`malloc`/`strcmp`/`isdigit`
+ freely, same as any normal C program compiled by a normal compiler. Two
+ new builtins (`__sys_brk`, `__sys_chmod`) plus `toolchain/hostlib/`
+ (compiled into every `--target=host` binary alongside the program's own
+ object files) implement just enough of `stdio.h`/`stdlib.h`/`string.h`/
+ `ctype.h`/`sys/stat.h` to cover what that source actually calls:
+ unbuffered `FILE*` I/O over the raw syscalls, a bump allocator over
+ `brk(2)` (no `free()` - these are short-lived, single-pass build tools,
+ same spirit as the rest of this codegen's "simple over optimal"
+ philosophy), and a `printf`-format subset (`%s %d %u %c`, the `+` flag,
+ `l`/`ll` length modifiers parsed and ignored - seen below, that's not a
+ shortcut, it's the only self-consistent choice once `int64_t` is a
+ 32-bit alias).
+
+ ### New CC features, needed only because CC now compiles *itself*
+
+ Two real language features, both invisible to every kernel/host-tool
+ build target until AS/CC/LD's own source went through CC for the first
+ time:
+
+ - **Variadic functions** (`fprintf(FILE*, const char*, ...)`, and
+ codegen.c's own `out(const char *fmt, ...)`): `...` in a parameter
+ list is now accepted (parser.c's `parse_param_list`) with no further
+ bookkeeping needed - call sites already push each argument
+ independently with no prototype-arity check (see
+ `codegen_call_or_builtin`), so a variadic call "just works" once
+ parsing tolerates the extra parameter marker. `va_start`/`va_arg`/
+ `va_end` aren't real functions (CC's preprocessor has no function-like
+ macros, and `va_arg`'s second argument is a type name, which no
+ ordinary call could accept) - they're parsed directly, like `sizeof`,
+ and desugar into ordinary pointer arithmetic on a `char*` (`va_list`):
+ `va_start(ap, last)` becomes `ap = (char*)&last + K`; `va_arg(ap, T)`
+ becomes `*(T*)((ap += K) - K)` (the compound-assign-then-subtract is
+ the only way to get "read the old value, then advance" as one
+ expression without a temporary), where `K` is `last`'s (or `T`'s)
+ 4-byte-rounded size.
+ - **Multi-dimensional arrays** (`uint8_t sec_bytes[MAX_SECTIONS]
+ [65536]` in AS's own `as.h`): `parse_declarator`, `parse_type_name`,
+ and `parse_struct_members` all only handled one `[N]` group before,
+ silently either erroring or (worse, see below) nesting extra
+ dimensions in the wrong order. A shared `parse_array_dims` helper now
+ collects every `[N]` group and nests them correctly - the *first*
+ bracket is the outermost dimension, so it has to wrap a type built
+ from the trailing brackets inward, not the other way around.
+
+ ### Real bugs found
+
+ This phase's verification bar was explicitly two-tier: the toolchain
+ test battery (`tools/run_toolchain_tests.sh`, host-only, fast) had to
+ pass under full self-hosting, *and* the full OS boot/shell suite
+ (`tools/run_tests.sh`) had to keep passing against a disk image built
+ entirely by the self-hosted result. Both were needed - one bug below
+ was invisible to the first and only caught by the second. As with every
+ previous phase, each was found by something concrete actually failing
+ (a crash, a wrong exit code, a corrupted disk image), not by inspection:
+
+ - **`elf_format.h`'s `ELF32_ST_INFO`/`ELF32_R_SYM`/`ELF32_R_TYPE`/
+ `ELF32_R_INFO` were function-like macros**: CC's preprocessor only
+ ever supported object-like `#define` (a Phase T2 scoping decision,
+ never previously an issue since no *kernel* source used one) - the
+ first time AS's or LD's own `elf_format.h` went through CC, these
+ four failed to expand as calls at all. Converted to plain `static
+ inline` functions (already proven to parse fine elsewhere, e.g.
+ `cc.h`'s own `safe_strcpy`).
+ - **Three compound literals in AS's own source** (`(token_t){ .kind =
+ TK_EOF }` in `parser.c`, `(Elf32_Sym){0}` twice in `elf_writer.c`/
+ `link_flat.c`): compound literals were already known-unsupported
+ (Phase T3's compound-literal refactor covered `kernel/` and `tools/`,
+ but not `toolchain/` itself, since nothing had compiled `toolchain/`
+ through CC before). Replaced with `memset` + explicit field
+ assignment, the same pattern T3 established.
+ - **`parse_param_list`'s own array-parameter decay was wrong for 2+
+ dimensions** - and its own declaration, right there in `parser.c`, is
+ `char names[][MAX_NAME]`: the old code wrapped every `[N]` bracket
+ group in another `ptr_to()`, so `T x[][M]` decayed to `T**` instead of
+ the correct `T (*x)[M]` (only the *first*, unsized dimension decays to
+ a pointer - trailing dimensions stay real array dimensions of the
+ pointee). Every `names[n]` inside `parse_param_list` itself then
+ indexed by 4-byte pointer strides instead of `MAX_NAME`-byte array
+ strides, corrupting parser state on the very first call with a real
+ parameter - self-hosting is what first parsed this function's own
+ signature through CC instead of gcc.
+ - **`codegen_addr` had no case for a struct/union-returning `ND_CALL`**:
+ `line_peek(ln).kind` (member access directly on a struct-by-value
+ call's result, no intermediate variable) is a common, ordinary C
+ idiom that Phase T7's struct-return test coverage never happened to
+ exercise. Fixed by treating it like any other "address of a
+ struct-typed rvalue" (`codegen_call_or_builtin` already leaves that
+ address in `eax` on return, per its own established convention).
+ - **AS's `.bss` handling overflowed its own 64KB-per-section buffer**:
+ `.resb`'s pass-2 loop materialized real (zero) bytes into
+ `ctx->sec_bytes[SEC_BSS]` even though nothing ever reads them back
+ (both writers skip emitting `.bss` content, matching `SHT_NOBITS`
+ semantics) - fine for kernel-sized `.bss`, but AS's own `ctx` global
+ alone reserves ~20MB of `.bss`. Fixed by passing a `NULL` destination
+ for `.bss` statements in pass 2 (the same "dry-run, length-only" mode
+ pass 1 already uses), so only the *count* is ever tracked.
+ - **LD's `MAX_SYMS_PER_FILE`/`MAX_RELOCS_PER_FILE` (512) and
+ `MAX_OUT_SYMS` (2048) were too small**: `toolchain/as/encode.c`'s own
+ object file alone has 800+ symbols (mostly its many small static
+ encoding tables and helpers). Bumped generously (4096 / 4096 / 16384),
+ the same "just raise the fixed limit" fix already used repeatedly in
+ Phase T7.
+ - **A short/partial local brace-init left the rest of the aggregate as
+ stack garbage, not zero**: `line_t ln = {0};` for a 3-member struct
+ (one member a 32-element array) only initialized the *first* member;
+ the fix `parse_one_local_decl` already had for globals (a trailing
+ `resb` in `emit_data_bytes`) had no local-variable equivalent. This
+ one was the session's most confusing bug to trace: `ln.n` (an
+ uninitialized bounds counter) and the outer parse loop's "did we
+ reach real EOF" check both silently read garbage, sending AS's own
+ line-tokenizing loop into a multi-million-iteration spin that looked
+ like a hang, not a crash. Fixed with `codegen_zero_range`, a byte-loop
+ that zeroes the whole aggregate up front (redundant but harmless on
+ nested recursive calls) before the existing per-element/member
+ overlay runs.
+ - **A string-literal initializer for a *pointer-typed* struct field
+ emitted a null pointer, not a relocation**: `static const regdef_t
+ REGS[] = {{"al",R_AL,1}, ...}` (AS's own register table) - `emit_
+ scalar_bytes` used `v->num` for every non-`TY_CHAR` scalar, but an
+ `ND_STR` node never sets `->num` at all. `REGS[0].name` came out NULL,
+ and `strcmp(NULL, "ebp")` crashed on the very first register lookup.
+ Fixed by checking for `ND_STR` first and emitting `dd <label>` (a real
+ relocation to the string's own bytes) instead.
+ - **Pointer-minus-pointer (`s - ctx->syms`, elf_writer.c's own
+ array-position computation for a relocation's symbol index) was typed
+ and compiled as if it were pointer arithmetic, not an integer element
+ count**: `make_binop` labeled *any* `TK_MINUS` between two pointers as
+ itself pointer-typed, so `codegen_binop`'s pointer-arithmetic scaling
+ fired and **multiplied** one operand by the element size instead of
+ **dividing** the raw byte difference by it - `sym_elf_index[garbage]`
+ then read wildly out of bounds. Fixed with real `ptrdiff_t` support:
+ `make_binop` now types `ptr - ptr` as plain `int`, and `codegen_binop`
+ computes the byte difference and divides it by the element size (via
+ `idiv`) as its own explicit case, ahead of the general pointer-
+ arithmetic branch.
+ - **`chmod(path, 0755)` in `link_static_exec.c` silently used decimal
+ 755, not octal 0755**: CC's numeric-literal lexer only ever recognized
+ a `0x` hex prefix, never a bare leading-zero octal one (never an issue
+ before - nothing in the kernel used octal literals). Every self-hosted-
+ built executable ended up with permission bits `1363` octal instead of
+ `755` (missing owner-read, among other things) - it happened to still
+ be *executable* by the OS (execute doesn't require read), so this was
+ a real, silent correctness bug that never actually broke a test.
+ Rewritten in decimal (493) with a comment explaining why.
+ - **AS's own `.dq` (8-byte data directive) computed its high 32 bits as
+ `v >> 32`, which silently becomes `v >> 0` under self-hosting**: not a
+ parsing bug at all - `int64_t` is CC's 32-bit alias (see stdint.h),
+ and x86's `SHR`/`SAR` mask a 32-bit operand's shift count to 5 bits,
+ so a *compiled* `v >> 32` is a no-op, not zero. `boot/boot.S`'s `dq 1`
+ (the disk-address-packet's starting-LBA field) came out as `0x0000000
+ 100000001` instead of `1` - the boot sector still assembled and
+ linked without error, and every toolchain test still passed, since
+ none of them touch `dq` at all (only hand-written boot/stage2 assembly
+ does). Only running the *full* `tools/run_tests.sh` against a disk
+ image built end-to-end by the self-hosted toolchain caught this - the
+ kernel never got to print anything, because the boot sector's own INT
+ 13h disk read (extended by that corrupted LBA) never loaded stage2 in
+ the first place. Fixed by writing the high word directly as a sign
+ extension (`v < 0 ? 0xFFFFFFFF : 0`) instead of a shift no self-hosted
+ build can trust.
+
+ _To be filled in once Phase T8 lands: the Stage0/1/2/3 procedure and howThe 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)
715 struct-typed rvalue" (`codegen_call_or_builtin` already leaves that 716 address in `eax` on return, per its own established convention). 717 - **AS's `.bss` handling overflowed its own 64KB-per-section buffer**: 718 `.resb`'s pass-2 loop materialized real (zero) bytes into 719 `ctx->sec_bytes[SEC_BSS]` even though nothing ever reads them back 720 (both writers skip emitting `.bss` content, matching `SHT_NOBITS` 721 semantics) - fine for kernel-sized `.bss`, but AS's own `ctx` global 722 alone reserves ~20MB of `.bss`. Fixed by passing a `NULL` destination 723 for `.bss` statements in pass 2 (the same "dry-run, length-only" mode 724 pass 1 already uses), so only the *count* is ever tracked.
$ cd /home/martin/workspace/prototypes/boot-to-claude tail -15 docs/toolchain.md wc -l docs/toolchain.md
so a *compiled* `v >> 32` is a no-op, not zero. `boot/boot.S`'s `dq 1` (the disk-address-packet's starting-LBA field) came out as `0x0000000 100000001` instead of `1` - the boot sector still assembled and linked without error, and every toolchain test still passed, since none of them touch `dq` at all (only hand-written boot/stage2 assembly does). Only running the *full* `tools/run_tests.sh` against a disk image built end-to-end by the self-hosted toolchain caught this - the kernel never got to print anything, because the boot sector's own INT 13h disk read (extended by that corrupted LBA) never loaded stage2 in the first place. Fixed by writing the high word directly as a sign extension (`v < 0 ? 0xFFFFFFFF : 0`) instead of a shift no self-hosted build can trust. _To be filled in once Phase T8 lands: the Stage0/1/2/3 procedure and how to re-verify Stage2 == Stage3._ 791 docs/toolchain.md
- extension (`v < 0 ? 0xFFFFFFFF : 0`) instead of a shift no self-hosted - build can trust. - - _To be filled in once Phase T8 lands: the Stage0/1/2/3 procedure and how - to re-verify Stage2 == Stage3._ + extension (`v < 0 ? 0xFFFFFFFF : 0`) instead of a shift no self-hosted + build can trust.
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)
1 # Self-hosted toolchain: AS, CC, LD, HOSTCC
$ grep -n 'Phase T8\|Phase T9' /home/martin/.claude/plans/swirling-mixing-bee.md
207:**Phase T8 - Self-hosting bootstrap** 216:**Phase T9 - Docs + cleanup**
207 **Phase T8 - Self-hosting bootstrap**
208 Stage1 = Phase T7's `CC`/`AS`/`LD` (built by host gcc). Use Stage1 to
209 compile `toolchain/{as,cc,ld}`'s own source -> Stage2. Use Stage2 to
210 compile the same source again -> Stage3. Verify: Stage2 and Stage3
211 binaries are byte-identical (`cmp`), for all three tools - the
212 fixed-point proof that `CC` genuinely self-hosts, not just "happened to
213 compile once under host gcc's tolerance for our code." `make` thereafter
214 builds everything (OS + toolchain) with Stage2/3.
215
216 **Phase T9 - Docs + cleanup**- **Phase T8 - Self-hosting bootstrap**
- Stage1 = Phase T7's `CC`/`AS`/`LD` (built by host gcc). Use Stage1 to
- compile `toolchain/{as,cc,ld}`'s own source -> Stage2. Use Stage2 to
- compile the same source again -> Stage3. Verify: Stage2 and Stage3
- binaries are byte-identical (`cmp`), for all three tools - the
- fixed-point proof that `CC` genuinely self-hosts, not just "happened to
- compile once under host gcc's tolerance for our code." `make` thereafter
- builds everything (OS + toolchain) with Stage2/3.
+ **Phase T8 - Self-hosting bootstrap** - done
+ `tools/bootstrap_selfhost.sh` runs the full chain: Stage1 (Phase T7's
+ `CC`/`AS`/`LD`, built by host gcc) compiles `toolchain/{as,cc,ld}` +
+ new `toolchain/hostlib/` source -> Stage2; Stage2 compiles the same
+ source again -> Stage3. Verified: Stage2 and Stage3 are byte-identical
+ (`cmp`) for all three tools. The Makefile's `$(BUILD)/toolchain/
+ {as,cc,ld}` rule now calls this script directly, so `make` builds
+ everything (OS + toolchain) with the verified Stage2/3 result, not a
+ one-shot Stage1 build.
+
+ Getting AS/CC/LD's own source to compile through CC at all (never
+ attempted before this phase - Phase T7 only ever compiled *kernel* and
+ *host-tool* source with CC) required two real CC feature additions
+ (variadic functions/`va_start`/`va_arg`/`va_end`, and proper
+ multi-dimensional array support) plus a minimal libc
+ (`toolchain/hostlib/`, backed by two new builtins: `__sys_brk`,
+ `__sys_chmod`) for the `fopen`/`malloc`/`strcmp`-style code AS/CC/LD's
+ own source already used freely. Getting the *result* to actually build
+ a working OS surfaced eleven further real bugs across AS, CC, and LD -
+ including one (an 8-byte data-directive encoding bug in AS's own source)
+ invisible to the toolchain test battery entirely and only caught by
+ re-running the full `tools/run_tests.sh` OS boot/shell suite against a
+ disk image built end-to-end by the self-hosted result, exactly the
+ two-tier verification this phase's plan called for. Full list in
+ `docs/toolchain.md`'s "Bootstrap (Phase T8 - self-hosting)" 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)
$ cd /home/martin/workspace/prototypes/boot-to-claude git status --short
M Makefile M docs/toolchain.md M toolchain/as/elf_format.h M toolchain/as/elf_writer.c M toolchain/as/encode.c M toolchain/as/parser.c M toolchain/cc/builtins.c M toolchain/cc/cc.h M toolchain/cc/codegen.c M toolchain/cc/lexer.c M toolchain/cc/parser.c M toolchain/include/stdint.h M toolchain/ld/elf_format.h M toolchain/ld/elf_reader.c M toolchain/ld/ld.h M toolchain/ld/link_flat.c M toolchain/ld/link_static_exec.c ?? toolchain/hostlib/ ?? toolchain/include/ctype.h ?? toolchain/include/hostsys.h ?? toolchain/include/stdarg.h ?? toolchain/include/stdio.h ?? toolchain/include/stdlib.h ?? toolchain/include/string.h ?? toolchain/include/sys/ ?? tools/bootstrap_selfhost.sh
$ cd /home/martin/workspace/prototypes/boot-to-claude git add Makefile docs/toolchain.md toolchain/ tools/bootstrap_selfhost.sh git status --short
M Makefile M docs/toolchain.md M toolchain/as/elf_format.h M toolchain/as/elf_writer.c M toolchain/as/encode.c M toolchain/as/parser.c M toolchain/cc/builtins.c M toolchain/cc/cc.h M toolchain/cc/codegen.c M toolchain/cc/lexer.c M toolchain/cc/parser.c A toolchain/hostlib/ctype.c A toolchain/hostlib/stat.c A toolchain/hostlib/stdio.c A toolchain/hostlib/stdlib.c A toolchain/hostlib/string.c A toolchain/include/ctype.h A toolchain/include/hostsys.h A toolchain/include/stdarg.h M toolchain/include/stdint.h A toolchain/include/stdio.h A toolchain/include/stdlib.h A toolchain/include/string.h A toolchain/include/sys/stat.h M toolchain/ld/elf_format.h M toolchain/ld/elf_reader.c M toolchain/ld/ld.h M toolchain/ld/link_flat.c M toolchain/ld/link_static_exec.c A tools/bootstrap_selfhost.sh