Repository navigation
CupidC Compiler
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
| 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) |
> cupidc program.cc
Compiles the source to memory and executes immediately. No binary is saved to disk. Perfect for rapid development and testing.
> 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).
| 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 |
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.
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*, nestedstruct - 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
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));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).
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:
=,+=,-=,*=,/=,++,--
| Constant | Value | Description |
|---|---|---|
NULL |
0 | Null pointer constant |
true |
1 | Boolean true |
false |
0 | Boolean false |
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
// 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 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.
char* msg = "Hello, CupidOS!\n";
char ch = 'A';
char esc = '\x1B'; // ESC character for ANSI codesCupidC 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.
// Line comments
/* Block
comments */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
CupidC programs can call kernel functions directly. These are pre-registered in the symbol table at compile time:
| 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 |
| Function | Signature | Description |
|---|---|---|
getchar |
int getchar() |
Read a single character from the keyboard (blocking) |
| Function | Signature | Description |
|---|---|---|
kmalloc |
void* kmalloc(int size) |
Allocate memory (kernel heap) |
kfree |
void kfree(void* ptr) |
Free allocated memory |
| 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 |
| 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 |
| 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) |
| 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) |
| Function | Signature | Description |
|---|---|---|
uptime_ms |
int uptime_ms() |
Get system uptime in milliseconds |
| 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" |
| 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 |
| 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 |
| 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 |
| 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).
| 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.
| 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 |
| 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 |
| 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 |
| 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) |
void main() {
println("Hello from CupidC!");
}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");
}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");
}void main() {
// Write 'A' to COM1 serial port
outb(0x3F8, 65);
// Read keyboard scancode
int code = inb(0x60);
print_hex(code);
print("\n");
}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;
}
}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");
}void main() {
char* msg = "CupidOS";
int len = strlen(msg);
print("String: ");
print(msg);
print("\nLength: ");
print_int(len);
print("\n");
}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);
}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.
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)
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
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
└─────────────────┘
| 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 |
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.
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 intoEBX, emit operation - Function calls: push arguments,
call, clean stack - Locals accessed via
[EBP - offset], params via[EBP + offset]
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 |
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
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.
- 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,#definenot supported) - useenumfor 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 (
?:) - useif/elseinstead - No variadic functions - fixed parameter count only
- Limited optimization - single-pass compilation with no optimization passes
| 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 |
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.
When you type a command the shell doesn't recognize, it searches in order:
-
/bin/<cmd>- ELF/CUPD binary in ramfs -
/bin/<cmd>.cc- CupidC source in ramfs (JIT compiled) -
/home/bin/<cmd>- ELF binary on disk -
/home/bin/<cmd>.cc- CupidC source on disk (JIT compiled)
CupidC .cc files are JIT-compiled and run instantly. No build step.
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
See the User Programs page for a complete guide.
-
Write a
.ccsource file using theededitor:> ed /home/hello.cc a void main() { println("Hello from CupidC!"); } . w q -
Run it with JIT compilation:
> cupidc /home/hello.cc Hello from CupidC! -
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!
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 |
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().
- 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=5These still resolve as shell commands because they are not valid CupidC REPL input:
help
ls /home
Use:
reset
This clears persistent REPL variables, functions, structs, typedefs, and ans.
Getting Started
Shell & Scripting
Compilers
- CupidC Compiler
- CupidC Language Reference
- CupidC 2D Graphics Library
- CupidASM Assembler
- Toolchain Bootstrap
- Floating Point
Programs & Docs
GUI
Kernel
Filesystems
Hardware