$ cd /home/martin/workspace/prototypes/boot-to-claude git commit -m "$(cat <<'EOF' Phase T2/T4: CC, a self-hosted C compiler (tinyC dialect) Adds toolchain/cc: preprocessor, lexer, recursive-descent parser, and a naive x86-32 codegen emitting AS-syntax assembly text, for two targets (--target=kernel: freestanding; --target=host: adds a _start that calls main then exits via a raw syscall). See docs/toolchain.md for the full "tinyC" dialect (types, what's deliberately unsupported and why) and the builtin mechanism (~7 fixed instruction sequences replacing GCC extended inline asm, plus raw Linux syscalls for host tools). Required substantially extending AS beyond what the OS's four hand- written .S files needed: general 32-bit reg/reg, reg/mem, reg/imm forms for add/sub/and/or/xor/cmp, imul/idiv/cdq/not/neg, setcc, lea, movzx with a memory source, the full jcc family, and three new addressing modes ([esp+disp] already existed; added [ebp+disp] always as the disp32 form, [reg] for any other general register, and [label(+disp)] as 32-bit disp32-direct). Caught three real bugs via actual execution, not just compiling clean: - AS's generic 32-bit r/m encoder had no case for a general register base ([ebx] etc) - fell through to the "bare label" case and silently corrupted the address. Caught by a standalone AS extension smoke test before CC even ran through it. - Struct member offsets were computed lazily inside type_size(), but nothing forced that to run at struct-definition time, so `.`/`->` access silently used offset 0 for every member until something incidentally triggered layout - wrong answers, not a crash. Fixed by computing layout eagerly right after a struct's closing brace. - AS's ELF writer computed a defined symbol's section index as a fixed offset from its internal section_kind_t instead of the section's actual assigned ELF header index - wrong the moment a file didn't use all four section kinds in order (e.g. .bss with no .data), pointing every reference at the wrong section and segfaulting at run time. Verified two ways: tests/toolchain/*.c + tools/run_toolchain_tests.sh compile through CC+AS, link with GNU ld (interim until LD exists in Phase T5), run directly, and check exit codes - covering arithmetic, control flow, recursion, pointers/arrays, structs/unions/packed layout, and function pointers. Kernel-target builtins (port I/O, hlt) can't run on the host, so a small kernel-target program using them was linked with the real kernel/entry.S and booted through the real boot.S/ stage2.S in QEMU, printing its expected output over serial. EOF )"
[main 72955af] Phase T2/T4: CC, a self-hosted C compiler (tinyC dialect) 21 files changed, 2999 insertions(+), 63 deletions(-) create mode 100644 tests/toolchain/t01_basic.c create mode 100644 tests/toolchain/t02_control.c create mode 100644 tests/toolchain/t02_types.c create mode 100644 tests/toolchain/t03_recursion.c create mode 100644 tests/toolchain/t04_pointers.c create mode 100644 tests/toolchain/t05_structs.c create mode 100644 toolchain/cc/builtins.c create mode 100644 toolchain/cc/cc.h create mode 100644 toolchain/cc/codegen.c create mode 100644 toolchain/cc/lexer.c create mode 100644 toolchain/cc/main.c create mode 100644 toolchain/cc/parser.c create mode 100644 toolchain/cc/preprocessor.c create mode 100644 toolchain/cc/types.c create mode 100644 toolchain/include/stddef.h create mode 100644 toolchain/include/stdint.h create mode 100755 tools/run_toolchain_tests.sh
{
"taskId": "10",
"status": "completed"
}Updated task #10 status
{
"taskId": "12",
"status": "completed"
}Updated task #12 status
{
"query": "select:TaskUpdate",
"max_results": 3
}{
"taskId": "11",
"status": "in_progress"
}Updated task #11 status
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -rn "__asm__" kernel/ | grep -v "kernel/lang"
kernel/idt.c:43: __asm__ volatile ("lidt %0" : : "m"(idtp));
kernel/serial.c:8: __asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
kernel/serial.c:13: __asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
kernel/ata.c:21: __asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
kernel/ata.c:26: __asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
kernel/ata.c:31: __asm__ volatile ("outw %0, %1" : : "a"(val), "Nd"(port));
kernel/ata.c:36: __asm__ volatile ("inw %1, %0" : "=a"(ret) : "Nd"(port));
kernel/keyboard.c:14: __asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
kernel/gdt.c:43: __asm__ volatile (
kernel/shell/shell.c:18: __asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
kernel/shell/shell.c:23: __asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
kernel/shell/shell.c:208: __asm__ volatile ("hlt");1 #include "serial.h"
2
3 #include <stdint.h>
4
5 #define COM1 0x3f8
6
7 static inline void outb(uint16_t port, uint8_t val) {
8 __asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
9 }
10
11 static inline uint8_t inb(uint16_t port) {
12 uint8_t ret;
13 __asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
14 return ret;
15 }
16
17 void serial_init(void) {
18 outb(COM1 + 1, 0x00); /* disable interrupts */
19 outb(COM1 + 3, 0x80); /* enable DLAB */
20 outb(COM1 + 0, 0x03); /* divisor low byte: 38400 baud */
21 outb(COM1 + 1, 0x00); /* divisor high byte */
22 outb(COM1 + 3, 0x03); /* 8 bits, no parity, one stop bit */
23 outb(COM1 + 2, 0xC7); /* enable FIFO, clear, 14-byte threshold */
24 outb(COM1 + 4, 0x0B); /* IRQs disabled, RTS/DSR set */
25 }
26
27 static int transmit_empty(void) {
28 return inb(COM1 + 5) & 0x20;
29 }
30
31 void serial_putc(char c) {
32 while (!transmit_empty()) {}
33 outb(COM1, (uint8_t)c);
34 }
35
36 void serial_write(const char *s) {
37 while (*s) {
38 serial_putc(*s++);
39 }
40 }
41 1 #include "ata.h"
2
3 #define ATA_IO_BASE 0x1F0
4 #define ATA_DATA (ATA_IO_BASE + 0)
5 #define ATA_SECCOUNT (ATA_IO_BASE + 2)
6 #define ATA_LBA_LO (ATA_IO_BASE + 3)
7 #define ATA_LBA_MID (ATA_IO_BASE + 4)
8 #define ATA_LBA_HI (ATA_IO_BASE + 5)
9 #define ATA_DRIVE (ATA_IO_BASE + 6)
10 #define ATA_STATUS (ATA_IO_BASE + 7)
11 #define ATA_COMMAND (ATA_IO_BASE + 7)
12
13 #define ATA_CMD_READ 0x20
14 #define ATA_CMD_WRITE 0x30
15 #define ATA_CMD_CACHE_FLUSH 0xE7
16
17 #define ATA_SR_BSY 0x80
18 #define ATA_SR_DRQ 0x08
19
20 static inline void outb(uint16_t port, uint8_t val) {
21 __asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
22 }
23
24 static inline uint8_t inb(uint16_t port) {
25 uint8_t ret;
26 __asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
27 return ret;
28 }
29
30 static inline void outw(uint16_t port, uint16_t val) {
31 __asm__ volatile ("outw %0, %1" : : "a"(val), "Nd"(port));
32 }
33
34 static inline uint16_t inw(uint16_t port) {
35 uint16_t ret;
36 __asm__ volatile ("inw %1, %0" : "=a"(ret) : "Nd"(port));
37 return ret;
38 }
39
40 static void ata_wait_bsy(void) {
41 while (inb(ATA_STATUS) & ATA_SR_BSY) {}
42 }
43
44 static void ata_wait_drq(void) {
45 while (!(inb(ATA_STATUS) & ATA_SR_DRQ)) {}
46 }
47
48 static void ata_select(uint32_t lba) {
49 outb(ATA_DRIVE, (uint8_t)(0xE0 | ((lba >> 24) & 0x0F)));
50 outb(ATA_SECCOUNT, 1);
51 outb(ATA_LBA_LO, (uint8_t)(lba & 0xFF));
52 outb(ATA_LBA_MID, (uint8_t)((lba >> 8) & 0xFF));
53 outb(ATA_LBA_HI, (uint8_t)((lba >> 16) & 0xFF));
54 }
55
56 void ata_read_sector(uint32_t lba, void *buf) {
57 ata_wait_bsy();
58 ata_select(lba);
59 outb(ATA_COMMAND, ATA_CMD_READ);
60 ata_wait_bsy();
61 ata_wait_drq();
62
63 uint16_t *p = buf;
64 for (int i = 0; i < 256; i++) {
65 p[i] = inw(ATA_DATA);
66 }
67 }
68
69 void ata_write_sector(uint32_t lba, const void *buf) {
70 ata_wait_bsy();
71 ata_select(lba);
72 outb(ATA_COMMAND, ATA_CMD_WRITE);
73 ata_wait_bsy();
74 ata_wait_drq();
75
76 const uint16_t *p = buf;
77 for (int i = 0; i < 256; i++) {
78 outw(ATA_DATA, p[i]);
79 }
80
81 outb(ATA_COMMAND, ATA_CMD_CACHE_FLUSH);
82 ata_wait_bsy();
83 }
84 - static inline void outb(uint16_t port, uint8_t val) {
- __asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
- }
-
- static inline uint8_t inb(uint16_t port) {
- uint8_t ret;
- __asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
- return ret;
- }
-
- static inline void outw(uint16_t port, uint16_t val) {
- __asm__ volatile ("outw %0, %1" : : "a"(val), "Nd"(port));
- }
-
- static inline uint16_t inw(uint16_t port) {
- uint16_t ret;
- __asm__ volatile ("inw %1, %0" : "=a"(ret) : "Nd"(port));
- return ret;
- }
+ /* Thin names over the compiler's fixed port-I/O builtins (see
+ * docs/toolchain.md) - kept so the many call sites below read the same
+ * as before rather than spelling out __builtin_* everywhere. */
+ static inline void outb(uint16_t port, uint8_t val) { __builtin_outb(port, val); }
+ static inline uint8_t inb(uint16_t port) { return __builtin_inb(port); }
+ static inline void outw(uint16_t port, uint16_t val) { __builtin_outw(port, val); }
+ static inline uint16_t inw(uint16_t port) { return __builtin_inw(port); }The file /home/martin/workspace/prototypes/boot-to-claude/kernel/ata.c has been updated successfully. (file state is current in your context — no need to Read it back)
#pragma once /* Declares the fixed set of low-level operations the kernel needs that * C can't express portably - port I/O, halting, loading the IDT/GDT. * Under CC (the self-hosted compiler, see docs/toolchain.md), calls to * these exact names are recognized in codegen and compiled directly to * a handful of instructions - no function body is ever compiled or * linked for them. Under the current interim GCC-based build, real * definitions live in kernel/builtins_shim.c (GCC inline asm) purely so * the same kernel source keeps building until Phase T7 switches the * default build over to CC, at which point builtins_shim.c is dropped * (CC never needs it - every call site is intercepted before linking). */ #include <stdint.h> void __builtin_outb(uint16_t port, uint8_t val); uint8_t __builtin_inb(uint16_t port); void __builtin_outw(uint16_t port, uint16_t val); uint16_t __builtin_inw(uint16_t port); void __builtin_hlt(void); void __builtin_lidt(void *idt_descriptor); void __builtin_gdt_flush(void *gdt_descriptor);
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/builtins.h (file state is current in your context — no need to Read it back)
$ cat > /tmp/test_builtin_name.c <<'EOF'
void __builtin_outb(unsigned short port, unsigned char val) {
__asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
}
int main(void) {
__builtin_outb(0x3f8, 65);
return 0;
}
EOF
gcc -m32 -Wall -Wextra -std=c11 -c /tmp/test_builtin_name.c -o /tmp/test_builtin_name.o 2>&1
echo "exit=$?"exit=0
/* Real GCC inline-asm bodies for the builtins declared in builtins.h -
* exists only so the kernel keeps building under the current interim
* GCC-based toolchain. Dropped in Phase T7 once the default build
* switches to CC, which intercepts every call site in codegen and never
* needs (or could compile - it has no inline-asm support at all) a
* linkable definition for these. Do not add any behavior here that
* isn't already implemented by CC's builtins.c. */
#include "builtins.h"
void __builtin_outb(uint16_t port, uint8_t val) {
__asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
}
uint8_t __builtin_inb(uint16_t port) {
uint8_t ret;
__asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
return ret;
}
void __builtin_outw(uint16_t port, uint16_t val) {
__asm__ volatile ("outw %0, %1" : : "a"(val), "Nd"(port));
}
uint16_t __builtin_inw(uint16_t port) {
uint16_t ret;
__asm__ volatile ("inw %1, %0" : "=a"(ret) : "Nd"(port));
return ret;
}
void __builtin_hlt(void) {
__asm__ volatile ("hlt");
}
void __builtin_lidt(void *idt_descriptor) {
__asm__ volatile ("lidt (%0)" : : "r"(idt_descriptor));
}
void __builtin_gdt_flush(void *gdt_descriptor) {
__asm__ volatile (
"lgdt (%0)\n"
"mov $0x10, %%ax\n"
"mov %%ax, %%ds\n"
"mov %%ax, %%es\n"
"mov %%ax, %%fs\n"
"mov %%ax, %%gs\n"
"mov %%ax, %%ss\n"
"ljmp $0x08, $1f\n"
"1:\n"
:
: "r"(gdt_descriptor)
: "eax"
);
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/builtins_shim.c (file state is current in your context — no need to Read it back)
1 #include "ata.h" 2 3 #define ATA_IO_BASE 0x1F0 4 #define ATA_DATA (ATA_IO_BASE + 0) 5 #define ATA_SECCOUNT (ATA_IO_BASE + 2) 6 #define ATA_LBA_LO (ATA_IO_BASE + 3) 7 #define ATA_LBA_MID (ATA_IO_BASE + 4) 8 #define ATA_LBA_HI (ATA_IO_BASE + 5) 9 #define ATA_DRIVE (ATA_IO_BASE + 6) 10 #define ATA_STATUS (ATA_IO_BASE + 7) 11 #define ATA_COMMAND (ATA_IO_BASE + 7) 12 13 #define ATA_CMD_READ 0x20 14 #define ATA_CMD_WRITE 0x30 15 #define ATA_CMD_CACHE_FLUSH 0xE7
- #include "ata.h" - - #define ATA_IO_BASE 0x1F0 + #include "ata.h" + #include "builtins.h" + + #define ATA_IO_BASE 0x1F0
The file /home/martin/workspace/prototypes/boot-to-claude/kernel/ata.c has been updated successfully. (file state is current in your context — no need to Read it back)
- #include "serial.h"
-
- #include <stdint.h>
-
- #define COM1 0x3f8
-
- static inline void outb(uint16_t port, uint8_t val) {
- __asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
- }
-
- static inline uint8_t inb(uint16_t port) {
- uint8_t ret;
- __asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
- return ret;
- }
+ #include "serial.h"
+
+ #include <stdint.h>
+
+ #include "builtins.h"
+
+ #define COM1 0x3f8
+
+ /* Thin names over the compiler's fixed port-I/O builtins (see
+ * docs/toolchain.md) - kept so the call sites below read the same as
+ * before rather than spelling out __builtin_* everywhere. */
+ static inline void outb(uint16_t port, uint8_t val) { __builtin_outb(port, val); }
+ static inline uint8_t inb(uint16_t port) { return __builtin_inb(port); }The file /home/martin/workspace/prototypes/boot-to-claude/kernel/serial.c has been updated successfully. (file state is current in your context — no need to Read it back)
1 #include "keyboard.h"
2
3 #include <stddef.h>
4 #include <stdint.h>
5
6 #define KBD_DATA 0x60
7 #define KBD_STATUS 0x64
8
9 #define SC_LSHIFT 0x2A
10 #define SC_RSHIFT 0x36
11
12 static inline uint8_t inb(uint16_t port) {
13 uint8_t ret;
14 __asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
15 return ret;
16 }
17
18 /* Scancode set 1, indexed by make code. 0 = unmapped/non-printable. */
19 static const char lower_table[] = {
20 0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
21 '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
22 0 /*ctrl*/, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`',
23 0 /*lshift*/, '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/',
24 0 /*rshift*/, '*', 0 /*alt*/, ' ',
25 };- #include "keyboard.h"
-
- #include <stddef.h>
- #include <stdint.h>
-
- #define KBD_DATA 0x60
- #define KBD_STATUS 0x64
-
- #define SC_LSHIFT 0x2A
- #define SC_RSHIFT 0x36
-
- static inline uint8_t inb(uint16_t port) {
- uint8_t ret;
- __asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
- return ret;
- }
+ #include "keyboard.h"
+
+ #include <stddef.h>
+ #include <stdint.h>
+
+ #include "builtins.h"
+
+ #define KBD_DATA 0x60
+ #define KBD_STATUS 0x64
+
+ #define SC_LSHIFT 0x2A
+ #define SC_RSHIFT 0x36
+
+ static inline uint8_t inb(uint16_t port) { return __builtin_inb(port); }The file /home/martin/workspace/prototypes/boot-to-claude/kernel/keyboard.c has been updated successfully. (file state is current in your context — no need to Read it back)
1 #include "shell.h"
2
3 #include <stdint.h>
4
5 #include "console.h"
6 #include "fs/tinyfs.h"
7 #include "keyboard.h"
8 #include "lang/compiler/codegen.h"
9 #include "lang/compiler/parser.h"
10 #include "lang/script/eval.h"
11 #include "lang/vm.h"
12 #include "serial.h"
13 #include "string.h"
14
15 #define LINE_MAX 128
16
17 static inline void outb(uint16_t port, uint8_t val) {
18 __asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
19 }
20
21 static inline uint8_t inb(uint16_t port) {
22 uint8_t ret;
23 __asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
24 return ret;
25 }
26
27 static void echo_char(char c) {
28 console_putc(c);
29 serial_putc(c);
30 }- #include "console.h"
- #include "fs/tinyfs.h"
- #include "keyboard.h"
- #include "lang/compiler/codegen.h"
- #include "lang/compiler/parser.h"
- #include "lang/script/eval.h"
- #include "lang/vm.h"
- #include "serial.h"
- #include "string.h"
-
- #define LINE_MAX 128
-
- static inline void outb(uint16_t port, uint8_t val) {
- __asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
- }
-
- static inline uint8_t inb(uint16_t port) {
- uint8_t ret;
- __asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
- return ret;
- }
+ #include "builtins.h"
+ #include "console.h"
+ #include "fs/tinyfs.h"
+ #include "keyboard.h"
+ #include "lang/compiler/codegen.h"
+ #include "lang/compiler/parser.h"
+ #include "lang/script/eval.h"
+ #include "lang/vm.h"
+ #include "serial.h"
+ #include "string.h"
+
+ #define LINE_MAX 128
+
+ static inline void outb(uint16_t port, uint8_t val) { __builtin_outb(port, val); }
+ static inline uint8_t inb(uint16_t port) { return __builtin_inb(port); }The file /home/martin/workspace/prototypes/boot-to-claude/kernel/shell/shell.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ grep -n "__asm__\|hlt" /home/martin/workspace/prototypes/boot-to-claude/kernel/shell/shell.c
202: __asm__ volatile ("hlt");193
194 static void cmd_reboot(void) {
195 print_line("rebooting...");
196 uint8_t status;
197 do {
198 status = inb(0x64);
199 } while (status & 0x02);
200 outb(0x64, 0xFE); /* pulse the keyboard controller's reset line */
201 for (;;) {
202 __asm__ volatile ("hlt");
203 }
204 }- outb(0x64, 0xFE); /* pulse the keyboard controller's reset line */
- for (;;) {
- __asm__ volatile ("hlt");
- }
- }
+ outb(0x64, 0xFE); /* pulse the keyboard controller's reset line */
+ for (;;) {
+ __builtin_hlt();
+ }
+ }The file /home/martin/workspace/prototypes/boot-to-claude/kernel/shell/shell.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 "__asm__" kernel/ | grep -v "kernel/lang" echo "---" grep -rn '"hlt"' kernel/
kernel/builtins_shim.c:12: __asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
kernel/builtins_shim.c:17: __asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
kernel/builtins_shim.c:22: __asm__ volatile ("outw %0, %1" : : "a"(val), "Nd"(port));
kernel/builtins_shim.c:27: __asm__ volatile ("inw %1, %0" : "=a"(ret) : "Nd"(port));
kernel/builtins_shim.c:32: __asm__ volatile ("hlt");
kernel/builtins_shim.c:36: __asm__ volatile ("lidt (%0)" : : "r"(idt_descriptor));
kernel/builtins_shim.c:40: __asm__ volatile (
kernel/gdt.c:43: __asm__ volatile (
kernel/idt.c:43: __asm__ volatile ("lidt %0" : : "m"(idtp));
---
kernel/builtins_shim.c:32: __asm__ volatile ("hlt");1 #include "idt.h"
2
3 #include <stdint.h>
4
5 #include "console.h"
6 #include "serial.h"
7 #include "string.h"
8
9 struct idt_entry {
10 uint16_t offset_low;
11 uint16_t selector;
12 uint8_t zero;
13 uint8_t type_attr;
14 uint16_t offset_high;
15 } __attribute__((packed));
16
17 struct idt_ptr {
18 uint16_t limit;
19 uint32_t base;
20 } __attribute__((packed));
21
22 extern uint32_t isr_stub_table[32];
23
24 static struct idt_entry idt[256];
25 static struct idt_ptr idtp;
26
27 static void idt_set_gate(int n, uint32_t handler) {
28 idt[n].offset_low = handler & 0xFFFF;
29 idt[n].selector = 0x08; /* kernel code segment, see gdt.c */
30 idt[n].zero = 0;
31 idt[n].type_attr = 0x8E; /* present, ring0, 32-bit interrupt gate */
32 idt[n].offset_high = (handler >> 16) & 0xFFFF;
33 }
34
35 void idt_init(void) {
36 idtp.limit = sizeof(idt) - 1;
37 idtp.base = (uint32_t)&idt;
38
39 for (int i = 0; i < 32; i++) {
40 idt_set_gate(i, isr_stub_table[i]);
41 }
42
43 __asm__ volatile ("lidt %0" : : "m"(idtp));
44 }
45
46 /* Called from idt_stubs.S. Never returns - the stub halts right after. */
47 void isr_handler(uint32_t vector, uint32_t error_code) {
48 char numbuf[16];
49
50 console_write("\nEXCEPTION vector=");
51 console_write(itoa((int)vector, numbuf, 10));
52 console_write(" error_code=");
53 console_write(itoa((int)error_code, numbuf, 10));
54 console_write(" - halted\n");
55
56 serial_write("\nEXCEPTION vector=");
57 serial_write(itoa((int)vector, numbuf, 10));
58 serial_write(" error_code=");
59 serial_write(itoa((int)error_code, numbuf, 10));
60 serial_write(" - halted\n");
61 }
62 - #include "idt.h" - - #include <stdint.h> - - #include "console.h" - #include "serial.h" - #include "string.h" + #include "idt.h" + + #include <stdint.h> + + #include "builtins.h" + #include "console.h" + #include "serial.h" + #include "string.h"
The file /home/martin/workspace/prototypes/boot-to-claude/kernel/idt.c has been updated successfully. (file state is current in your context — no need to Read it back)
- __asm__ volatile ("lidt %0" : : "m"(idtp));
+ __builtin_lidt(&idtp);The file /home/martin/workspace/prototypes/boot-to-claude/kernel/idt.c has been updated successfully. (file state is current in your context — no need to Read it back)
1 #include "gdt.h"
2
3 #include <stdint.h>
4
5 /* The kernel owns its GDT rather than relying on the one stage2 built on
6 * the real-mode stack - stage2's copy happens to still be valid at this
7 * point, but the kernel should not depend on bootloader memory. */
8
9 struct gdt_entry {
10 uint16_t limit_low;
11 uint16_t base_low;
12 uint8_t base_mid;
13 uint8_t access;
14 uint8_t granularity;
15 uint8_t base_high;
16 } __attribute__((packed));
17
18 struct gdt_ptr {
19 uint16_t limit;
20 uint32_t base;
21 } __attribute__((packed));
22
23 static struct gdt_entry gdt[3];
24 static struct gdt_ptr gdtp;
25
26 static void gdt_set_gate(int n, uint32_t base, uint32_t limit, uint8_t access, uint8_t gran) {
27 gdt[n].base_low = base & 0xFFFF;
28 gdt[n].base_mid = (base >> 16) & 0xFF;
29 gdt[n].base_high = (base >> 24) & 0xFF;
30 gdt[n].limit_low = limit & 0xFFFF;
31 gdt[n].granularity = (uint8_t)(((limit >> 16) & 0x0F) | (gran & 0xF0));
32 gdt[n].access = access;
33 }
34
35 void gdt_init(void) {
36 gdtp.limit = sizeof(gdt) - 1;
37 gdtp.base = (uint32_t)&gdt;
38
39 gdt_set_gate(0, 0, 0, 0, 0); /* null descriptor */
40 gdt_set_gate(1, 0, 0xFFFFF, 0x9A, 0xC0); /* 0x08: code, ring0, 4GB flat */
41 gdt_set_gate(2, 0, 0xFFFFF, 0x92, 0xC0); /* 0x10: data, ring0, 4GB flat */
42
43 __asm__ volatile (
44 "lgdt %0\n"
45 "mov $0x10, %%ax\n"
46 "mov %%ax, %%ds\n"
47 "mov %%ax, %%es\n"
48 "mov %%ax, %%fs\n"
49 "mov %%ax, %%gs\n"
50 "mov %%ax, %%ss\n"
51 "ljmp $0x08, $1f\n"
52 "1:\n"
53 :
54 : "m"(gdtp)
55 : "eax"
56 );
57 }
58 - #include "gdt.h" - - #include <stdint.h> + #include "gdt.h" + + #include <stdint.h> + + #include "builtins.h"
The file /home/martin/workspace/prototypes/boot-to-claude/kernel/gdt.c has been updated successfully. (file state is current in your context — no need to Read it back)
- __asm__ volatile ( - "lgdt %0\n" - "mov $0x10, %%ax\n" - "mov %%ax, %%ds\n" - "mov %%ax, %%es\n" - "mov %%ax, %%fs\n" - "mov %%ax, %%gs\n" - "mov %%ax, %%ss\n" - "ljmp $0x08, $1f\n" - "1:\n" - : - : "m"(gdtp) - : "eax" - ); - } + __builtin_gdt_flush(&gdtp); + }
The file /home/martin/workspace/prototypes/boot-to-claude/kernel/gdt.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 "__asm__" kernel/ | grep -v "kernel/lang" | grep -v "builtins_shim.c" echo "should be empty above"
should be empty above
1 AS := nasm 2 CC := gcc 3 HOSTCC := gcc 4 LD := ld 5 OBJCOPY := objcopy 6 QEMU := qemu-system-i386 7 BUILD := build 8 9 CFLAGS := -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib \ 10 -Wall -Wextra -O2 -std=c11 -Ikernel 11 LDFLAGS := -m elf_i386 -T kernel/link.ld -nostdlib 12 13 # Must match TINYFS_DISK_SECTORS / TINYFS_FS_START_LBA in kernel/fs/tinyfs.h 14 # and KERNEL_START_LBA / KERNEL_SECTORS in boot/stage2.S 15 DISK_SECTORS := 4096 16 KERNEL_START_SECTOR := 5 17 KERNEL_MAX_SECTORS := 512 18 19 BOOT_BIN := $(BUILD)/boot.bin 20 STAGE2_BIN := $(BUILD)/stage2.bin 21 DISK_IMG := $(BUILD)/disk.img 22 23 KERNEL_C_SRCS := kernel/kmain.c kernel/console.c kernel/serial.c kernel/gdt.c \ 24 kernel/idt.c kernel/string.c kernel/memory.c kernel/selftest.c \ 25 kernel/ata.c kernel/fs/tinyfs.c kernel/fs/tinyfs_disk.c \ 26 kernel/keyboard.c kernel/shell/shell.c \ 27 kernel/lang/lexer.c kernel/lang/script/ast.c \ 28 kernel/lang/script/parser.c kernel/lang/script/eval.c \ 29 kernel/lang/compiler/parser.c kernel/lang/compiler/codegen.c \ 30 kernel/lang/vm.c 31 KERNEL_ASM_SRCS := kernel/entry.S kernel/idt_stubs.S 32 KERNEL_OBJS := $(patsubst kernel/%.S,$(BUILD)/%.o,$(KERNEL_ASM_SRCS)) \ 33 $(patsubst kernel/%.c,$(BUILD)/%.o,$(KERNEL_C_SRCS)) 34 KERNEL_ELF := $(BUILD)/kernel.elf 35 KERNEL_BIN := $(BUILD)/kernel.bin 36 37 MKFS_BIN := $(BUILD)/mkfs_tinyfs 38 MKFS_OBJS := $(BUILD)/host_tinyfs.o $(BUILD)/host_mkfs_tinyfs.o 39 # host-path:fs-name pairs seeded into every disk image; the host paths 40 # alone are listed separately so they can be used as make prerequisites 41 MKFS_SEED_FILES := tests/hello.tsh tests/hello.tc 42 MKFS_SEED_SPECS := tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc 43 44 .PHONY: all run run-gui clean 45 46 all: $(DISK_IMG) 47 48 BUILD_SUBDIRS := $(BUILD) $(BUILD)/fs $(BUILD)/shell $(BUILD)/lang $(BUILD)/lang/script $(BUILD)/lang/compiler 49 50 $(BUILD_SUBDIRS): 51 mkdir -p $@ 52 53 $(BOOT_BIN): boot/boot.S | $(BUILD) 54 $(AS) -f bin -o $@ $< 55 56 $(STAGE2_BIN): boot/stage2.S | $(BUILD) 57 $(AS) -f bin -o $@ $< 58 59 $(BUILD)/%.o: kernel/%.S | $(BUILD_SUBDIRS) 60 $(AS) -f elf32 -o $@ $< 61 62 $(BUILD)/%.o: kernel/%.c | $(BUILD_SUBDIRS) 63 $(CC) $(CFLAGS) -c -o $@ $< 64 65 $(KERNEL_ELF): $(KERNEL_OBJS) kernel/link.ld 66 $(LD) $(LDFLAGS) -o $@ $(KERNEL_OBJS) 67 68 $(KERNEL_BIN): $(KERNEL_ELF) 69 $(OBJCOPY) -O binary $< $@ 70 @size=$$(stat -c%s $@); budget=$$(( $(KERNEL_MAX_SECTORS) * 512 )); \ 71 if [ "$$size" -gt "$$budget" ]; then \ 72 echo "error: $@ is $$size bytes, exceeds the $$budget-byte ($(KERNEL_MAX_SECTORS)-sector) boot budget" >&2; \ 73 echo " bump KERNEL_SECTORS in boot/stage2.S, KERNEL_MAX_SECTORS here, and TINYFS_FS_START_LBA in kernel/fs/tinyfs.h together" >&2; \ 74 exit 1; \ 75 fi 76 77 # --- host-side mkfs tool (build-time infrastructure, not kernel code) --- 78 79 $(BUILD)/host_tinyfs.o: kernel/fs/tinyfs.c | $(BUILD) 80 $(HOSTCC) -Wall -Wextra -O2 -c -o $@ $< 81 82 $(BUILD)/host_mkfs_tinyfs.o: tools/mkfs_tinyfs.c | $(BUILD) 83 $(HOSTCC) -Wall -Wextra -O2 -c -o $@ $< 84 85 $(MKFS_BIN): $(MKFS_OBJS) 86 $(HOSTCC) -o $@ $(MKFS_OBJS) 87 88 $(DISK_IMG): $(BOOT_BIN) $(STAGE2_BIN) $(KERNEL_BIN) $(MKFS_BIN) $(MKFS_SEED_FILES) 89 dd if=/dev/zero of=$@ bs=512 count=$(DISK_SECTORS) status=none 90 dd if=$(BOOT_BIN) of=$@ conv=notrunc status=none 91 dd if=$(STAGE2_BIN) of=$@ bs=512 seek=1 conv=notrunc status=none 92 dd if=$(KERNEL_BIN) of=$@ bs=512 seek=$(KERNEL_START_SECTOR) conv=notrunc status=none 93 $(MKFS_BIN) $@ $(MKFS_SEED_SPECS) 94 95 # Headless: no window at all, so there's no input surface for the emulated 96 # PS/2 keyboard - useful for a quick boot sanity check or scripting, but 97 # you cannot type into the shell this way. Use `make run-gui` to interact. 98 run: $(DISK_IMG) 99 $(QEMU) -drive file=$(DISK_IMG),format=raw -display none -serial stdio -no-reboot 100 101 # Opens a real window - click into it, then type. Serial output is also 102 # echoed to this terminal, but keystrokes only reach the guest via the 103 # window. 104 run-gui: $(DISK_IMG) 105 $(QEMU) -drive file=$(DISK_IMG),format=raw -serial stdio -no-reboot 106 107 clean: 108 rm -rf $(BUILD) 109
- KERNEL_C_SRCS := kernel/kmain.c kernel/console.c kernel/serial.c kernel/gdt.c \ - kernel/idt.c kernel/string.c kernel/memory.c kernel/selftest.c \ - kernel/ata.c kernel/fs/tinyfs.c kernel/fs/tinyfs_disk.c \ - kernel/keyboard.c kernel/shell/shell.c \ + KERNEL_C_SRCS := kernel/kmain.c kernel/console.c kernel/serial.c kernel/gdt.c \ + kernel/idt.c kernel/string.c kernel/memory.c kernel/selftest.c \ + kernel/ata.c kernel/fs/tinyfs.c kernel/fs/tinyfs_disk.c \ + kernel/keyboard.c kernel/shell/shell.c kernel/builtins_shim.c \
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 ./tools/run_tests.sh 2>&1 | tail -15
ld: warning: build/kernel.elf has a LOAD segment with RWX permissions run_tests: PASS qemu-system-i386: terminating on signal 15 from pid 2912960 (bash)
$ grep -c "(value_t)" /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/script/eval.c grep -n "(value_t)" /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/script/eval.c | head -30
22
117: return (value_t){ VAL_STR, { .s = str_concat(ls, rs) } };
128: return (value_t){ VAL_INT, { .i = (op == TOK_EQ) ? eq : !eq } };
134: case TOK_PLUS: return (value_t){ VAL_INT, { .i = li + ri } };
135: case TOK_MINUS: return (value_t){ VAL_INT, { .i = li - ri } };
136: case TOK_STAR: return (value_t){ VAL_INT, { .i = li * ri } };
137: case TOK_SLASH: return (value_t){ VAL_INT, { .i = ri != 0 ? li / ri : 0 } };
138: case TOK_PERCENT: return (value_t){ VAL_INT, { .i = ri != 0 ? li % ri : 0 } };
139: case TOK_LT: return (value_t){ VAL_INT, { .i = li < ri } };
140: case TOK_LE: return (value_t){ VAL_INT, { .i = li <= ri } };
141: case TOK_GT: return (value_t){ VAL_INT, { .i = li > ri } };
142: case TOK_GE: return (value_t){ VAL_INT, { .i = li >= ri } };
211: return (value_t){ VAL_STR, { .s = out } };
213: return (value_t){ VAL_STR, { .s = "" } };
220: return (value_t){ VAL_INT, { .i = rc == 0 } };
232: return (value_t){ VAL_INT, { .i = n } };
254: return (value_t){ VAL_INT, { .i = node->num } };
256: return (value_t){ VAL_STR, { .s = node->str } };
267: return (value_t){ VAL_INT, { .i = -to_int(v) } };
269: return (value_t){ VAL_INT, { .i = !to_int(v) } };
278: return (value_t){ VAL_INT, { .i = to_int(r) != 0 } };
283: return (value_t){ VAL_INT, { .i = 1 } };
286: return (value_t){ VAL_INT, { .i = to_int(r) != 0 } };1 #include "eval.h"
2
3 #include "../../console.h"
4 #include "../../fs/tinyfs.h"
5 #include "../../memory.h"
6 #include "../../serial.h"
7 #include "../../string.h"
8 #include "ast.h"
9 #include "parser.h"
10
11 typedef enum { VAL_INT, VAL_STR } value_type_t;
12
13 typedef struct {
14 value_type_t type;
15 union {
16 int32_t i;
17 char *s;
18 } as;
19 } value_t;
20
21 #define MAX_VARS 32
22
23 typedef struct {
24 char names[MAX_VARS][TOKEN_TEXT_MAX];
25 value_t values[MAX_VARS];
26 int count;
27 } env_t;
28
29 #define MAX_FUNCS 16
30
31 typedef struct {
32 char name[TOKEN_TEXT_MAX];
33 ast_node_t *def;
34 } func_entry_t;
35
36 typedef struct {
37 func_entry_t funcs[MAX_FUNCS];
38 int func_count;
39 } interp_t;
40
41 typedef enum { EXEC_NORMAL, EXEC_RETURN } exec_signal_t;
42
43 typedef struct {
44 exec_signal_t signal;
45 value_t return_value;
46 } exec_result_t;
47
48 static const value_t VAL_ZERO = { VAL_INT, { .i = 0 } };
49
50 static int env_find(env_t *env, const char *name) {
51 for (int i = 0; i < env->count; i++) {
52 if (strcmp(env->names[i], name) == 0) {
53 return i;
54 }
55 }
56 return -1;
57 }
58
59 static value_t env_get(env_t *env, const char *name) {
60 int i = env_find(env, name);
61 return i < 0 ? VAL_ZERO : env->values[i];
62 }
63
64 static void env_set(env_t *env, const char *name, value_t v) {
65 int i = env_find(env, name);
66 if (i >= 0) {
67 env->values[i] = v;
68 return;
69 }
70 if (env->count < MAX_VARS) {
71 int n = 0;
72 while (name[n] && n < TOKEN_TEXT_MAX - 1) {
73 env->names[env->count][n] = name[n];
74 n++;
75 }
76 env->names[env->count][n] = '\0';
77 env->values[env->count] = v;
78 env->count++;
79 }
80 /* else: variable table full - silently dropped, tiny scope */
81 }
82
83 static int to_int(value_t v) {
84 if (v.type == VAL_INT) {
85 return v.as.i;
86 }
87 return (v.as.s && v.as.s[0]) ? 1 : 0;
88 }
89
90 static char *str_concat(const char *a, const char *b) {
91 size_t la = strlen(a);
92 size_t lb = strlen(b);
93 char *r = kmalloc(la + lb + 1);
94 memcpy(r, a, la);
95 memcpy(r + la, b, lb);
96 r[la + lb] = '\0';
97 return r;
98 }
99
100 static void print_value(value_t v) {
101 if (v.type == VAL_STR) {
102 console_write(v.as.s);
103 serial_write(v.as.s);
104 } else {
105 char buf[16];
106 itoa(v.as.i, buf, 10);
107 console_write(buf);
108 serial_write(buf);
109 }
110 }
111
112 static value_t eval_binop(token_type_t op, value_t l, value_t r) {
113 if (op == TOK_PLUS && (l.type == VAL_STR || r.type == VAL_STR)) {
114 char lb[16], rb[16];
115 const char *ls = (l.type == VAL_STR) ? l.as.s : itoa(l.as.i, lb, 10);
116 const char *rs = (r.type == VAL_STR) ? r.as.s : itoa(r.as.i, rb, 10);
117 return (value_t){ VAL_STR, { .s = str_concat(ls, rs) } };
118 }
119 if (op == TOK_EQ || op == TOK_NEQ) {
120 int eq;
121 if (l.type == VAL_STR && r.type == VAL_STR) {
122 eq = strcmp(l.as.s, r.as.s) == 0;
123 } else if (l.type == VAL_INT && r.type == VAL_INT) {
124 eq = l.as.i == r.as.i;
125 } else {
126 eq = 0;
127 }
128 return (value_t){ VAL_INT, { .i = (op == TOK_EQ) ? eq : !eq } };
129 }
130
131 int li = to_int(l);
132 int ri = to_int(r);
133 switch (op) {
134 case TOK_PLUS: return (value_t){ VAL_INT, { .i = li + ri } };
135 case TOK_MINUS: return (value_t){ VAL_INT, { .i = li - ri } };
136 case TOK_STAR: return (value_t){ VAL_INT, { .i = li * ri } };
137 case TOK_SLASH: return (value_t){ VAL_INT, { .i = ri != 0 ? li / ri : 0 } };
138 case TOK_PERCENT: return (value_t){ VAL_INT, { .i = ri != 0 ? li % ri : 0 } };
139 case TOK_LT: return (value_t){ VAL_INT, { .i = li < ri } };
140 case TOK_LE: return (value_t){ VAL_INT, { .i = li <= ri } };
141 case TOK_GT: return (value_t){ VAL_INT, { .i = li > ri } };
142 case TOK_GE: return (value_t){ VAL_INT, { .i = li >= ri } };
143 default: return VAL_ZERO;
144 }
145 }
146
147 static ast_node_t *find_func(interp_t *interp, const char *name) {
148 for (int i = 0; i < interp->func_count; i++) {
149 if (strcmp(interp->funcs[i].name, name) == 0) {
150 return interp->funcs[i].def;
151 }
152 }
153 return NULL;
154 }
155
156 static void register_func(interp_t *interp, ast_node_t *def) {
157 if (interp->func_count >= MAX_FUNCS) {
158 return;
159 }
160 func_entry_t *e = &interp->funcs[interp->func_count++];
161 int n = 0;
162 while (def->name[n] && n < TOKEN_TEXT_MAX - 1) {
163 e->name[n] = def->name[n];
164 n++;
165 }
166 e->name[n] = '\0';
167 e->def = def;
168 }
169
170 static value_t eval_expr(interp_t *interp, env_t *env, ast_node_t *node);
171 static exec_result_t exec_stmt(interp_t *interp, env_t *env, ast_node_t *node);
172
173 static exec_result_t exec_block(interp_t *interp, env_t *env, ast_node_t *block) {
174 exec_result_t r = { EXEC_NORMAL, VAL_ZERO };
175 for (ast_node_t *s = block->list; s; s = s->next) {
176 r = exec_stmt(interp, env, s);
177 if (r.signal == EXEC_RETURN) {
178 return r;
179 }
180 }
181 return r;
182 }
183
184 static value_t eval_call(interp_t *interp, env_t *env, ast_node_t *node) {
185 value_t args[8];
186 int argc = 0;
187 for (ast_node_t *a = node->list; a && argc < 8; a = a->next) {
188 args[argc++] = eval_expr(interp, env, a);
189 }
190
191 if (strcmp(node->name, "print") == 0) {
192 for (int i = 0; i < argc; i++) {
193 if (i > 0) {
194 console_write(" ");
195 serial_write(" ");
196 }
197 print_value(args[i]);
198 }
199 console_write("\n");
200 serial_write("\n");
201 return VAL_ZERO;
202 }
203
204 if (strcmp(node->name, "fread") == 0 && argc >= 1 && args[0].type == VAL_STR) {
205 static char buf[TINYFS_MAX_FILE_SIZE + 1];
206 uint32_t len = 0;
207 if (tinyfs_read(args[0].as.s, buf, sizeof(buf) - 1, &len) == 0) {
208 buf[len] = '\0';
209 char *out = kmalloc(len + 1);
210 memcpy(out, buf, len + 1);
211 return (value_t){ VAL_STR, { .s = out } };
212 }
213 return (value_t){ VAL_STR, { .s = "" } };
214 }
215
216 if (strcmp(node->name, "fwrite") == 0 && argc >= 2 && args[0].type == VAL_STR) {
217 const char *content = (args[1].type == VAL_STR) ? args[1].as.s : "";
218 tinyfs_create(args[0].as.s); /* ignored if it already exists */
219 int rc = tinyfs_write(args[0].as.s, content, (uint32_t)strlen(content));
220 return (value_t){ VAL_INT, { .i = rc == 0 } };
221 }
222
223 if (strcmp(node->name, "flist") == 0) {
224 tinyfs_dirent_t entries[TINYFS_NUM_INODES];
225 int n = tinyfs_list(entries, TINYFS_NUM_INODES);
226 for (int i = 0; i < n; i++) {
227 console_write(entries[i].name);
228 console_write("\n");
229 serial_write(entries[i].name);
230 serial_write("\n");
231 }
232 return (value_t){ VAL_INT, { .i = n } };
233 }
234
235 ast_node_t *def = find_func(interp, node->name);
236 if (!def) {
237 return VAL_ZERO; /* unknown function - tiny scope, no error propagation */
238 }
239
240 env_t local;
241 local.count = 0;
242 int i = 0;
243 for (ast_node_t *pn = def->list; pn; pn = pn->next, i++) {
244 env_set(&local, pn->name, i < argc ? args[i] : VAL_ZERO);
245 }
246
247 exec_result_t r = exec_block(interp, &local, def->b);
248 return r.signal == EXEC_RETURN ? r.return_value : VAL_ZERO;
249 }
250
251 static value_t eval_expr(interp_t *interp, env_t *env, ast_node_t *node) {
252 switch (node->kind) {
253 case AST_NUM:
254 return (value_t){ VAL_INT, { .i = node->num } };
255 case AST_STR:
256 return (value_t){ VAL_STR, { .s = node->str } };
257 case AST_VAR:
258 return env_get(env, node->name);
259 case AST_ASSIGN: {
260 value_t v = eval_expr(interp, env, node->a);
261 env_set(env, node->name, v);
262 return v;
263 }
264 case AST_UNOP: {
265 value_t v = eval_expr(interp, env, node->a);
266 if (node->op == TOK_MINUS) {
267 return (value_t){ VAL_INT, { .i = -to_int(v) } };
268 }
269 return (value_t){ VAL_INT, { .i = !to_int(v) } };
270 }
271 case AST_BINOP: {
272 if (node->op == TOK_AND) {
273 value_t l = eval_expr(interp, env, node->a);
274 if (!to_int(l)) {
275 return VAL_ZERO;
276 }
277 value_t r = eval_expr(interp, env, node->b);
278 return (value_t){ VAL_INT, { .i = to_int(r) != 0 } };
279 }
280 if (node->op == TOK_OR) {
281 value_t l = eval_expr(interp, env, node->a);
282 if (to_int(l)) {
283 return (value_t){ VAL_INT, { .i = 1 } };
284 }
285 value_t r = eval_expr(interp, env, node->b);
286 return (value_t){ VAL_INT, { .i = to_int(r) != 0 } };
287 }
288 value_t l = eval_expr(interp, env, node->a);
289 value_t r = eval_expr(interp, env, node->b);
290 return eval_binop(node->op, l, r);
291 }
292 case AST_CALL:
293 return eval_call(interp, env, node);
294 default:
295 return VAL_ZERO;
296 }
297 }
298
299 static exec_result_t exec_stmt(interp_t *interp, env_t *env, ast_node_t *node) {
300 exec_result_t normal = { EXEC_NORMAL, VAL_ZERO };
301
302 switch (node->kind) {
303 case AST_BLOCK:
304 return exec_block(interp, env, node);
305 case AST_IF: {
306 value_t c = eval_expr(interp, env, node->a);
307 if (to_int(c)) {
308 return exec_stmt(interp, env, node->b);
309 }
310 if (node->c) {
311 return exec_stmt(interp, env, node->c);
312 }
313 return normal;
314 }
315 case AST_WHILE:
316 while (to_int(eval_expr(interp, env, node->a))) {
317 exec_result_t r = exec_stmt(interp, env, node->b);
318 if (r.signal == EXEC_RETURN) {
319 return r;
320 }
321 }
322 return normal;
323 case AST_FUNC_DEF:
324 /* top-level defs are pre-registered by script_run; a nested
325 * func statement (inside a block) registers here instead */
326 register_func(interp, node);
327 return normal;
328 case AST_RETURN: {
329 exec_result_t r = { EXEC_RETURN, VAL_ZERO };
330 if (node->a) {
331 r.return_value = eval_expr(interp, env, node->a);
332 }
333 return r;
334 }
335 case AST_EXPR_STMT:
336 eval_expr(interp, env, node->a);
337 return normal;
338 default:
339 return normal;
340 }
341 }
342
343 void script_run(const char *src) {
344 parser_t p;
345 parser_init(&p, src);
346 ast_node_t *prog = parse_program(&p);
347
348 if (p.error) {
349 console_write("script: parse error: ");
350 console_write(p.error_msg);
351 console_write("\n");
352 serial_write("script: parse error: ");
353 serial_write(p.error_msg);
354 serial_write("\n");
355 return;
356 }
357
358 interp_t interp;
359 interp.func_count = 0;
360 env_t global;
361 global.count = 0;
362
363 for (ast_node_t *s = prog->list; s; s = s->next) {
364 if (s->kind == AST_FUNC_DEF) {
365 register_func(&interp, s);
366 }
367 }
368 for (ast_node_t *s = prog->list; s; s = s->next) {
369 if (s->kind != AST_FUNC_DEF) {
370 exec_result_t r = exec_stmt(&interp, &global, s);
371 if (r.signal == EXEC_RETURN) {
372 break;
373 }
374 }
375 }
376 }
377 $ cd /home/martin/workspace/prototypes/boot-to-claude
python3 - <<'EOF'
import re
with open('kernel/lang/script/eval.c') as f:
content = f.read()
# (value_t){ VAL_INT, { .i = EXPR } } -> mkint(EXPR)
# (value_t){ VAL_STR, { .s = EXPR } } -> mkstr(EXPR)
# Use a balanced-brace-aware approach isn't needed since these are all
# single-line, non-nested compound literals - simple regex is safe here.
pattern = re.compile(r'\(value_t\)\{\s*VAL_INT,\s*\{\s*\.i\s*=\s*(.+?)\s*\}\s*\}')
content, n1 = pattern.subn(r'mkint(\1)', content)
pattern2 = re.compile(r'\(value_t\)\{\s*VAL_STR,\s*\{\s*\.s\s*=\s*(.+?)\s*\}\s*\}')
content, n2 = pattern2.subn(r'mkstr(\1)', content)
print(f"replaced {n1} VAL_INT literals, {n2} VAL_STR literals")
with open('kernel/lang/script/eval.c', 'w') as f:
f.write(content)
EOFreplaced 18 VAL_INT literals, 4 VAL_STR literals
1 #include "eval.h"
2
3 #include "../../console.h"
4 #include "../../fs/tinyfs.h"
5 #include "../../memory.h"
6 #include "../../serial.h"
7 #include "../../string.h"
8 #include "ast.h"
9 #include "parser.h"
10
11 typedef enum { VAL_INT, VAL_STR } value_type_t;
12
13 typedef struct {
14 value_type_t type;
15 union {
16 int32_t i;
17 char *s;
18 } as;
19 } value_t;
20
21 #define MAX_VARS 32
22
23 typedef struct {
24 char names[MAX_VARS][TOKEN_TEXT_MAX];
25 value_t values[MAX_VARS];
26 int count;
27 } env_t;
28
29 #define MAX_FUNCS 16
30
31 typedef struct {
32 char name[TOKEN_TEXT_MAX];
33 ast_node_t *def;
34 } func_entry_t;
35
36 typedef struct {
37 func_entry_t funcs[MAX_FUNCS];
38 int func_count;
39 } interp_t;
40
41 typedef enum { EXEC_NORMAL, EXEC_RETURN } exec_signal_t;
42
43 typedef struct {
44 exec_signal_t signal;
45 value_t return_value;
46 } exec_result_t;
47
48 static const value_t VAL_ZERO = { VAL_INT, { .i = 0 } };
49
50 static int env_find(env_t *env, const char *name) {- static const value_t VAL_ZERO = { VAL_INT, { .i = 0 } };
-
- static int env_find(env_t *env, const char *name) {
+ static const value_t VAL_ZERO = { VAL_INT, { .i = 0 } };
+
+ /* Constructors used in place of compound literals (`(value_t){...}`) -
+ * kept out of tinyC's dialect deliberately (see docs/toolchain.md): the
+ * feature was used in ~22 places in this file alone and implementing it
+ * in CC would mean parsing/lowering anonymous-temporary initializer
+ * lists for one file's convenience. Plain aggregate initialization of a
+ * file-scope variable (VAL_ZERO above) is a different, more basic
+ * feature and stays as-is - CC supports brace-init for globals/statics
+ * directly. */
+ static value_t mkint(int32_t i) {
+ value_t v;
+ v.type = VAL_INT;
+ v.as.i = i;
+ return v;
+ }
+
+ static value_t mkstr(char *s) {
+ value_t v;
+ v.type = VAL_STR;
+ v.as.s = s;
+ return v;
+ }
+
+ static int env_find(env_t *env, const char *name) {The file /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/script/eval.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ grep -n "mkint\|mkstr" /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/script/eval.c
58:static value_t mkint(int32_t i) {
65:static value_t mkstr(char *s) {
139: return mkstr(str_concat(ls, rs));
150: return mkint((op == TOK_EQ) ? eq : !eq);
156: case TOK_PLUS: return mkint(li + ri);
157: case TOK_MINUS: return mkint(li - ri);
158: case TOK_STAR: return mkint(li * ri);
159: case TOK_SLASH: return mkint(ri != 0 ? li / ri : 0);
160: case TOK_PERCENT: return mkint(ri != 0 ? li % ri : 0);
161: case TOK_LT: return mkint(li < ri);
162: case TOK_LE: return mkint(li <= ri);
163: case TOK_GT: return mkint(li > ri);
164: case TOK_GE: return mkint(li >= ri);
233: return mkstr(out);
235: return mkstr("");
242: return mkint(rc == 0);
254: return mkint(n);
276: return mkint(node->num);
278: return mkstr(node->str);
289: return mkint(-to_int(v));
291: return mkint(!to_int(v));
300: return mkint(to_int(r) != 0);
305: return mkint(1);
308: return mkint(to_int(r) != 0);$ cd /home/martin/workspace/prototypes/boot-to-claude make clean >/dev/null ./tools/run_tests.sh 2>&1 | tail -10
ld: warning: build/kernel.elf has a LOAD segment with RWX permissions run_tests: PASS qemu-system-i386: terminating on signal 15 from pid 2913757 (bash)
$ grep -n "BINOP_TABLE" /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/compiler/codegen.c
161:static const bc_opcode_t BINOP_TABLE[] = {
193: if ((size_t)node->op >= sizeof(BINOP_TABLE) / sizeof(BINOP_TABLE[0])) {
197: emit_op(c, BINOP_TABLE[node->op]);<tool_use_error>InputValidationError: Read was called with input that could not be parsed as JSON.
You sent (first 116 of 116 bytes): {"file_path": "/home/martin/workspace/prototypes/boot-to-claude/kernel/lang/compiler/codegen.c", "offset": 155, 200}
Common causes: unescaped backslashes in file paths (use / or \\), unescaped control characters, or truncated output. Retry with valid JSON.</tool_use_error>155 int slot = find_or_add_global(c, name);
156 emit_op(c, OP_STORE_GLOBAL);
157 emit_byte(c, (uint8_t)slot);
158 }
159 }
160
161 static const bc_opcode_t BINOP_TABLE[] = {
162 [TOK_PLUS] = OP_ADD, [TOK_MINUS] = OP_SUB, [TOK_STAR] = OP_MUL,
163 [TOK_SLASH] = OP_DIV, [TOK_PERCENT] = OP_MOD,
164 [TOK_EQ] = OP_EQ, [TOK_NEQ] = OP_NEQ,
165 [TOK_LT] = OP_LT, [TOK_LE] = OP_LE, [TOK_GT] = OP_GT, [TOK_GE] = OP_GE,
166 [TOK_AND] = OP_AND, [TOK_OR] = OP_OR,
167 };
168
169 static void compile_expr(compiler_t *c, ast_node_t *node) {
170 switch (node->kind) {
171 case AST_NUM:
172 emit_op(c, OP_PUSH_CONST);
173 emit_i32(c, node->num);
174 return;
175 case AST_STR:
176 compile_error(c, "strings are not supported in the compiled language");
177 return;
178 case AST_VAR:
179 compile_var_load(c, node->name);
180 return;
181 case AST_ASSIGN:
182 compile_expr(c, node->a);
183 emit_op(c, OP_DUP); /* assignment evaluates to the assigned value */
184 compile_var_store(c, node->name);
185 return;
186 case AST_UNOP:
187 compile_expr(c, node->a);
188 emit_op(c, node->op == TOK_MINUS ? OP_NEG : OP_NOT);
189 return;
190 case AST_BINOP: {
191 compile_expr(c, node->a);
192 compile_expr(c, node->b);
193 if ((size_t)node->op >= sizeof(BINOP_TABLE) / sizeof(BINOP_TABLE[0])) {
194 compile_error(c, "unsupported operator");
195 return;
196 }
197 emit_op(c, BINOP_TABLE[node->op]);
198 return;
199 }- static const bc_opcode_t BINOP_TABLE[] = {
- [TOK_PLUS] = OP_ADD, [TOK_MINUS] = OP_SUB, [TOK_STAR] = OP_MUL,
- [TOK_SLASH] = OP_DIV, [TOK_PERCENT] = OP_MOD,
- [TOK_EQ] = OP_EQ, [TOK_NEQ] = OP_NEQ,
- [TOK_LT] = OP_LT, [TOK_LE] = OP_LE, [TOK_GT] = OP_GT, [TOK_GE] = OP_GE,
- [TOK_AND] = OP_AND, [TOK_OR] = OP_OR,
- };
+ /* Explicit dispatch in place of a designated-initializer lookup table
+ * (`[TOK_PLUS] = OP_ADD, ...`) - kept out of tinyC's dialect deliberately
+ * (see docs/toolchain.md): implementing designated initializers in CC
+ * for this one table isn't worth the added parser/codegen complexity. */
+ static int binop_opcode(token_type_t op, bc_opcode_t *out) {
+ switch (op) {
+ case TOK_PLUS: *out = OP_ADD; return 1;
+ case TOK_MINUS: *out = OP_SUB; return 1;
+ case TOK_STAR: *out = OP_MUL; return 1;
+ case TOK_SLASH: *out = OP_DIV; return 1;
+ case TOK_PERCENT: *out = OP_MOD; return 1;
+ case TOK_EQ: *out = OP_EQ; return 1;
+ case TOK_NEQ: *out = OP_NEQ; return 1;
+ case TOK_LT: *out = OP_LT; return 1;
+ case TOK_LE: *out = OP_LE; return 1;
+ case TOK_GT: *out = OP_GT; return 1;
+ case TOK_GE: *out = OP_GE; return 1;
+ case TOK_AND: *out = OP_AND; return 1;
+ case TOK_OR: *out = OP_OR; return 1;
+ default: return 0;
+ }
+ }The file /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/compiler/codegen.c has been updated successfully. (file state is current in your context — no need to Read it back)
- case AST_BINOP: {
- compile_expr(c, node->a);
- compile_expr(c, node->b);
- if ((size_t)node->op >= sizeof(BINOP_TABLE) / sizeof(BINOP_TABLE[0])) {
- compile_error(c, "unsupported operator");
- return;
- }
- emit_op(c, BINOP_TABLE[node->op]);
- return;
- }
+ case AST_BINOP: {
+ compile_expr(c, node->a);
+ compile_expr(c, node->b);
+ bc_opcode_t op;
+ if (!binop_opcode(node->op, &op)) {
+ compile_error(c, "unsupported operator");
+ return;
+ }
+ emit_op(c, op);
+ return;
+ }The file /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/compiler/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 make clean >/dev/null ./tools/run_tests.sh 2>&1 | tail -10
ld: warning: build/kernel.elf has a LOAD segment with RWX permissions run_tests: PASS qemu-system-i386: terminating on signal 15 from pid 2914422 (bash)
1 #pragma once 2 3 #include <stddef.h> 4 5 void *memcpy(void *dst, const void *src, size_t n); 6 void *memset(void *dst, int val, size_t n); 7 size_t strlen(const char *s); 8 int strcmp(const char *a, const char *b); 9 10 /* Writes the decimal (base 10) or hex (base 16) representation of value 11 * into buf (caller-provided, must be large enough) and returns buf. */ 12 char *itoa(int value, char *buf, int base); 13
- 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); + 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);
The file /home/martin/workspace/prototypes/boot-to-claude/kernel/string.h has been updated successfully. (file state is current in your context — no need to Read it back)
1 #include "string.h"
2
3 #include <stdint.h>
4
5 void *memcpy(void *dst, const void *src, size_t n) {
6 uint8_t *d = dst;
7 const uint8_t *s = src;
8 for (size_t i = 0; i < n; i++) {
9 d[i] = s[i];
10 }
11 return dst;
12 }
13
14 void *memset(void *dst, int val, size_t n) {
15 uint8_t *d = dst;
16 for (size_t i = 0; i < n; i++) {
17 d[i] = (uint8_t)val;
18 }
19 return dst;
20 }
21
22 size_t strlen(const char *s) {
23 size_t n = 0;
24 while (s[n]) {
25 n++;
26 }
27 return n;
28 }
29
30 int strcmp(const char *a, const char *b) {
31 while (*a && (*a == *b)) {
32 a++;
33 b++;
34 }
35 return (unsigned char)*a - (unsigned char)*b;
36 }
37
38 char *itoa(int value, char *buf, int base) {
39 char tmp[34];
40 int i = 0;
41 int negative = (base == 10 && value < 0);
42 unsigned int uval = negative ? (unsigned int)(-value) : (unsigned int)value;
43
44 if (uval == 0) {
45 tmp[i++] = '0';
46 }
47 while (uval) {
48 int digit = (int)(uval % (unsigned int)base);
49 tmp[i++] = (char)(digit < 10 ? '0' + digit : 'a' + (digit - 10));
50 uval /= (unsigned int)base;
51 }
52 if (negative) {
53 tmp[i++] = '-';
54 }
55
56 int j = 0;
57 while (i > 0) {
58 buf[j++] = tmp[--i];
59 }
60 buf[j] = '\0';
61 return buf;
62 }
63 - char *itoa(int value, char *buf, int base) {
+ 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) {The file /home/martin/workspace/prototypes/boot-to-claude/kernel/string.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 clean >/dev/null ./tools/run_tests.sh 2>&1 | tail -10
ld: warning: build/kernel.elf has a LOAD segment with RWX permissions run_tests: PASS qemu-system-i386: terminating on signal 15 from pid 2915154 (bash)
1 /* Host-side build tool: formats the tinyfs region of a disk image and
2 * seeds it with initial files. Not part of the OS - infrastructure, like
3 * the assembler/linker/QEMU, run at build time on the host. Reuses
4 * kernel/fs/tinyfs.c verbatim (compiled natively here, freestanding in
5 * the kernel) so there is exactly one implementation of the fs format. */
6
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10
11 #include "../kernel/fs/tinyfs.h"
12
13 static FILE *disk;
14
15 static void disk_block_read(uint32_t block, void *buf) {
16 long offset = (long)(TINYFS_FS_START_LBA + block) * TINYFS_BLOCK_SIZE;
17 if (fseek(disk, offset, SEEK_SET) != 0 ||
18 fread(buf, 1, TINYFS_BLOCK_SIZE, disk) != TINYFS_BLOCK_SIZE) {
19 fprintf(stderr, "mkfs_tinyfs: read failed at block %u\n", block);
20 exit(1);
21 }
22 }
23
24 static void disk_block_write(uint32_t block, const void *buf) {
25 long offset = (long)(TINYFS_FS_START_LBA + block) * TINYFS_BLOCK_SIZE;
26 if (fseek(disk, offset, SEEK_SET) != 0 ||
27 fwrite(buf, 1, TINYFS_BLOCK_SIZE, disk) != TINYFS_BLOCK_SIZE) {
28 fprintf(stderr, "mkfs_tinyfs: write failed at block %u\n", block);
29 exit(1);
30 }
31 }
32
33 static void seed_file(const char *name, const char *contents) {
34 tinyfs_create(name);
35 tinyfs_write(name, contents, (uint32_t)strlen(contents));
36 }
37
38 /* Seeds a tinyfs file straight from a host file, for test fixtures under
39 * tests/ so their source lives in one place instead of being duplicated
40 * as a C string literal here. */
41 static void seed_file_from_host(const char *host_path, const char *fs_name) {
42 FILE *f = fopen(host_path, "rb");
43 if (!f) {
44 fprintf(stderr, "mkfs_tinyfs: cannot open seed file %s\n", host_path);
45 exit(1);
46 }
47 char buf[TINYFS_MAX_FILE_SIZE];
48 size_t n = fread(buf, 1, sizeof(buf), f);
49 fclose(f);
50
51 tinyfs_create(fs_name);
52 tinyfs_write(fs_name, buf, (uint32_t)n);
53 }
54
55 int main(int argc, char **argv) {
56 if (argc < 2) {
57 fprintf(stderr, "usage: %s <disk-image> [host-path:fs-name ...]\n", argv[0]);
58 return 1;
59 }
60
61 disk = fopen(argv[1], "r+b");
62 if (!disk) {
63 perror("fopen");
64 return 1;
65 }
66
67 tinyfs_init(disk_block_read, disk_block_write);
68 tinyfs_format();
69
70 seed_file("hello.txt", "Hello, tinyfs!\n");
71
72 for (int i = 2; i < argc; i++) {
73 char spec[256];
74 size_t speclen = strlen(argv[i]);
75 if (speclen >= sizeof(spec)) {
76 fprintf(stderr, "mkfs_tinyfs: seed spec too long: %s\n", argv[i]);
77 return 1;
78 }
79 memcpy(spec, argv[i], speclen + 1);
80
81 char *sep = strchr(spec, ':');
82 if (!sep) {
83 fprintf(stderr, "mkfs_tinyfs: bad seed spec %s (want host-path:fs-name)\n", argv[i]);
84 return 1;
85 }
86 *sep = '\0';
87 seed_file_from_host(spec, sep + 1);
88 }
89
90 fclose(disk);
91 printf("mkfs_tinyfs: formatted %s (%u data blocks available)\n",
92 argv[1], (unsigned)TINYFS_DATA_BLOCKS);
93 return 0;
94 }
95 /* Host-side build tool: formats the tinyfs region of a disk image and
* seeds it with initial files. Not part of the OS - infrastructure, like
* the assembler/linker/QEMU, run at build time on the host. Reuses
* kernel/fs/tinyfs.c verbatim (compiled natively here, freestanding in
* the kernel) so there is exactly one implementation of the fs format.
*
* Libc-free: uses only the __sys_* raw-syscall names CC's builtins.c
* intercepts directly at the call site (see docs/toolchain.md), and
* kernel/string.h instead of the system <string.h>, so this file will
* compile unchanged once Phase T7 switches the build to CC. Under the
* current interim GCC-based build, __sys_open/read/write/lseek/close/
* exit need real definitions to link against - see the shim below,
* which mirrors builtins.c's fixed argument conventions exactly (e.g.
* __sys_open takes only path+flags; the mode argument is hardcoded to
* 420/0644 in the builtin, so the shim hardcodes it too) and is dropped
* once CC takes over, same as kernel/builtins_shim.c. */
#include "../kernel/fs/tinyfs.h"
#include "../kernel/string.h"
#if !defined(__TINYC__)
/* --- interim GCC-only shim: real x86-32 int 0x80 syscalls --- */
static inline int __sys_open(const char *path, int flags) {
int ret;
__asm__ volatile ("int $0x80" : "=a"(ret) : "a"(5), "b"(path), "c"(flags), "d"(420) : "memory");
return ret;
}
static inline int __sys_read(int fd, void *buf, unsigned int count) {
int ret;
__asm__ volatile ("int $0x80" : "=a"(ret) : "a"(3), "b"(fd), "c"(buf), "d"(count) : "memory");
return ret;
}
static inline int __sys_write(int fd, const void *buf, unsigned int count) {
int ret;
__asm__ volatile ("int $0x80" : "=a"(ret) : "a"(4), "b"(fd), "c"(buf), "d"(count) : "memory");
return ret;
}
static inline int __sys_lseek(int fd, int offset, int whence) {
int ret;
__asm__ volatile ("int $0x80" : "=a"(ret) : "a"(19), "b"(fd), "c"(offset), "d"(whence) : "memory");
return ret;
}
static inline int __sys_close(int fd) {
int ret;
__asm__ volatile ("int $0x80" : "=a"(ret) : "a"(6), "b"(fd) : "memory");
return ret;
}
static inline void __sys_exit(int code) {
__asm__ volatile ("int $0x80" : : "a"(1), "b"(code));
for (;;) {}
}
#endif
#define O_RDONLY 0
#define O_RDWR 2
#define SEEK_SET 0
static void die(const char *msg) {
__sys_write(2, msg, strlen(msg));
__sys_exit(1);
}
static int g_disk_fd;
static void disk_block_read(uint32_t block, void *buf) {
long offset = (long)(TINYFS_FS_START_LBA + block) * TINYFS_BLOCK_SIZE;
if (__sys_lseek(g_disk_fd, (int)offset, SEEK_SET) < 0 ||
__sys_read(g_disk_fd, buf, TINYFS_BLOCK_SIZE) != TINYFS_BLOCK_SIZE) {
die("mkfs_tinyfs: disk read failed\n");
}
}
static void disk_block_write(uint32_t block, const void *buf) {
long offset = (long)(TINYFS_FS_START_LBA + block) * TINYFS_BLOCK_SIZE;
if (__sys_lseek(g_disk_fd, (int)offset, SEEK_SET) < 0 ||
__sys_write(g_disk_fd, buf, TINYFS_BLOCK_SIZE) != TINYFS_BLOCK_SIZE) {
die("mkfs_tinyfs: disk write failed\n");
}
}
static void seed_file(const char *name, const char *contents) {
tinyfs_create(name);
tinyfs_write(name, contents, (uint32_t)strlen(contents));
}
/* Seeds a tinyfs file straight from a host file, for test fixtures under
* tests/ so their source lives in one place instead of being duplicated
* as a C string literal here. */
static void seed_file_from_host(const char *host_path, const char *fs_name) {
int fd = __sys_open(host_path, O_RDONLY);
if (fd < 0) {
die("mkfs_tinyfs: cannot open seed file\n");
}
static char buf[TINYFS_MAX_FILE_SIZE];
int n = __sys_read(fd, buf, sizeof(buf));
__sys_close(fd);
if (n < 0) {
die("mkfs_tinyfs: cannot read seed file\n");
}
tinyfs_create(fs_name);
tinyfs_write(fs_name, buf, (uint32_t)n);
}
int main(int argc, char **argv) {
if (argc < 2) {
die("usage: mkfs_tinyfs <disk-image> [host-path:fs-name ...]\n");
}
g_disk_fd = __sys_open(argv[1], O_RDWR);
if (g_disk_fd < 0) {
die("mkfs_tinyfs: cannot open disk image\n");
}
tinyfs_init(disk_block_read, disk_block_write);
tinyfs_format();
seed_file("hello.txt", "Hello, tinyfs!\n");
for (int i = 2; i < argc; i++) {
static char spec[256];
size_t speclen = strlen(argv[i]);
if (speclen >= sizeof(spec)) {
die("mkfs_tinyfs: seed spec too long\n");
}
memcpy(spec, argv[i], speclen + 1);
char *sep = strchr(spec, ':');
if (!sep) {
die("mkfs_tinyfs: bad seed spec (want host-path:fs-name)\n");
}
*sep = '\0';
seed_file_from_host(spec, sep + 1);
}
__sys_close(g_disk_fd);
__sys_write(1, "mkfs_tinyfs: done\n", 19);
__sys_exit(0);
return 0;
}
The file /home/martin/workspace/prototypes/boot-to-claude/tools/mkfs_tinyfs.c has been updated successfully. (file state is current in your context — no need to Read it back)
/* Host-side build tool: formats the tinyfs region of a disk image and
* seeds it with initial files. Not part of the OS - infrastructure, like
* the assembler/linker/QEMU, run at build time on the host. Reuses
* kernel/fs/tinyfs.c verbatim (compiled natively here, freestanding in
* the kernel) so there is exactly one implementation of the fs format.
*
* Libc-free: uses only the __sys_* raw-syscall names CC's builtins.c
* intercepts directly at the call site (see docs/toolchain.md), and
* kernel/string.h instead of the system <string.h>, so this file
* compiles unchanged once Phase T7 switches the build to CC. Under the
* current interim GCC-based build, __sys_open/read/write/lseek/close/
* exit need real definitions to link against - see
* tools/host_syscalls_shim.c (dropped once CC takes over, same as
* kernel/builtins_shim.c - CC intercepts every call site directly and
* couldn't even parse that shim's inline asm). */
#include "../kernel/fs/tinyfs.h"
#include "../kernel/string.h"
#include "host_syscalls.h"
#define O_RDONLY 0
#define O_RDWR 2
#define SEEK_SET 0
static void die(const char *msg) {
__sys_write(2, msg, strlen(msg));
__sys_exit(1);
}
static int g_disk_fd;
static void disk_block_read(uint32_t block, void *buf) {
long offset = (long)(TINYFS_FS_START_LBA + block) * TINYFS_BLOCK_SIZE;
if (__sys_lseek(g_disk_fd, (int)offset, SEEK_SET) < 0 ||
__sys_read(g_disk_fd, buf, TINYFS_BLOCK_SIZE) != TINYFS_BLOCK_SIZE) {
die("mkfs_tinyfs: disk read failed\n");
}
}
static void disk_block_write(uint32_t block, const void *buf) {
long offset = (long)(TINYFS_FS_START_LBA + block) * TINYFS_BLOCK_SIZE;
if (__sys_lseek(g_disk_fd, (int)offset, SEEK_SET) < 0 ||
__sys_write(g_disk_fd, buf, TINYFS_BLOCK_SIZE) != TINYFS_BLOCK_SIZE) {
die("mkfs_tinyfs: disk write failed\n");
}
}
static void seed_file(const char *name, const char *contents) {
tinyfs_create(name);
tinyfs_write(name, contents, (uint32_t)strlen(contents));
}
/* Seeds a tinyfs file straight from a host file, for test fixtures under
* tests/ so their source lives in one place instead of being duplicated
* as a C string literal here. */
static void seed_file_from_host(const char *host_path, const char *fs_name) {
int fd = __sys_open(host_path, O_RDONLY);
if (fd < 0) {
die("mkfs_tinyfs: cannot open seed file\n");
}
static char buf[TINYFS_MAX_FILE_SIZE];
int n = __sys_read(fd, buf, sizeof(buf));
__sys_close(fd);
if (n < 0) {
die("mkfs_tinyfs: cannot read seed file\n");
}
tinyfs_create(fs_name);
tinyfs_write(fs_name, buf, (uint32_t)n);
}
int main(int argc, char **argv) {
if (argc < 2) {
die("usage: mkfs_tinyfs <disk-image> [host-path:fs-name ...]\n");
}
g_disk_fd = __sys_open(argv[1], O_RDWR);
if (g_disk_fd < 0) {
die("mkfs_tinyfs: cannot open disk image\n");
}
tinyfs_init(disk_block_read, disk_block_write);
tinyfs_format();
seed_file("hello.txt", "Hello, tinyfs!\n");
for (int i = 2; i < argc; i++) {
static char spec[256];
size_t speclen = strlen(argv[i]);
if (speclen >= sizeof(spec)) {
die("mkfs_tinyfs: seed spec too long\n");
}
memcpy(spec, argv[i], speclen + 1);
char *sep = strchr(spec, ':');
if (!sep) {
die("mkfs_tinyfs: bad seed spec (want host-path:fs-name)\n");
}
*sep = '\0';
seed_file_from_host(spec, sep + 1);
}
__sys_close(g_disk_fd);
__sys_write(1, "mkfs_tinyfs: done\n", 19);
__sys_exit(0);
return 0;
}
The file /home/martin/workspace/prototypes/boot-to-claude/tools/mkfs_tinyfs.c has been updated successfully. (file state is current in your context — no need to Read it back)
#pragma once /* Declares the raw Linux x86-32 syscalls host tools use in place of * libc file I/O (see docs/toolchain.md and tools/mkfs_tinyfs.c). Under * CC (--target=host), calls to these exact names are recognized in * codegen and compiled directly to `int 0x80` - no function body is * ever compiled or linked for them, matching kernel/builtins.h's * __builtin_* mechanism. Under the current interim GCC-based build, * real definitions live in tools/host_syscalls_shim.c. */ 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);
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/tools/host_syscalls.h (file state is current in your context — no need to Read it back)
/* Real x86-32 int 0x80 syscall bodies for the names declared in
* host_syscalls.h - exists only so host tools (tools/mkfs_tinyfs.c) keep
* building under the current interim GCC-based toolchain. Dropped in
* Phase T7 once the default build switches to CC, which intercepts
* every call site in codegen and never needs (or could compile - it has
* no inline-asm support at all) a linkable definition for these. Must
* be compiled -m32: these are x86-32 Linux syscall numbers/calling
* convention, matching what CC's builtins.c emits, since CC only ever
* targets x86-32.
*
* Argument conventions mirror builtins.c exactly - e.g. __sys_open
* takes only path+flags, with mode hardcoded to 420 (0644), since
* that's what the actual builtin does. */
#include "host_syscalls.h"
int __sys_open(const char *path, int flags) {
int ret;
__asm__ volatile ("int $0x80" : "=a"(ret) : "a"(5), "b"(path), "c"(flags), "d"(420) : "memory");
return ret;
}
int __sys_read(int fd, void *buf, unsigned int count) {
int ret;
__asm__ volatile ("int $0x80" : "=a"(ret) : "a"(3), "b"(fd), "c"(buf), "d"(count) : "memory");
return ret;
}
int __sys_write(int fd, const void *buf, unsigned int count) {
int ret;
__asm__ volatile ("int $0x80" : "=a"(ret) : "a"(4), "b"(fd), "c"(buf), "d"(count) : "memory");
return ret;
}
int __sys_lseek(int fd, int offset, int whence) {
int ret;
__asm__ volatile ("int $0x80" : "=a"(ret) : "a"(19), "b"(fd), "c"(offset), "d"(whence) : "memory");
return ret;
}
int __sys_close(int fd) {
int ret;
__asm__ volatile ("int $0x80" : "=a"(ret) : "a"(6), "b"(fd) : "memory");
return ret;
}
void __sys_exit(int code) {
__asm__ volatile ("int $0x80" : : "a"(1), "b"(code));
for (;;) {}
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/tools/host_syscalls_shim.c (file state is current in your context — no need to Read it back)
1 AS := nasm 2 CC := gcc 3 HOSTCC := gcc 4 LD := ld 5 OBJCOPY := objcopy 6 QEMU := qemu-system-i386 7 BUILD := build 8 9 CFLAGS := -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib \ 10 -Wall -Wextra -O2 -std=c11 -Ikernel 11 LDFLAGS := -m elf_i386 -T kernel/link.ld -nostdlib 12 13 # Must match TINYFS_DISK_SECTORS / TINYFS_FS_START_LBA in kernel/fs/tinyfs.h 14 # and KERNEL_START_LBA / KERNEL_SECTORS in boot/stage2.S 15 DISK_SECTORS := 4096 16 KERNEL_START_SECTOR := 5 17 KERNEL_MAX_SECTORS := 512 18 19 BOOT_BIN := $(BUILD)/boot.bin 20 STAGE2_BIN := $(BUILD)/stage2.bin 21 DISK_IMG := $(BUILD)/disk.img 22 23 KERNEL_C_SRCS := kernel/kmain.c kernel/console.c kernel/serial.c kernel/gdt.c \ 24 kernel/idt.c kernel/string.c kernel/memory.c kernel/selftest.c \ 25 kernel/ata.c kernel/fs/tinyfs.c kernel/fs/tinyfs_disk.c \ 26 kernel/keyboard.c kernel/shell/shell.c kernel/builtins_shim.c \ 27 kernel/lang/lexer.c kernel/lang/script/ast.c \ 28 kernel/lang/script/parser.c kernel/lang/script/eval.c \ 29 kernel/lang/compiler/parser.c kernel/lang/compiler/codegen.c \ 30 kernel/lang/vm.c 31 KERNEL_ASM_SRCS := kernel/entry.S kernel/idt_stubs.S 32 KERNEL_OBJS := $(patsubst kernel/%.S,$(BUILD)/%.o,$(KERNEL_ASM_SRCS)) \ 33 $(patsubst kernel/%.c,$(BUILD)/%.o,$(KERNEL_C_SRCS)) 34 KERNEL_ELF := $(BUILD)/kernel.elf 35 KERNEL_BIN := $(BUILD)/kernel.bin 36 37 MKFS_BIN := $(BUILD)/mkfs_tinyfs 38 MKFS_OBJS := $(BUILD)/host_tinyfs.o $(BUILD)/host_mkfs_tinyfs.o 39 # host-path:fs-name pairs seeded into every disk image; the host paths 40 # alone are listed separately so they can be used as make prerequisites 41 MKFS_SEED_FILES := tests/hello.tsh tests/hello.tc 42 MKFS_SEED_SPECS := tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc 43 44 .PHONY: all run run-gui clean 45 46 all: $(DISK_IMG) 47 48 BUILD_SUBDIRS := $(BUILD) $(BUILD)/fs $(BUILD)/shell $(BUILD)/lang $(BUILD)/lang/script $(BUILD)/lang/compiler 49 50 $(BUILD_SUBDIRS): 51 mkdir -p $@ 52 53 $(BOOT_BIN): boot/boot.S | $(BUILD) 54 $(AS) -f bin -o $@ $< 55 56 $(STAGE2_BIN): boot/stage2.S | $(BUILD) 57 $(AS) -f bin -o $@ $< 58 59 $(BUILD)/%.o: kernel/%.S | $(BUILD_SUBDIRS) 60 $(AS) -f elf32 -o $@ $< 61 62 $(BUILD)/%.o: kernel/%.c | $(BUILD_SUBDIRS) 63 $(CC) $(CFLAGS) -c -o $@ $< 64 65 $(KERNEL_ELF): $(KERNEL_OBJS) kernel/link.ld 66 $(LD) $(LDFLAGS) -o $@ $(KERNEL_OBJS) 67 68 $(KERNEL_BIN): $(KERNEL_ELF) 69 $(OBJCOPY) -O binary $< $@ 70 @size=$$(stat -c%s $@); budget=$$(( $(KERNEL_MAX_SECTORS) * 512 )); \ 71 if [ "$$size" -gt "$$budget" ]; then \ 72 echo "error: $@ is $$size bytes, exceeds the $$budget-byte ($(KERNEL_MAX_SECTORS)-sector) boot budget" >&2; \ 73 echo " bump KERNEL_SECTORS in boot/stage2.S, KERNEL_MAX_SECTORS here, and TINYFS_FS_START_LBA in kernel/fs/tinyfs.h together" >&2; \ 74 exit 1; \ 75 fi 76 77 # --- host-side mkfs tool (build-time infrastructure, not kernel code) --- 78 79 $(BUILD)/host_tinyfs.o: kernel/fs/tinyfs.c | $(BUILD) 80 $(HOSTCC) -Wall -Wextra -O2 -c -o $@ $< 81 82 $(BUILD)/host_mkfs_tinyfs.o: tools/mkfs_tinyfs.c | $(BUILD) 83 $(HOSTCC) -Wall -Wextra -O2 -c -o $@ $< 84 85 $(MKFS_BIN): $(MKFS_OBJS) 86 $(HOSTCC) -o $@ $(MKFS_OBJS) 87 88 $(DISK_IMG): $(BOOT_BIN) $(STAGE2_BIN) $(KERNEL_BIN) $(MKFS_BIN) $(MKFS_SEED_FILES) 89 dd if=/dev/zero of=$@ bs=512 count=$(DISK_SECTORS) status=none 90 dd if=$(BOOT_BIN) of=$@ conv=notrunc status=none 91 dd if=$(STAGE2_BIN) of=$@ bs=512 seek=1 conv=notrunc status=none 92 dd if=$(KERNEL_BIN) of=$@ bs=512 seek=$(KERNEL_START_SECTOR) conv=notrunc status=none 93 $(MKFS_BIN) $@ $(MKFS_SEED_SPECS) 94 95 # Headless: no window at all, so there's no input surface for the emulated 96 # PS/2 keyboard - useful for a quick boot sanity check or scripting, but 97 # you cannot type into the shell this way. Use `make run-gui` to interact. 98 run: $(DISK_IMG) 99 $(QEMU) -drive file=$(DISK_IMG),format=raw -display none -serial stdio -no-reboot 100 101 # Opens a real window - click into it, then type. Serial output is also 102 # echoed to this terminal, but keystrokes only reach the guest via the 103 # window. 104 run-gui: $(DISK_IMG) 105 $(QEMU) -drive file=$(DISK_IMG),format=raw -serial stdio -no-reboot 106 107 clean: 108 rm -rf $(BUILD) 109
- MKFS_BIN := $(BUILD)/mkfs_tinyfs - MKFS_OBJS := $(BUILD)/host_tinyfs.o $(BUILD)/host_mkfs_tinyfs.o + HOSTCFLAGS := -m32 -Wall -Wextra -O2 -Ikernel + + MKFS_BIN := $(BUILD)/mkfs_tinyfs + # host_string.o: kernel/string.c compiled natively - host tools are + # libc-free (see docs/toolchain.md), same as the kernel itself. + # host_syscalls_shim.o: real int 0x80 bodies for the interim GCC build + # only, dropped once Phase T7 switches to CC (see tools/host_syscalls.h). + MKFS_OBJS := $(BUILD)/host_tinyfs.o $(BUILD)/host_string.o \ + $(BUILD)/host_syscalls_shim.o $(BUILD)/host_mkfs_tinyfs.o
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)
- # --- host-side mkfs tool (build-time infrastructure, not kernel code) --- - - $(BUILD)/host_tinyfs.o: kernel/fs/tinyfs.c | $(BUILD) - $(HOSTCC) -Wall -Wextra -O2 -c -o $@ $< - - $(BUILD)/host_mkfs_tinyfs.o: tools/mkfs_tinyfs.c | $(BUILD) - $(HOSTCC) -Wall -Wextra -O2 -c -o $@ $< - - $(MKFS_BIN): $(MKFS_OBJS) - $(HOSTCC) -o $@ $(MKFS_OBJS) + # --- host-side mkfs tool (build-time infrastructure, not kernel code) --- + # -m32: matches CC's x86-32-only codegen, so tools/host_syscalls_shim.c's + # int 0x80 convention is correct and this build stays consistent with + # what Phase T7 will produce. + + $(BUILD)/host_tinyfs.o: kernel/fs/tinyfs.c | $(BUILD) + $(HOSTCC) $(HOSTCFLAGS) -c -o $@ $< + + $(BUILD)/host_string.o: kernel/string.c | $(BUILD) + $(HOSTCC) $(HOSTCFLAGS) -c -o $@ $< + + $(BUILD)/host_syscalls_shim.o: tools/host_syscalls_shim.c | $(BUILD) + $(HOSTCC) $(HOSTCFLAGS) -c -o $@ $< + + $(BUILD)/host_mkfs_tinyfs.o: tools/mkfs_tinyfs.c | $(BUILD) + $(HOSTCC) $(HOSTCFLAGS) -c -o $@ $< + + $(MKFS_BIN): $(MKFS_OBJS) + $(HOSTCC) -m32 -o $@ $(MKFS_OBJS)
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 make 2>&1 | tail -40
mkdir -p build/lang/script mkdir -p build/lang/compiler nasm -f elf32 -o build/entry.o kernel/entry.S nasm -f elf32 -o build/idt_stubs.o kernel/idt_stubs.S gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/kmain.o kernel/kmain.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/console.o kernel/console.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/serial.o kernel/serial.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/gdt.o kernel/gdt.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/idt.o kernel/idt.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/string.o kernel/string.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/memory.o kernel/memory.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/selftest.o kernel/selftest.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/ata.o kernel/ata.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/fs/tinyfs.o kernel/fs/tinyfs.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/fs/tinyfs_disk.o kernel/fs/tinyfs_disk.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/keyboard.o kernel/keyboard.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/shell/shell.o kernel/shell/shell.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/builtins_shim.o kernel/builtins_shim.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/lang/lexer.o kernel/lang/lexer.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/lang/script/ast.o kernel/lang/script/ast.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/lang/script/parser.o kernel/lang/script/parser.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/lang/script/eval.o kernel/lang/script/eval.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/lang/compiler/parser.o kernel/lang/compiler/parser.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/lang/compiler/codegen.o kernel/lang/compiler/codegen.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/lang/vm.o kernel/lang/vm.c ld -m elf_i386 -T kernel/link.ld -nostdlib -o build/kernel.elf build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/builtins_shim.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o ld: warning: build/kernel.elf has a LOAD segment with RWX permissions objcopy -O binary build/kernel.elf build/kernel.bin gcc -m32 -Wall -Wextra -O2 -Ikernel -c -o build/host_tinyfs.o kernel/fs/tinyfs.c gcc -m32 -Wall -Wextra -O2 -Ikernel -c -o build/host_string.o kernel/string.c gcc -m32 -Wall -Wextra -O2 -Ikernel -c -o build/host_syscalls_shim.o tools/host_syscalls_shim.c gcc -m32 -Wall -Wextra -O2 -Ikernel -c -o build/host_mkfs_tinyfs.o tools/mkfs_tinyfs.c gcc -m32 -o build/mkfs_tinyfs build/host_tinyfs.o build/host_string.o build/host_syscalls_shim.o build/host_mkfs_tinyfs.o dd if=/dev/zero of=build/disk.img bs=512 count=4096 status=none dd if=build/boot.bin of=build/disk.img conv=notrunc status=none dd if=build/stage2.bin of=build/disk.img bs=512 seek=1 conv=notrunc status=none dd if=build/kernel.bin of=build/disk.img bs=512 seek=5 conv=notrunc status=none build/mkfs_tinyfs build/disk.img tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc mkfs_tinyfs: done
$ cd /home/martin/workspace/prototypes/boot-to-claude ./tools/run_tests.sh 2>&1 | tail -15
run_tests: PASS qemu-system-i386: terminating on signal 15 from pid 2921621 (bash)
$ cd /home/martin/workspace/prototypes/boot-to-claude rm -rf build/toolchain build/toolchain_tests ./tools/run_toolchain_tests.sh 2>&1
PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) run_toolchain_tests: PASS
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -rn "formatted.*data blocks\|mkfs_tinyfs: formatted" README.md docs/ 2>/dev/null
(Bash completed with no output)
$ cd /home/martin/workspace/prototypes/boot-to-claude grep -n "fopen\|libc\|fread\|fwrite" docs/tinyfs.md README.md 2>/dev/null
docs/tinyfs.md:44:the kernel, `fopen`/`fseek`/`fwrite` on the host). `tinyfs_format` is only README.md:44:scripting language (`run`, including its own `fread`/`fwrite` calls into
38 39 One bit per data block, index 0 = first data block (fs block 10). 40 41 ## API (`kernel/fs/tinyfs.c`) 42 43 `tinyfs_init(read_fn, write_fn)` wires in the block I/O backend (ATA PIO in 44 the kernel, `fopen`/`fseek`/`fwrite` on the host). `tinyfs_format` is only 45 ever called by `mkfs_tinyfs`. The kernel calls `tinyfs_mount`, then 46 `tinyfs_create` / `tinyfs_read` / `tinyfs_write` / `tinyfs_delete` / 47 `tinyfs_list`. 48 49 There is exactly one implementation of the format - `tinyfs.c` is compiled
- `tinyfs_init(read_fn, write_fn)` wires in the block I/O backend (ATA PIO in - the kernel, `fopen`/`fseek`/`fwrite` on the host). `tinyfs_format` is only - ever called by `mkfs_tinyfs`. The kernel calls `tinyfs_mount`, then + `tinyfs_init(read_fn, write_fn)` wires in the block I/O backend (ATA PIO in + the kernel, raw `__sys_lseek`/`__sys_read`/`__sys_write` syscalls on the + host - see `tools/mkfs_tinyfs.c` and `docs/toolchain.md`). `tinyfs_format` + is only ever called by `mkfs_tinyfs`. The kernel calls `tinyfs_mount`, then
The file /home/martin/workspace/prototypes/boot-to-claude/docs/tinyfs.md has been updated successfully. (file state is current in your context — no need to Read it back)
$ grep -n "Requires\|toolchain" /home/martin/workspace/prototypes/boot-to-claude/README.md | head -10
49:Requires `nasm`, `gcc` (with `-m32` support), `ld`, `objcopy`, `dd`, and
$ cd /home/martin/workspace/prototypes/boot-to-claude make clean >/dev/null ./tools/run_tests.sh 2>&1 | tail -10 echo "===" rm -rf build/toolchain build/toolchain_tests ./tools/run_toolchain_tests.sh 2>&1 | tail -10
ld: warning: build/kernel.elf has a LOAD segment with RWX permissions run_tests: PASS qemu-system-i386: terminating on signal 15 from pid 2922853 (bash) === PASS t01_basic (5) PASS t02_control (7) PASS t02_types (42) PASS t03_recursion (55) PASS t04_pointers (31) PASS t05_structs (17) run_toolchain_tests: PASS
190 `static` locals (compile-time-constant `.data` bytes) - this codebase
191 never brace-initializes a plain stack local, only scalars, so codegen
192 never needs to lower a brace list into runtime store instructions.
193 Compound literals (`(type){...}`) and designated initializers
194 (`[i] = x`, `.field = x`) aren't supported at all - the two call sites
195 that used them (`kernel/lang/script/eval.c`, `kernel/lang/compiler/
196 codegen.c`) get refactored to avoid them in Phase T3, a deliberate
197 scope-narrowing decision (see the plan) rather than an oversight.
198
199 ### Codegen
200
201 Naive and non-optimizing on purpose (see the plan's Phase T4 entry):
202 every expression's result lives in `EAX`; a binary operator evaluates
203 its right operand first and `push`es it, then its left operand into
204 `EAX`, then `pop`s the right operand into `ECX` and combines - no
205 register allocation, values spill to the real machine stack between
206 every sub-expression. cdecl calling convention (caller pushes args
207 right-to-left, callee's `EBP+8`/`+12`/... for params, return value in
208 `EAX`, caller cleans up the pushed args). Locals live at fixed `EBP`-
209 relative offsets assigned as their declarations are parsed; every slot- Compound literals (`(type){...}`) and designated initializers
- (`[i] = x`, `.field = x`) aren't supported at all - the two call sites
- that used them (`kernel/lang/script/eval.c`, `kernel/lang/compiler/
- codegen.c`) get refactored to avoid them in Phase T3, a deliberate
- scope-narrowing decision (see the plan) rather than an oversight.
+ Compound literals (`(type){...}`) and designated initializers
+ (`[i] = x`, `.field = x`) aren't supported at all - the two call sites
+ that used them (`kernel/lang/script/eval.c`'s `mkint`/`mkstr`
+ constructors, `kernel/lang/compiler/codegen.c`'s `binop_opcode`
+ function) were refactored to avoid them in Phase T3, a deliberate
+ scope-narrowing decision rather than an oversight.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)
$ grep -n "^## LD, OBJCOPY" /home/martin/workspace/prototypes/boot-to-claude/docs/toolchain.md
259:## LD, OBJCOPY
- ## LD, OBJCOPY
+ ## Kernel source adaptation (Phase T3)
+
+ The kernel source now expresses every low-level operation the way CC
+ expects it, ahead of Phase T7's actual switchover - so this phase's
+ changes are visible today even though the build still uses host
+ gcc/nasm/ld/objcopy:
+
+ - All 12 inline-asm sites (`kernel/{ata,serial,keyboard,idt,gdt}.c`,
+ `kernel/shell/shell.c`) now call named builtins declared in
+ `kernel/builtins.h` (`__builtin_outb`/`inb`/`outw`/`inw`/`hlt`/
+ `lidt`/`gdt_flush`) instead of raw `__asm__` blocks.
+ - `tools/mkfs_tinyfs.c` is libc-free: raw `__sys_open`/`read`/`write`/
+ `lseek`/`close`/`exit` (declared in `tools/host_syscalls.h`) instead
+ of `fopen`/`fread`/`fwrite`/`fseek`/`fclose`/`exit`/`perror`, and
+ `kernel/string.h` instead of the system `<string.h>` (which needed
+ adding `strchr` there, used to split `host-path:fs-name` seed specs).
+
+ Since GCC doesn't recognize any of these names as its own builtins, real
+ GCC-inline-asm bodies live in two small shim files -
+ `kernel/builtins_shim.c` and `tools/host_syscalls_shim.c` - that exist
+ *only* so the current interim build still links. Both get dropped from
+ the Makefile in Phase T7: CC intercepts every call to these names
+ directly in codegen and never needs (or could parse - CC has no
+ inline-asm support at all) a linkable definition. `docs/toolchain.md`'s
+ CC/[Builtins](#builtins-builtinsc) section above documents the actual
+ instruction sequences CC emits for each one; the shims mirror them
+ exactly (e.g. `__sys_open`'s hardcoded mode `420`) so behavior is
+ identical either way.
+
+ Verified with the *existing* toolchain, deliberately - this phase proves
+ the refactor is behavior-preserving before the new toolchain is even in
+ the loop: `tools/run_tests.sh` (full OS boot + shell + both languages)
+ and `tools/run_toolchain_tests.sh` (CC's own test battery) both stay
+ green throughout, built entirely with gcc/nasm/ld/objcopy as before.
+
+ ## LD, OBJCOPYThe 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)
- directly in codegen and never needs (or could parse - CC has no - inline-asm support at all) a linkable definition. `docs/toolchain.md`'s - CC/[Builtins](#builtins-builtinsc) section above documents the actual - instruction sequences CC emits for each one; the shims mirror them - exactly (e.g. `__sys_open`'s hardcoded mode `420`) so behavior is - identical either way. + directly in codegen and never needs (or could parse - CC has no + inline-asm support at all) a linkable definition. The "Builtins" section + above documents the actual instruction sequences CC emits for each one; + the shims mirror them exactly (e.g. `__sys_open`'s hardcoded mode `420`) + so behavior is identical either way.
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)
$ cd /home/martin/workspace/prototypes/boot-to-claude make clean >/dev/null rm -rf build/toolchain build/toolchain_tests git status --short
M Makefile M docs/tinyfs.md M docs/toolchain.md M kernel/ata.c M kernel/gdt.c M kernel/idt.c M kernel/keyboard.c M kernel/lang/compiler/codegen.c M kernel/lang/script/eval.c M kernel/serial.c M kernel/shell/shell.c M kernel/string.c M kernel/string.h M tools/mkfs_tinyfs.c ?? kernel/builtins.h ?? kernel/builtins_shim.c ?? tools/host_syscalls.h ?? tools/host_syscalls_shim.c
$ cd /home/martin/workspace/prototypes/boot-to-claude git add -A git status --short
M Makefile M docs/tinyfs.md M docs/toolchain.md M kernel/ata.c A kernel/builtins.h A kernel/builtins_shim.c M kernel/gdt.c M kernel/idt.c M kernel/keyboard.c M kernel/lang/compiler/codegen.c M kernel/lang/script/eval.c M kernel/serial.c M kernel/shell/shell.c M kernel/string.c M kernel/string.h A tools/host_syscalls.h A tools/host_syscalls_shim.c M tools/mkfs_tinyfs.c