ExpL Compiler: Stage 0 | XSM Execution Environment and Setup
1. Overview and Core Learning Objectives
The purpose of this tutorial is to bridge the gap between building an ExpL Compiler and the execution environment provided by the XSM (Experimental String Machine) Simulator running the ExpOS Operating System.
Key Takeaways:
-
Generate XEXE executable files containing XSM assembly language code.
-
Master the Application Binary Interface (ABI) to partition application memory.
-
Implement Console Input/Output (I/O) using low-level OS System Calls (
INTinstruction). -
Abstract system interaction via the ExpOS Library Interface (
CALL 0).
2. Theoretical Architecture & Execution Pipeline
When executing a program, the compiler translates source code into machine/assembly code, but the bare hardware cannot handle execution isolation or lifecycle orchestration alone. ExpOS sets up this environment.
The Execution Workflow:
-
File Transfer: The XSM simulator script copies your
.xsmtarget file from the local machine's disk to the simulated XSM hard disk. -
OS Bootstrapping: The ExpOS bootstrap loader starts in Kernel Mode, initializes a user process, sets up page tables for address translation (paging), and copies the file from the disk to the designated code region in virtual memory.
-
Execution Start: The OS pushes the starting address onto the application's stack and executes the
IRETinstruction. This simultaneously switches the machine from Kernel Mode to User Mode and sets the Instruction Pointer (IP) register to the specified Entry Point, beginning execution.
3. The ExpOS Memory Model & Virtual Address Space
The ABI establishes a consistent Virtual Address Space for user-mode applications, ranging from address 0 to 5119. The compiler partitions this memory space into four explicit regions:
| Memory Region | Address Range | Purpose / Description |
|---|---|---|
| Library | 0 to 1023 |
Preloaded utility functions (loaded automatically from library.lib by the simulator). |
| Code (Text) | 2048 to 4095 |
Contains the Header (2048–2055) and the loaded XSM Assembly Instructions (2056–4095). |
| Stack | 4096 to 5119 |
Runtime data stack for variables, activation records, and system call communication. |
| Heap | N/A (Dynamic) | Managed dynamically for run-time allocations (Alloc/Free). |
Note: There is no isolated static data region; global/static variables are allocated in the stack region.
The XEXE Executable Header format
The first 8 words of an executable file are reserved exclusively for the header. The loader reads this header starting at virtual address 2048.
-
Word 0 (Magic Number): Must be set to
0. -
Word 1 (Entry Point): Stores the memory address of the very first instruction to be executed (typically
2056if code starts immediately after the header). -
Words 2–7: Reserved/Unused for basic experiments (initialized to
0).
Because instructions take two words, the limit for a single program is $(4095 - 2056 + 1) / 2 = 1020$ instructions.
4. Experiment I: Basic Arithmetic & Simulator Debug Mode
Step A: Code Layout Configuration
To add two numbers and see the results via registers, your compiler writes the 8-word header followed directly by XSM instructions:
C
// Boilerplate C code generating the target XSM file
fprintf(target_file, "%d\n%d\n%d\n%d\n%d\n%d\n%d\n%d\n", 0, 2056, 0, 0, 0, 0, 0, 0);
fprintf(target_file, "BRKP\n"); // Software breakpoint for debugging
fprintf(target_file, "MOV R0, 3\n"); // Load immediate value 3 into R0
fprintf(target_file, "MOV R1, 2\n"); // Load immediate value 2 into R1
fprintf(target_file, "ADD R0, R1\n"); // R0 = R0 + R1
Step B: Running in Debug Mode
To examine register state without I/O functionality, create a dummy library.lib file containing only a RET instruction and fire up the engine:
Bash
./xsm -l library.lib -e path/to/target_file.xsm --debug
-
Use
sto step through instructions. -
Use
reg R0orreg R0 R1to inspect register statuses. -
Use
regto view all registers (IP,SP,BP,R0-R19, etc.).
Warning: Reaching the end of a program without an explicit exit instruction causes an invalid instruction exception because the simulator increments IP into empty memory, forcing an ungraceful termination by the kernel.
5. Experiment II: I/O & System Calls (Low-Level Interface)
Application programs run in Unprivileged (User) Mode and cannot execute raw hardware instructions for device access. Instead, they must invoke System Calls to interact with the console via the kernel using the Software Trap instruction INT.
The System Call Stack Convention
Arguments are pushed onto the stack before triggering the INT instruction. The ExpOS ABI defines the argument-passing convention as follows:
[ Top of Stack ]
| Storage for Return Value (Empty Space/Placeholder Register) | <-- SP
| Argument 3 |
| Argument 2 |
| Argument 1 |
| System Call Number |
[ Bottom of Stack ]
Implementing Write to Console via INT 7
To write the value of register R0 to the screen:
-
Initialize Stack Pointer (
MOV SP, 4095). -
Push parameters:
-
System Call Number for Write:
5 -
Argument 1 (File Descriptor / Destination):
-2(Constant for Console Out) -
Argument 2 (Data): The actual contents to print (e.g., contents of
R0) -
Argument 3: Dummy placeholder value
-
Return Value Space: Dummy placeholder value
-
-
Trigger
INT 7.
Code snippet
MOV SP, 4095 ; Set stack base (first push will sit at 4096)
MOV R2, 5 ; Syscall number 5
PUSH R2
MOV R2, -2 ; Console File Descriptor
PUSH R2
PUSH R0 ; Push the data stored in R0
PUSH R2 ; Dummy argument 3
PUSH R2 ; Space for Return Value
INT 7 ; Invoke Kernel Interrupt 7
POP R1 ; Clean up return value and 4 arguments
POP R1
POP R1
POP R1
POP R1
Context Preservation Rule
System calls alter general-purpose registers (R0, R1, etc.). If you need your register values intact after a system call, follow the Caller-Save Routine:
-
PUSHall active registers in current use onto the stack. -
Execute the system call convention.
-
POPsaved registers back in reverse order.
6. Experiment III: High-Level Abstraction via the ExpOS Library
Instead of tying compilers to exact hardware interrupt numbers, the ABI routes I/O through a shared library module mapped at addresses 0 to 1023. The application uses the CALL 0 instruction, passing function names as strings.
Library Call Convention
[ Top of Stack ]
| Space for Return Value | <-- SP
| Argument 3 |
| Argument 2 |
| Argument 1 |
| Function Name (String) |
[ Bottom of Stack ]
Implementation using CALL 0
Rewriting the console write snippet using the library layer:
Code snippet
MOV SP, 4095 ; Initialize stack
MOV R1, "Write" ; Function name argument
PUSH R1
MOV R1, -2 ; Argument 1: Console out
PUSH R1
PUSH R0 ; Argument 2: Data from R0
PUSH R1 ; Argument 3: Dummy space
PUSH R1 ; Space for Return Value
CALL 0 ; Control transfers to library landing page at address 0
POP R0 ; Clean stack frames
POP R1
POP R1
POP R1
POP R1
Under the Hood: Inside the Library (library.lib)
The library is a translator. It checks the function string, shifts parameters, maps them to the corresponding low-level system call array structure, and handles the INT context switch dynamically.
Code snippet
; Simplified internal layout snippet of a library tracking "Write"
MOV R1, SP
MOV R2, 5
SUB R1, R2 ; Locate the Function Name string on the stack
MOV R2, "Write"
EQ R1, R2
JZ R1, [Next_Check]; If it's not "Write", jump to check other functions
; ... Extract stack variables, remap, push args for low level interface ...
INT 7 ; Traps to Kernel
; ... Extract return value from INT 7 and store back in library's return frame ...
RET ; Return to Application code
7. Key Takeaways & Design Exercises
-
Graceful Exits: Always terminate programs properly using the
Exitsystem call (INT 10or calling"Exit"viaCALL 0). This tells ExpOS to safely release resources rather than triggering hardware execution faults. -
Variable Storage: To read data via
INT 6("Read"), pass the actual physical memory address (e.g., a stack offset like4096) as Argument 2 so the operating system knows exactly where to store the captured input stream.