Skip to content

CupidC Compiler

Frank edited this page Oct 10, 2026 · 39 revisions

CupidC Compiler

Hosted allocator source, 2026-10-10

The shared hosted runtime source now uses size bins for free storage on both hosts and shared Windows regions for small allocations. The allocator retains sixteen-byte alignment and failed-realloc data, and keeps storage reusable when OS release fails. tests.test_hosted_heap builds the actual runtime and its positive/failure callers entirely with verified Cupid tools, then runs strict CupidDis checks. Both Windows startup profiles run these contracts, including a Unicode filename beyond 260 characters. Both hosts finish matching prepared stage-three/stage-four cohorts. Both complete source qualifications now pass for committed 1d852023, with all 99 producer inputs bound to their Git blobs. Seed adoption still requires replacement-tool consumer acceptance. See the heap record.

The qualified replacement tools pass cold normal OS builds and all four strict boots on both hosts. All 429 complete object pairs and the complete image pair match the source-accepted products. Both full compatibility selections pass all 367 methods per host, with 705 passes and 29 declared platform skips in total. Independent review rechecks all outcomes, 99 committed inputs and unchanged installed seeds. All four original SDK profiles pass full publication; independent review accepts 22 programs, 104 SDK inputs and all 77 complete stage pairs per profile. A four-profile comparison rereads all 616 stage files and 88 published programs; every complete stream agrees across both hosts and profiles. The original Linux public selection also passes with its Windows-only boundary skip and complete independent review. Windows public methods and both actual Make bootstraps remain in progress before installed-seed adoption.

The native image argument owner is retained as an unapplied C patch with complete actual-host oracles and a repository replay. All four callers pass 47,816 calls across 11,954 sequences; seven complete shared object pairs agree. Both installed-seed replays pass, including allocation failure and borrowed/owned storage checks. Native discovery, observers, diagnostics and publication remain open. The normal producer cohort and its three Python coordinators are unchanged.

The native image integer prototype is retained as an unapplied C patch with a frozen oracle fixture and repository replay. Native and Cupid-built callers pass 66,816 complete cases under the actual Unicode 15 and 16 profiles, including eighty new digits accepted only by profile 16. Three whole checked object pairs agree. A separate hosted fgetc extension passes every unsigned byte value and EOF recovery. Both hosts also pass the installed-seed replay. The producer cohort and normal image recipe remain unchanged; complete native command integration stays open.

Qualified normal tools and SDK

The paired 344a9eee producer is qualified and its reviewed cohort is installed by 51f3c9ee. Normal user Make uses native ABI verification directly. The complete version-5 i386 service table has 103 fields, 412 bytes and 101 providers. User objects, executables and independent ABI reports match on Windows and Linux.

The hosted SDK captures 101 inputs and all 74 native stage comparisons. Explicit reviewed behavior authority remains checked through bootstrap, cache reuse and the final publication boundary. Both default and long-profile publications pass on both hosts without changing stage deadlines or fixture requirements. See the release context and ELF programs.

Toolchain publication policy context, 2026-10-04

The native publication policy accepts CUPMAN5 author and CUPMAN6 verification requests containing a captured manifest digest, supported plan and six ordered tool identities. The adapters validate the cohort through the shared seed reader and retain original observations through final checks. The native policy hashes the raw manifest, binds every seed observation and keeps all publication and raw-pair rules. Classic CUPMAN4/CUPMAN2 requests retain their historical checks.

9e566013 passes 163 source test methods per host, with three Windows POSIX skips. Checked Cupid tools build and run three verification profiles per host. Actual qualified candidate tools also build the author/check policy and pass valid, wrong-identity and recovery probes; other publication facts in those probes are unit fixtures. Kernel, artifact, image and strict private four-CPU boot acceptance pass.

This contract is outside the compiler producer snapshots. All 82 compiler inputs remain byte-identical to the qualified f6a8b6dd sources. Complete producer qualification now passes on both hosts. New-parent consumption and full hosted publication acceptance remain separate from seed installation and the ABI Make handoff.

Authorized Linux behavior plans, 2026-10-04

The release coordinator now carries the candidate Linux build plan into behavior fixtures. Previously, the staged 29-source ABI tools were checked against their parent's 27-source plan, and the native manifest reader correctly rejected the mismatch. Authorization captures immutable plan bytes; a wrong release-bound digest fails before fixture publication. Windows manifest layout, compiler inputs and tool images remain unchanged.

Actual prepared-tool replays and both host regression selections pass. Fresh committed complete qualification is running on both hosts. The unchanged 82-input preparations remain unqualified until complete behavior and release consumption pass. The fix is f6a8b6dd.

Artifact policy observation API, 2026-10-04

Source-head CupidBuild adds artifact_size_policy_validate_observations for immutable CUPSIZE3 bytes. Its role-ordered facts contain six Linux sizes, six Windows size/digest pairs, six Windows image observations and sixteen artifact observations. It checks the complete policy, paths, owners, sizes and failure markers. It clears results on failure, bounds diagnostics and retains no input pointers or mutable global state. The caller validates the selected release, both manifests, actual images and observation lifetime. The original CUPSIZE2 entry point retains its historical manifest and parent checks.

CupidBuild uses the shared release-aware readers before constructing these facts. This supports the 82-input ABI profile and release-supplied parent tuples without embedding new parent hashes. Hosted policy-only contracts keep their dependency closure. Both host selections and fresh Cupid-built command checks pass, including truncation, allocation, input drift and recovery cases.

The change is committed in 819cb3d4. ADR 0415 records the boundary. Windows kernel, artifact, image publication and strict private four-CPU boot acceptance pass. Installed seeds remain on their prior implementation; fresh paired producer/consumption proofs and installation remain required.

Native ABI bootstrap gates, 2026-10-04

Both complete bootstrap matrices require the native verify-user-abi --root ROOT command in stage three and stage four. Each generation runs eleven positive and eleven negative cases. The gate compares the complete independent oracle report, tests Unicode paths and escaped literals, rejects semantic and input drift, checks invalid arguments, and requires recovery after failures. It retains the six declaration identities, executable hashes and case results, and checks that each command preserves its input tree.

The Linux matrix requires 73 success, 66 failure and seven help cases. Native Windows requires 60 success, 54 failure and seven help cases. Fresh command builds pass on both hosts, as do all 262 fixed-point audit mutation cases. Supplemental checks on retained final-stage tools pass all 44 ABI calls per host. These checks do not replace a fresh complete paired release qualification.

The implementation is committed in 30598dd1. ADR 0414 keeps behavior fixtures separate from compiler producer snapshots. Installed seeds and the normal ABI recipe retain their current roles. Ownership remains 447 CupidBuild actions and five Python actions across 452 transforms. The five Python actions are the shared ABI gate, disk/ISO publication and the two hosted contract actions. Three user compilations depend on the one ABI gate; those edges are not separate actions.

Native ABI command at toolchain head, 2026-10-03

Source-head CupidBuild provides verify-user-abi --root ROOT. It observes the six syscall ABI sources and their ancestors, validates the captured bytes, rechecks those observations, and closes them before emitting JSON. The standalone ABI contract uses the same semantic module. Both check version 5, 103 fields, 412 table bytes, both VFS layouts, scalar and socket constants, and all 101 providers.

The memory API borrows bounded UTF-8 spans without trailing NUL bytes. Calls share no mutable state; failures clear the report. The command creates no files, locks or child processes. POSIX roots accept dot and repeated slash components and reject parent components to preserve ancestor checks. Source rechecks are sequential, so they do not imply an atomic filesystem snapshot. Incomplete string and character literals fail, including an escaped final quote. Well-formed escaped literals remain valid.

Fresh command builds use checked CupidC, CupidASM and CupidLD on Windows and Linux. Allocation, malformed-input, source-drift, close-failure and concurrent call tests cover the shared module and its filesystem coordinator. The final selection executes all 41 methods on Linux and 40 on Windows, with one POSIX-only Windows skip. The forced normal Windows user build also passes and retains the same complete ABI report. ADR 0413 records the ownership boundary.

Checks across three staged CupidBuild executables per host pass all 66 ABI commands. Complete reports match the independent oracle; incomplete literals and a missing input fail without success output, and every repair succeeds. The original inputs and staged executable bytes stay unchanged.

Windows long/alias preparation passes with 82 captured sources, 135 staged artifacts and 45 equal final-stage pairs. Its actual staged CupidBuild rejects the changed parent through the original provenance check. This preparation remains unqualified and contains no complete bootstrap report.

The complete Linux bootstrap passes with 29 C objects, one startup object and six tools in each of three stages. All 36 final-stage pairs match, and both final generations pass 62 success, 55 failure and seven help cases plus the Windows runtime bridge checks. Independent verification rehashes all 81 compiler inputs and 108 stage artifacts. This uses the installed Linux seed; a paired release remains required for promotion.

Removing one extra blank line at the validator's end of file preserves its recorded 79,452-byte object with all eight installed and staged compilers. The frozen bootstrap reports retain their earlier input hashes. Seed promotion still requires fresh qualification from the committed source.

The source audit records both native fault probes as host test fixtures. Its active inventory stays at 770 sources and 452 transforms; the unreachable inventory contains 43 files. The probes do not count as production migration.

The candidate has 29 C objects and four exact UTF-8 Windows profiles with 80, 81 or 82 source inputs. Installed seeds and the normal Python ABI gate retain their current roles. Ownership remains 447 CupidBuild actions and five Python actions across 452 transforms. A paired release and the Make recipe handoff remain required.

The normal Windows OS replay passes with host C/ASM tools forbidden and all sixteen exact artifact checks satisfied. Its complete embedded manual is 152,237 bytes; the measured raw kernel is 9,585,024 bytes. Only the three changed kernel size-policy rows are updated.

A strict private-image max/e1000 four-CPU boot passes the SMP, FPU, RDRAND, 62-check crypto and DHCP contract, reaches the desktop and Terminal, and completes ls. The original 200 MiB image stays unchanged, with SHA-256 738742dc1060dff777f36f3cc4dc43e6c092a5ceaa47d1737cb299c460c79e87. The normal image replay preserves existing FAT data. This result covers the Windows normal build and boot; paired Linux OS and full Doom runtime acceptance remain separate qualifications.

Shared compiler block assertions

The shared bootstrap frontend accepts C11 _Static_assert declarations inside function compounds, including nested blocks and declarations after return. It checks local typedefs, enum constants, and target layout at compile time. Successful assertions add no runtime code. This source-head capability is separate from the checked seed and private JIT/AOT parser. ADR 0412 records its tests and limits.

Verified physical user-link source, 2026-10-01

Commit 77a114a0 integrates the physical cupidbuild_link_user_object API. It retains an existing output-parent chain, freezes the approved object and six-tool cohort, runs checked CupidLD and CupidDis, validates the user ELF and publishes through the guarded transaction. CupidLD accepts one caller-owned output flag; ordinary invocation retains its atomic publisher. Equal validated executable bytes preserve timestamps.

Both hosts pass 172 native methods, fourteen checked link methods, 57 checked observer methods and sixteen user compiler methods. Corrected conditional audit fixtures pass their affected methods and all 39 native and 39 checked preprocessor methods. Both normal OS builds forbid host code-producing tools. Final independent verification rereads 1,549 inputs per host, sixteen artifacts, 431 link inputs, unchanged user executables and eight private four-CPU boots. Both hosts pass the platform ABI, kernel/disassembly/shell/SMP smoke and separate cat, hello and ls boots. Base and staged images remain unchanged.

The 65,853-byte manual is embedded in both kernels. Raw kernels are 9,578,888 bytes; pass-one and final ELFs are 9,675,196 and 9,806,268 bytes. Both 200 MiB images have SHA-256 784d906d6a122e7a600a0a8a1fe0022e7cfa7a291f959605c0898f37d25a80db. Linux passes complete default and long-profile publications with 89 control inputs, 76 or 77 producer inputs, 22 artifacts and 67 matching contract pairs. The source record retains the stale-fixture and evidence-wrapper failures.

The installed seeds remain 78e71bd6, and production ownership remains 444 CupidBuild/eight Python actions. The three normal user links still use Python. Alias resolution, CLI exposure, new checked seeds and recipe adoption remain separate acceptance steps. Serial record atomicity and full Doom gameplay, audio, save/load, reboot and performance acceptance remain open.

The source record binds the implementation and final evidence.

Accepted user compiler recipes, 2026-10-01

Commit 1440d33f installs the paired 78e71bd6 seeds and three user object recipes. compile-user keeps each source/header bundle, fixed freestanding profile, retained publication checks and syscall ABI scheduling edge. Production ownership is 444 CupidBuild and eight Python actions.

The 77-input producer snapshot is unchanged. Native Linux and Windows proofs match 34 and 42 stage-three/four pairs; both long-profile consumers reproduce the proved tools. The complete default Windows bootstrap also passes its own 76-input plan. Each proposed-seed regression root passes the 254-method selection, with sixteen Windows-only skips on Linux. Complete publications match 89 inputs, 77 producer inputs and 22 artifacts.

Final paired acceptance verifies 1,547 captured inputs per host, sixteen artifacts and 431 link inputs. Both normal Make builds forbid host code-producing tools and produce matching images. User ABI and artifact gates, private four-CPU kernel/SMP checks and separate cat, hello and ls boots pass on both hosts. The three executables retain their bytes. Installed ownership tests and generated audit checks also pass on both hosts. The handoff record records the manual size calibration and acceptance evidence.

The three user links remain Python transactions. Filesystem alias handling and integration of the isolated native user-link foundation remain work for the next handoff. Serial record atomicity and long Windows child working directories remain open. Full Doom gameplay, audio quality, save/load, reboot and performance acceptance are still required.

Earlier hosted toolchain checkpoint, 2026-09-30

Source-head CupidBuild now coordinates compile-user through checked CupidC. Each approved example captures exactly its source and user/cupid.h, retains the fixed freestanding profile and configurable output parents, and publishes a validated object. Both hosts reproduce the three current objects with native and CupidC-built coordinators. The staged helper and parent-reader checks pass. The complete sixteen-method integrated caller suite passes on both hosts. The committed paired native proof and final OS/user/boot replay also pass on both hosts. Both proposed long-profile consumers and the complete default Windows bootstrap pass independent verification. Both proposed-seed roots pass the complete 254-test regression selection and fresh normal OS builds. The paired artifacts, link inputs and images match the accepted source replay. The isolated Windows Make handoff passes four output paths, oracle byte comparisons and equal-object timestamp checks. Complete publication retries, seed carriage and active recipe adoption remain required. Windows's fresh native ABI/artifact/private-boot checks pass; its three external programs also pass separate four-CPU boots with their PID-bound runtime predicates and full SMP validation. Independent rereads verify both completed records. Split serial terminal markers remain an open logging issue. Linux's complete publication, normal OS/ABI acceptance, four-path user Make and three external runtime boots also pass. Paired verification matches the users, objects and images; the three user links remain separate. The repository source record retains exact evidence and limits.

Fresh native Windows and Linux proofs from 8403b0a8 pass with the same 77-input long-file source snapshot. Both checked seeds rebuild themselves; the complete default 76-input Windows bootstrap also passes with its exact plan. Independent verification checks every staged artifact and the committed source. The reviewed pair is installed after current-publication verification and user/boot acceptance on both hosts.

The Linux user ABI recipe now carries explicit long-profile selection. Twenty ABI/profile methods pass on each host, including current-publication reuse and live-input rejection. The installed-seed Windows manual replay and proposed Windows and Linux images pass private four-CPU disassembly, shell and SMP checks. Their images and all 431 link inputs match; normal build ownership remains 441 CupidBuild actions and eleven Python actions.

The latest retained IWAD timedemo still misses its 1,200-second deadline. An isolated sustained-note check produces identical PCM with the actual CupidC-built OPL object and GCC oracle objects, but native timing does not prove QEMU or whole-game performance. Gameplay, audio quality, save/load and reboot acceptance remain open. The bootstrap record has the source, image and diagnostic evidence.

CupidC is a HolyC-inspired C compiler built directly into the cupid-os kernel. It compiles .cc source files to native x86 machine code and runs them with full ring-0 privileges - no VM, no interpreter, no restrictions. Inspired by TempleOS's HolyC, CupidC embodies cupid-os's philosophy of complete user empowerment.


Overview

Feature Details
Language C-like (HolyC-inspired)
Target x86 32-bit machine code
Compiler type Single-pass recursive descent
Calling convention cdecl
Execution modes JIT (in-memory) and AOT (ELF32 binary)
Privilege level Ring 0 - full system access
Source extension .cc
Code size limit 128 KB code, 32 KB data
Max source file 256 KB
Max functions 256
Max symbols 512
Max structs 32 (up to 16 fields each)

Getting Started

JIT Mode - Compile and Run Instantly

> cupidc program.cc

Compiles the source to memory and executes immediately. No binary is saved to disk. Perfect for rapid development and testing.

AOT Mode - Compile to ELF Binary

> ccc program.cc -o program
> exec program

Compiles the source to a persistent ELF32 binary on the FAT16 disk. The binary can be re-executed without recompilation.

If -o is omitted, the output name is derived from the source file (e.g., program.cc → program).


Language Reference

Types

Type Size Description
int 32-bit Signed integer
char 8-bit Character / byte
bool 32-bit Boolean (alias for int)
void - No value (functions only)
int* 32-bit Pointer to int
char* 32-bit Pointer to char
struct varies User-defined composite type
struct* 32-bit Pointer to struct

Arrays

Fixed-size arrays, both local (stack-allocated) and global (data section):

// Global arrays - stored in data section
int scores[100];
char buffer[256];

void main() {
    // Local arrays - stack-allocated
    int arr[10];
    char buf[64];

    arr[0] = 42;
    buf[0] = 'A';
}

Array elements are accessed with arr[i] and can be assigned with arr[i] = value.

Compound assignment also works: arr[i] += value, arr[i] -= value, arr[i] *= value, arr[i] /= value.

Structs

User-defined composite types with named fields:

struct Point {
    int x;
    int y;
};

struct Rect {
    struct Point origin;
    int width;
    int height;
};

void main() {
    struct Point p;
    p.x = 10;
    p.y = 20;
    print_int(p.x);

    // Heap-allocated structs via pointer
    struct Point *hp = kmalloc(sizeof(struct Point));
    hp->x = 100;
    hp->y = 200;
    print_int(hp->x);
    kfree(hp);
}

Struct features:

  • Up to 32 named struct types, each with up to 16 fields
  • Field types: int, char, void*, int*, char*, nested struct
  • Stack-allocated structs (struct Foo s;) are zero-initialized
  • Heap-allocated structs via kmalloc(sizeof(struct Foo))
  • Member access with . (value) and -> (pointer)
  • Chained access: rect.origin.x, ptr->origin.y
  • All fields are 4-byte aligned for x86 compatibility

sizeof Operator

Compute the size of a type at compile time:

int a = sizeof(int);           // 4
int b = sizeof(char);          // 1
int c = sizeof(struct Point);  // 8 (two ints)

struct Foo *p = kmalloc(sizeof(struct Foo));

Enumerations

Define named integer constants:

enum {
    RED,        // 0
    GREEN,      // 1
    BLUE        // 2
};

enum Colors {
    BLACK = 0,
    WHITE = 15,
    YELLOW = 14
};

void main() {
    int color = RED;       // 0
    int bg = WHITE;        // 15
    print_int(color);
}

Enum values are stored as global integers in the data section. Values auto-increment from 0, or can be set explicitly with = value (including negative values).

Global Variables

Variables declared outside functions are stored in the data section:

// Scalar globals with optional initializers
int count = 0;
int max_size = 1024;
int error_code = -1;
char *greeting = "Hello";

// Global arrays
int data[256];
char name[64];

// Global struct variables
struct Point origin;

void main() {
    count = 42;
    data[0] = 100;
    origin.x = 10;
    origin.y = 20;
    print(greeting);
}

Global variables support:

  • Integer and character literal initializers (including negative values)
  • String literal initializers (pointer to data section string)
  • Arrays of any supported element type
  • Struct variables (zero-initialized)
  • All operators: =, +=, -=, *=, /=, ++, --

Built-in Constants

Constant Value Description
NULL 0 Null pointer constant
true 1 Boolean true
false 0 Boolean false

Operators

Full C operator precedence is supported:

Precedence Operators Description
10 * / % Multiplication, division, modulo
9 + - Addition, subtraction
8 << >> Bitwise shift
7 < > <= >= Relational comparison
6 == != Equality comparison
5 & Bitwise AND
4 ^ Bitwise XOR
3 | Bitwise OR
2 && Logical AND
1 || Logical OR

Unary operators: ! (logical NOT), ~ (bitwise NOT), - (negate), * (dereference), & (address-of), ++ / -- (increment/decrement)

Assignment operators: =, +=, -=, *=, /=

Type casts: (int)expr, (char*)ptr

Control Flow

// If/else
if (x > 5) {
    print("big");
} else {
    print("small");
}

// While loop
while (i < 10) {
    i++;
}

// Do-while loop
do {
    process();
    i++;
} while (i < 10);

// For loop
for (int i = 0; i < 10; i++) {
    print_int(i);
}

// Switch/case
switch (cmd) {
    case 'a':
        print("add");
        break;
    case 'd':
        print("delete");
        break;
    default:
        print("unknown");
        break;
}

break and continue work inside while, for, do-while loops, and switch statements.

Break statement implementation: CupidC supports multiple break statements per loop (up to 32 breaks per loop, 64 nested loop levels). The compiler maintains an array of break patch locations and patches them all when the loop ends.

Functions

Functions use the cdecl calling convention. Up to 16 parameters per function.

int add(int a, int b) {
    return a + b;
}

void main() {
    int result = add(5, 10);
    print_int(result);
}

Every program must have a main() function - it is the entry point.

Forward references are supported: functions can call other functions that are defined later in the file. The compiler resolves these after parsing is complete.

String and Character Literals

char* msg = "Hello, CupidOS!\n";
char ch = 'A';
char esc = '\x1B';  // ESC character for ANSI codes

Escape Sequences

CupidC supports the following escape sequences in string and character literals:

Escape Meaning ASCII Value
\n Newline (LF) 10
\t Tab 9
\r Carriage return (CR) 13
\b Backspace 8
\\ Backslash 92
\' Single quote 39
\" Double quote 34
\0 Null terminator 0
\xNN Hexadecimal byte (00-FF) Variable

Hexadecimal Escapes: The \xNN escape sequence allows specifying a byte value in hexadecimal. Both uppercase and lowercase hex digits are supported:

char *red = "\x1B[31m";     // ANSI red color code
print("\x48\x69");          // Prints "Hi" (0x48='H', 0x69='i')

String literals are stored in the data section and their address is loaded into registers.

Comments

// Line comments

/* Block
   comments */

Inline Assembly

Direct x86 assembly inside CupidC functions:

void disable_interrupts() {
    asm {
        cli;
        hlt;
    }
}

Supported instructions:

Category Instructions
No operand cli, sti, hlt, nop, ret, iret, pushad, popad, cdq
Register push, pop, inc, dec
Reg, Reg/Imm mov, add, sub, xor, cmp
I/O in al, dx / out dx, al
Control call, int

Registers: eax, ecx, edx, ebx, esp, ebp, esi, edi, al, cl, dl, bl


Kernel Bindings

CupidC programs can call kernel functions directly. These are pre-registered in the symbol table at compile time:

Console Output

Function Signature Description
print void print(char* s) Print string to screen
println void println(char* s) Print string + newline
putchar void putchar(char c) Print single character
print_int void print_int(int n) Print integer
print_hex void print_hex(int n) Print hex value
clear_screen void clear_screen() Clear the display

Console Input

Function Signature Description
getchar int getchar() Read a single character from the keyboard (blocking)

Memory Management

Function Signature Description
kmalloc void* kmalloc(int size) Allocate memory (kernel heap)
kfree void kfree(void* ptr) Free allocated memory

String Operations

Function Signature Description
strlen int strlen(char* s) Get string length
strcmp int strcmp(char* a, char* b) Compare two strings
strncmp int strncmp(char* a, char* b, int n) Compare up to n characters
strcpy char* strcpy(char* dst, char* src) Copy string (including null terminator)
strncpy char* strncpy(char* dst, char* src, int n) Copy up to n characters (pads with nulls)
strcat char* strcat(char* dst, char* src) Concatenate src onto end of dst
strchr char* strchr(char* s, int c) Find first occurrence of character c in s
strstr char* strstr(char* haystack, char* needle) Find first occurrence of substring
memset void* memset(void* p, int val, int n) Fill memory
memcpy void* memcpy(void* dst, void* src, int n) Copy memory
memcmp int memcmp(void* a, void* b, int n) Compare n bytes of memory

Port I/O

Function Signature Description
outb void outb(int port, int value) Write byte to I/O port
inb int inb(int port) Read byte from I/O port

File System

Function Signature Description
vfs_open int vfs_open(char* path, int flags) Open a file
vfs_close int vfs_close(int fd) Close a file
vfs_read int vfs_read(int fd, void* buf, int size) Read from file
vfs_write int vfs_write(int fd, void* buf, int size) Write to file
vfs_seek int vfs_seek(int fd, int offset, int whence) Seek within an open file
vfs_stat int vfs_stat(char* path, void* stat_buf) Get file info (size + type)
vfs_readdir int vfs_readdir(int fd, void* dirent) Read next directory entry
vfs_mkdir int vfs_mkdir(char* path) Create a directory
vfs_unlink int vfs_unlink(char* path) Delete a file
vfs_rename int vfs_rename(char* old, char* new) Move/rename a file (copy + delete)

Shell Integration

Function Signature Description
get_args char* get_args() Get command-line arguments from the shell
get_cwd char* get_cwd() Get current working directory
set_cwd void set_cwd(char* path) Set the shell's current working directory
resolve_path void resolve_path(char* input, char* out) Resolve a relative path against CWD (out must be 256 bytes)
get_history_count int get_history_count() Get the number of entries in shell history
get_history_entry char* get_history_entry(int index) Get history entry by offset from newest (0 = most recent)

Timer

Function Signature Description
uptime_ms int uptime_ms() Get system uptime in milliseconds

RTC (Real-Time Clock)

Function Signature Description
rtc_hour int rtc_hour() Current hour (0-23)
rtc_minute int rtc_minute() Current minute (0-59)
rtc_second int rtc_second() Current second (0-59)
rtc_day int rtc_day() Current day of month (1-31)
rtc_month int rtc_month() Current month (1-12)
rtc_year int rtc_year() Current year (e.g. 2026)
rtc_weekday int rtc_weekday() Day of week (0=Sunday, 6=Saturday)
rtc_epoch int rtc_epoch() Seconds since Unix epoch (Jan 1, 1970)
date_full_string char* date_full_string() Formatted date: "Thursday, February 6, 2026"
date_short_string char* date_short_string() Formatted date: "Feb 6, 2026"
time_string char* time_string() Formatted time: "6:32:15 PM"
time_short_string char* time_short_string() Formatted time: "6:32 PM"

Process Management

Function Signature Description
yield void yield() Yield CPU to scheduler
exit void exit() Terminate current process
exec int exec(char* path, char* args) Execute a program
process_list void process_list() Print all processes (PID, state, name)
process_kill void process_kill(int pid) Terminate a process by PID
spawn_test int spawn_test(int count) Spawn N test counting processes (max 16), returns count spawned

Mount Info

Function Signature Description
mount_count int mount_count() Get the number of mounted filesystems
mount_name char* mount_name(int index) Get the filesystem name for mount at index
mount_path char* mount_path(int index) Get the mount path for mount at index

Diagnostics

Function Signature Description
memstats void memstats() Print heap and physical memory statistics
detect_memory_leaks void detect_memory_leaks(int ms) Report allocations older than ms milliseconds
heap_check_integrity void heap_check_integrity() Walk heap blocks and verify canary values
pmm_free_pages int pmm_free_pages() Number of free physical 4 KB pages
pmm_total_pages int pmm_total_pages() Total number of physical 4 KB pages
dump_stack_trace void dump_stack_trace() Print current call stack (EBP frame chain)
dump_registers void dump_registers() Print all CPU registers + EFLAGS
peek_byte int peek_byte(int addr) Read one byte from a memory address
print_hex_byte void print_hex_byte(int val) Print a byte as 2 hex digits
get_cpu_mhz int get_cpu_mhz() CPU frequency in MHz
timer_get_frequency int timer_get_frequency() Timer interrupt rate in Hz
process_get_count int process_get_count() Number of running processes

BMP Image Encoding/Decoding

Function Signature Description
bmp_get_info int bmp_get_info(char* path, void* info) Get BMP dimensions without loading pixels (fills bmp_info_t)
bmp_decode int bmp_decode(char* path, int* buf, int size) Decode 24-bit BMP to 32bpp XRGB buffer
bmp_encode int bmp_encode(char* path, int* buf, int w, int h) Encode 32bpp XRGB buffer to 24-bit BMP file
bmp_decode_to_fb int bmp_decode_to_fb(char* path, int x, int y) Decode BMP directly to framebuffer at position

All BMP functions return 0 on success. Error codes: -1 (invalid), -2 (unsupported format), -3 (I/O error), -4 (buffer too small).

File Dialogs

Function Signature Description
file_dialog_open int file_dialog_open(char* start, char* result, char* ext) Show modal file open dialog; returns 1 if selected, 0 if cancelled
file_dialog_save int file_dialog_save(char* start, char* name, char* result, char* ext) Show modal file save dialog; returns 1 if confirmed, 0 if cancelled

The result buffer must be 128 bytes. Pass 0 for ext to show all files.

VFS Helpers

Function Signature Description
vfs_read_all int vfs_read_all(char* path, void* buf, int max) Read entire file into buffer; returns bytes read or negative error
vfs_write_all int vfs_write_all(char* path, void* buf, int size) Write buffer to file (creates/truncates); returns bytes written or negative error
vfs_read_text int vfs_read_text(char* path, char* buf, int max) Read text file as null-terminated string; returns string length
vfs_write_text int vfs_write_text(char* path, char* text) Write null-terminated string to file; returns bytes written

Block Cache

Function Signature Description
blockcache_sync void blockcache_sync() Flush all dirty cache blocks to disk
blockcache_stats void blockcache_stats() Print cache hit/miss statistics

Serial Log Control

Function Signature Description
set_log_level void set_log_level(int level) Set log level (0=debug, 1=info, 2=warn, 3=error, 4=panic)
get_log_level_name char* get_log_level_name() Current log level as a string
print_log_buffer void print_log_buffer() Print the circular log buffer contents

Crash Testing

Function Signature Description
kernel_panic void kernel_panic(char* msg) Trigger a kernel panic with message
crashtest_nullptr void crashtest_nullptr() Dereference NULL pointer
crashtest_divzero void crashtest_divzero() Divide by zero
crashtest_overflow void crashtest_overflow() Overflow heap buffer (canary detection)
crashtest_stackoverflow void crashtest_stackoverflow() Allocate 64 KB on stack (page fault)

Examples

Hello World

void main() {
    println("Hello from CupidC!");
}

Fibonacci

int fib(int n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);
}

void main() {
    for (int i = 0; i < 10; i++) {
        print_int(fib(i));
        print(" ");
    }
    print("\n");
}

Working with Pointers

void swap(int* a, int* b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

void main() {
    int x = 10;
    int y = 20;
    swap(&x, &y);
    print_int(x);   // 20
    print(" ");
    print_int(y);   // 10
    print("\n");
}

Direct Hardware Access

void main() {
    // Write 'A' to COM1 serial port
    outb(0x3F8, 65);

    // Read keyboard scancode
    int code = inb(0x60);
    print_hex(code);
    print("\n");
}

Inline Assembly

void main() {
    int result;

    asm {
        mov eax, 42;
    }

    // EAX now contains 42
    print("The answer is: ");
    print_int(42);
    print("\n");

    // Disable then re-enable interrupts
    asm {
        cli;
        sti;
    }
}

Array Manipulation

void main() {
    int arr[5];

    for (int i = 0; i < 5; i++) {
        arr[i] = i * i;
    }

    for (int i = 0; i < 5; i++) {
        print_int(arr[i]);
        print(" ");
    }
    print("\n");
}

String Processing

void main() {
    char* msg = "CupidOS";
    int len = strlen(msg);

    print("String: ");
    print(msg);
    print("\nLength: ");
    print_int(len);
    print("\n");
}

File I/O

void main() {
    int fd = vfs_open("/home/hello.txt", 0);
    if (fd < 0) {
        println("Cannot open file");
        return;
    }

    char buf[128];
    int n = vfs_read(fd, buf, 127);
    if (n > 0) {
        buf[n] = 0;
        println(buf);
    }

    vfs_close(fd);
}

Common Patterns

Parsing Command-Line Arguments

Programs receive arguments as a single string via get_args(). To parse multiple space-separated arguments, use a token parsing function:

// Parse a single token from a string
int parse_token(char *str, int start, char *out, int maxlen) {
    int i = start;

    // Skip leading spaces
    while (str[i] == ' ' || str[i] == '\t') {
        i = i + 1;
    }

    // Check if end of string
    if (str[i] == 0) {
        out[0] = 0;
        return 0;
    }

    // Copy token until space or end
    int j = 0;
    while (str[i] != 0 && str[i] != ' ' && str[i] != '\t' && j < maxlen - 1) {
        out[j] = str[i];
        i = i + 1;
        j = j + 1;
    }
    out[j] = 0;

    return i - start;
}

// Usage example
void main() {
    char *args = (char*)get_args();
    char token[256];
    int pos = 0;

    while (1) {
        int len = parse_token(args, pos, token, 256);
        if (len == 0) break;  // No more tokens

        print("Token: ");
        print(token);
        print("\n");

        pos = pos + len;
    }
}

This pattern is used in commands like rm to handle multiple file arguments.

Error Handling with VFS

VFS functions return negative error codes on failure. Check return values and provide user-friendly error messages:

int fd = vfs_open(path, 0);
if (fd < 0) {
    print("Error opening file: ");
    print(path);
    print("\n");
    return;
}

// Use file...
vfs_close(fd);

Common VFS error codes:

  • -2: No such file or directory (ENOENT)
  • -13: Permission denied (EACCES)
  • -21: Is a directory (EISDIR)

Using ANSI Colors

Use hexadecimal escape sequences to embed ANSI color codes:

void main() {
    char *red = "\x1B[31m";
    char *green = "\x1B[32m";
    char *reset = "\x1B[0m";

    print(red);
    print("Error: ");
    print(reset);
    print("Something went wrong\n");

    print(green);
    print("Success!\n");
    print(reset);
}

Common ANSI codes:

  • \x1B[0m - Reset all attributes
  • \x1B[31m - Red text
  • \x1B[32m - Green text
  • \x1B[33m - Yellow text
  • \x1B[34m - Blue text
  • \x1B[35m - Magenta text
  • \x1B[36m - Cyan text
  • \x1B[95m - Bright magenta

Compiler Architecture

Pipeline

CupidC is a single-pass compiler - it lexes, parses, and emits x86 machine code in one pass through the source:

Source (.cc)
    │
    ▼
┌─────────────────┐
│  Lexer           │  cupidc_lex.c
│  (Tokenization)  │  Keywords, identifiers, literals, operators
└────────┬────────┘
         │ token stream
         ▼
┌─────────────────┐
│  Parser +        │  cupidc_parse.c
│  Code Generator  │  Recursive descent → x86 machine code
└────────┬────────┘
         │ raw bytes
         ▼
┌─────────────────┐
│  JIT: Execute    │  cupidc.c - copy to memory, jump to main()
│  AOT: Write ELF  │  cupidc_elf.c - emit ELF32 binary to disk
└─────────────────┘

Source Files

File Lines Role
cupidc.h ~250 Header: token types, symbol table, compiler state, public API
cupidc.c ~790 Driver: JIT/AOT entry points, kernel bindings, state init
cupidc_lex.c ~445 Lexer: tokenizes source into keywords, literals, operators
cupidc_parse.c ~2485 Parser + x86 code generator: the core of the compiler
cupidc_elf.c - ELF32 binary writer for AOT mode

Lexer

The lexer (cupidc_lex.c) breaks source text into tokens:

  • Keywords: int, char, void, bool, if, else, while, for, do, return, asm, break, continue, struct, sizeof, switch, case, default, enum
  • Identifiers: variable and function names
  • Literals: decimal integers, hex integers (0xFF), strings ("..."), character literals ('A')
  • Operators: all arithmetic, comparison, logical, bitwise, assignment
  • Delimiters: ( ) { } [ ] ; ,

Skips whitespace and both // line comments and /* */ block comments.

Parser & Code Generator

The parser (cupidc_parse.c) is a recursive descent parser that directly emits x86 machine code bytes into a code buffer. There is no AST or intermediate representation.

Key parsing functions:

Function Purpose
cc_parse_program() Top-level: parse functions and globals
cc_parse_function() Function definition with prologue/epilogue
cc_parse_statement() Statement dispatch (if, while, for, return, etc.)
cc_parse_expression() Operator precedence climbing
cc_parse_primary() Atoms: numbers, strings, identifiers, unary ops
cc_parse_asm_block() Inline assembly parsing and encoding

Code generation pattern:

  • Expressions evaluate into EAX
  • Binary operations: push left to stack, evaluate right into EAX, pop left into EBX, emit operation
  • Function calls: push arguments, call, clean stack
  • Locals accessed via [EBP - offset], params via [EBP + offset]

Symbol Table

Symbols are stored in a flat array (max 256 entries), searched linearly from the end so that locals shadow globals:

Kind Description Storage
SYM_LOCAL Local variable [EBP - offset]
SYM_PARAM Function parameter [EBP + offset]
SYM_FUNC User-defined function Code offset
SYM_KERNEL Kernel binding Absolute address
SYM_GLOBAL Global variable Data section address

Memory Layout

JIT Mode:
  0x00400000 - 0x004FFFFF  Code region (up to 64KB used)
  0x00500000 - 0x005FFFFF  Data region (strings, globals)

AOT Mode:
  0x00200000+               ELF load address
  Code and data packed into ELF segments

Forward References

When the parser encounters a call to an undefined function, it emits a placeholder call instruction and records a patch entry. After the entire program is parsed, cc_parse_program() resolves all forward references by patching the call targets with the correct addresses.


Limitations

  • 128 KB code limit and 32 KB data limit per program
  • 512 symbols maximum (functions + variables + kernel bindings)
  • 16 parameters maximum per function
  • 32 struct definitions with up to 16 fields each
  • No preprocessor (#include, #define not supported) - use enum for constants
  • No multi-file compilation - single source file only
  • No floating point - integer arithmetic only
  • No standard library - only kernel bindings are available
  • No function pointers - cannot store/call through function pointer variables
  • No ternary operator (?:) - use if/else instead
  • No variadic functions - fixed parameter count only
  • Limited optimization - single-pass compilation with no optimization passes

Shell Commands

Command Usage Description
cupidc cupidc <file.cc> JIT compile and execute a CupidC source file
ccc ccc <file.cc> -o <output> AOT compile to ELF32 binary

User Programs (TempleOS-style)

CupidC programs can be placed in /bin/ or /home/bin/ and the shell discovers them automatically - no kernel recompile needed. Just type the program name and it runs.

How It Works

When you type a command the shell doesn't recognize, it searches in order:

  1. /bin/<cmd> - ELF/CUPD binary in ramfs
  2. /bin/<cmd>.cc - CupidC source in ramfs (JIT compiled)
  3. /home/bin/<cmd> - ELF binary on disk
  4. /home/bin/<cmd>.cc - CupidC source on disk (JIT compiled)

CupidC .cc files are JIT-compiled and run instantly. No build step.

Example: mv (Move/Rename Files)

The mv command is a CupidC user program at /bin/mv.cc:

> mv old.txt new.txt        # rename a file
> mv report.txt /tmp/       # move into a directory
> mv /home/a.txt /home/b/   # absolute paths

Source (bin/mv.cc):

// mv.cc - move/rename files for CupidOS

void resolve_path(char *out, char *path) {
    int i = 0;
    if (path[0] == '/') {
        while (path[i]) { out[i] = path[i]; i = i + 1; }
        out[i] = 0;
        return;
    }
    char *cwd = (char*)get_cwd();
    int ci = 0;
    while (cwd[ci]) { out[i] = cwd[ci]; i = i + 1; ci = ci + 1; }
    if (i > 1) { out[i] = '/'; i = i + 1; }
    int pi = 0;
    while (path[pi]) { out[i] = path[pi]; i = i + 1; pi = pi + 1; }
    out[i] = 0;
}

void main() {
    char *args = (char*)get_args();
    // ... parse source and dest, resolve paths,
    // check if dest is a directory, call vfs_rename()
}

Key patterns used:

  • get_args() - retrieve shell arguments
  • get_cwd() - resolve relative paths
  • vfs_stat() - check if destination is a directory
  • vfs_rename() - perform the move

Writing Your Own Program

See the User Programs page for a complete guide.


Workflow

  1. Write a .cc source file using the ed editor:

    > ed /home/hello.cc
    a
    void main() {
        println("Hello from CupidC!");
    }
    .
    w
    q
    
  2. Run it with JIT compilation:

    > cupidc /home/hello.cc
    Hello from CupidC!
    
  3. Or compile to a persistent binary:

    > ccc /home/hello.cc -o /home/hello
    Compiled: 42 bytes code, 20 bytes data
    Written to /home/hello
    > exec /home/hello
    Hello from CupidC!
    

Comparison with HolyC

CupidC draws direct inspiration from TempleOS's HolyC:

Feature HolyC (TempleOS) CupidC (cupid-os)
Execution JIT compiled JIT + AOT (ELF)
Architecture x86-64 x86-32
Privilege Ring 0 Ring 0
Types Full C types + classes int, char, void, bool, pointers, arrays, structs
Enums Yes Yes
Inline ASM Yes Yes
Port I/O Direct access inb()/outb() builtins
Hardware access Full Full
Structs Yes Yes
Classes Yes No
Preprocessor #include No

Shell REPL

CupidOS now uses a TempleOS-style CupidC REPL directly at the normal shell prompt. The shell tries to compile each line as CupidC first and only falls back to normal command dispatch when REPL parsing or compilation fails. The north-star behavior is TempleOS's ExeCmdLine(), LexStmt2Bin(), and CmdLinePmt().

Prompt behavior

  • End statements and expressions with semicolons, HolyC-style.
  • Zero-argument REPL functions can be invoked as Foo;.
  • Multi-line blocks stay open until braces balance.
  • The next prompt shows the elapsed execution time.
  • Expression results are surfaced as TempleOS-style ans.
  • Forward-call fixups persist across prompt entries, so later function definitions can resolve earlier REPL-defined callers.

Example:

/home> U32 x = 7;
/home> x + 5;
0.001s ans=0x0000000C=12
/home> ans;
0.000s ans=0x0000000C=12
/home> U32 Add(U32 a, U32 b) {
..>   return a + b;
..> }
/home> Add(2, 3);
0.000s ans=0x00000005=5

Fallback behavior

These still resolve as shell commands because they are not valid CupidC REPL input:

help
ls /home

Resetting REPL state

Use:

reset

This clears persistent REPL variables, functions, structs, typedefs, and ans.

Clone this wiki locally