# Calling Convention — Output Explanation This document explains the output of the calling convention algorithm implemented in `calling-convention.ipynb`, tested across 3 scenarios. --- ## TEST 1: A single 1-byte argument **Function signature:** `result = func(x)` where `x` is 8 bits (1 byte) **Output:** ``` ===== TEST 1: a 1 byte argument ===== === State after do_function_call === Registers used: ['RA'] Stack: [{'type': 'caller_saved', 'reg': 'R0', 'value': 0}, {'type': 'caller_saved', 'reg': 'R1', 'value': 0}, {'type': 'caller_saved', 'reg': 'R2', 'value': 0}, {'type': 'caller_saved', 'reg': 'R3', 'value': 0}] RS points to: 4 === State after undo_function_call === Clean stack: [] RS: 0 Collected result: {'result': {'type': 'result', 'value': 42}} ``` **Explanation:** Since there is only one argument and it fits in a single register, it is placed directly in `RA` (the first available argument register). Before the call, the 4 caller-saved registers (R0-R3) are pushed onto the stack to preserve their values. `RS` points to position 4, reflecting those 4 entries. After `undo_function_call`, the stack is empty, `RS` returns to 0, and the result value `42` is successfully collected from `RA`. --- ## TEST 2: 8 arguments, some overflow to the stack **Function signature:** `result = func(a, b, c, d, e, f, g, h)` **Output:** ``` ===== TEST 2: 8 arguments, some on the stack ===== === State after do_function_call === Registers used: ['RA', 'RB', 'RC', 'RD', 'R8', 'R9'] Stack: [{'type': 'caller_saved', 'reg': 'R0', 'value': 0}, {'type': 'caller_saved', 'reg': 'R1', 'value': 0}, {'type': 'caller_saved', 'reg': 'R2', 'value': 0}, {'type': 'caller_saved', 'reg': 'R3', 'value': 0}, {'type': 'param', 'name': 'g', 'size': 64, 'value': 0}, {'type': 'param', 'name': 'h', 'size': 64, 'value': 0}] RS points to: 6 === State after undo_function_call === Clean stack: [] RS: 0 Collected result: {'result': {'type': 'result', 'value': 99}} ``` **Explanation:** Spider provides 6 registers for passing arguments: RA, RB, RC, RD, R8, R9. The first 6 arguments (a through f) fill all available registers. Arguments `g` and `h`, which are 64-bit values, have no registers left and are pushed onto the stack instead. `RS` points to 6, reflecting the 4 caller-saved entries plus 2 stack parameters. After `undo_function_call`, all stack entries are cleaned and the result `99` is collected from `RA`. --- ## TEST 3: Boolean arguments **Function signature:** `ok = func(flag1, flag2, flag3, x)` **Output:** ``` ===== TEST 3: Boolean arguments ===== === State after do_function_call === Registers used: ['RA', 'RB'] Stack: [{'type': 'caller_saved', 'reg': 'R0', 'value': 0}, {'type': 'caller_saved', 'reg': 'R1', 'value': 0}, {'type': 'caller_saved', 'reg': 'R2', 'value': 0}, {'type': 'caller_saved', 'reg': 'R3', 'value': 0}] ... === State after undo_function_call === Clean stack: [] RS: 0 Collected result: {'ok': {'type': 'result', 'value': True}} ``` **Explanation:** Booleans (1-bit values) are not placed individually into registers. Instead they are accumulated into a queue and packed together before occupying a single register. The 3 boolean flags (flag1, flag2, flag3) are packed together and placed in `RB` as a `bool_pack_padded` entry. The regular argument `x` (8 bits) is placed in `RA` as normal. No arguments overflow to the stack. After `undo_function_call`, the result `True` is successfully collected from `RA`. --- ## General observations - The stack always starts and ends with the same state, proving the algorithm correctly preserves the machine's previous context. - Caller-saved registers (R0-R3) are always pushed before the call and restored after, regardless of the number of arguments. - `RS` accurately tracks the stack size at every step.