diff --git a/calling-convention/calling-convention.ipynb b/calling-convention/calling-convention.ipynb index c55fb45..24adf9d 100644 --- a/calling-convention/calling-convention.ipynb +++ b/calling-convention/calling-convention.ipynb @@ -11,7 +11,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "b9c572cd", "metadata": {}, "outputs": [], @@ -51,7 +51,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "a1ddd324", "metadata": {}, "outputs": [], @@ -76,7 +76,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "02a2ad1a", "metadata": {}, "outputs": [], @@ -96,7 +96,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "d2e4ce44", "metadata": {}, "outputs": [], @@ -118,33 +118,328 @@ ] }, { - "cell_type": "code", - "execution_count": null, - "id": "b58fbf72", + "cell_type": "markdown", + "id": "bcc3aa37", "metadata": {}, - "outputs": [], - "source": [] + "source": [ + "## Implementing do_function_call\n", + "This function simulates the function call process following Spider's calling convention.\n", + "It places arguments into registers (RA, RB, RC, RD, R8, R9), and anything that doesn't fit is placed on the stack.\n", + "Booleans are bundled together before being placed in a register." + ] }, { "cell_type": "code", "execution_count": null, - "id": "2860c30b", + "id": "f01040f2", "metadata": {}, "outputs": [], - "source": [] + "source": [ + "def do_function_call(input_params: list, output_return: list):\n", + " \n", + " # Rgisters available for arguments in order\n", + " arg_registers = ['RA', 'RB', 'RC', 'RD', 'R8', 'R9']\n", + " reg_index = 0 # index of current register\n", + " \n", + " # Save caller saved registers to the stack\n", + "\n", + " for reg in ['R0', 'R1', 'R2', 'R3']:\n", + " spider_vm['stack'].append({\n", + " 'type': 'caller_saved',\n", + " 'reg': reg,\n", + " 'value': spider_vm['regs'][reg]\n", + " })\n", + " \n", + " # If the return value is greater than 16 bytes (128 bits),\n", + " # reserve space on the stack and append a pointer to the front.\n", + " \n", + " total_return_size = sum(p['size'] for p in output_return)\n", + " if total_return_size > 128:\n", + " spider_vm['stack'].append({\n", + " 'type': 'return_space',\n", + " 'size': total_return_size,\n", + " 'value': None\n", + " })\n", + " # The pointer to the return space goes at the beginning of the arguments\n", + " input_params = [{'name': '__return_ptr', 'size': 64}] + input_params\n", + " \n", + " # Arrange arguments in registers and stack\n", + " \n", + " bool_queue = [] # Boolean accumulator (1 bit each)\n", + " stack_params = [] # Arguments that didn't fit in records\n", + " \n", + " for param in input_params:\n", + " is_bool = param['size'] == 1\n", + " fits_in_register = param['size'] <= 128 # Up to 16 bytes = 128 bits\n", + " \n", + " if is_bool:\n", + " # Booleans accumulate together\n", + " bool_queue.append(param)\n", + " \n", + " # If the boolean queue forms 8 bytes (64 bits), it is sent\n", + " if sum(p['size'] for p in bool_queue) >= 64:\n", + " if reg_index < len(arg_registers):\n", + " spider_vm['regs'][arg_registers[reg_index]] = {\n", + " 'type': 'bool_pack',\n", + " 'params': bool_queue.copy()\n", + " }\n", + " reg_index += 1\n", + " else:\n", + " stack_params.append({\n", + " 'type': 'bool_pack',\n", + " 'params': bool_queue.copy()\n", + " })\n", + " bool_queue = []\n", + " \n", + " elif fits_in_register and reg_index < len(arg_registers):\n", + " # It fits in the register and register is available.\n", + " spider_vm['regs'][arg_registers[reg_index]] = {\n", + " 'type': 'param',\n", + " 'name': param['name'],\n", + " 'size': param['size'],\n", + " 'value': 0 \n", + " }\n", + " reg_index += 1\n", + " \n", + " else:\n", + " # It doesn't fit in the register, it goes to the stack later\n", + " stack_params.append(param)\n", + " \n", + " # If there are any pending booleans and a register is available\n", + " if bool_queue:\n", + " if reg_index < len(arg_registers):\n", + " spider_vm['regs'][arg_registers[reg_index]] = {\n", + " 'type': 'bool_pack_padded',\n", + " 'params': bool_queue.copy()\n", + " }\n", + " reg_index += 1\n", + " else:\n", + " stack_params.append({\n", + " 'type': 'bool_pack_padded',\n", + " 'params': bool_queue.copy()\n", + " })\n", + " bool_queue = []\n", + " \n", + " # Push onto the stack what didn't fit in the registers\n", + " # First the small ones (up to 8 bytes = 64 bits), then the large ones\n", + "\n", + " small_params = [p for p in stack_params if p.get('size', 999) <= 64]\n", + " large_params = [p for p in stack_params if p.get('size', 0) > 64]\n", + " \n", + " for param in small_params:\n", + " spider_vm['stack'].append({\n", + " 'type': 'param',\n", + " 'name': param.get('name', 'bool_pack'),\n", + " 'size': param.get('size'),\n", + " 'value': 0\n", + " })\n", + " \n", + " for param in large_params:\n", + " spider_vm['stack'].append({\n", + " 'type': 'large_param',\n", + " 'name': param['name'],\n", + " 'size': param['size'],\n", + " 'value': 0\n", + " })\n", + " \n", + " # Update RS to reflect the top of the stack\n", + " spider_vm['regs']['RS'] = len(spider_vm['stack'])\n", + " \n", + " print(\"=== State after do_function_call ===\")\n", + " print(f\"Registers used: {arg_registers[:reg_index]}\")\n", + " print(f\"Stack: {spider_vm['stack']}\")\n", + " print(f\"RS points to: {spider_vm['regs']['RS']}\")" + ] + }, + { + "cell_type": "markdown", + "id": "962c406c", + "metadata": {}, + "source": [ + "## Implementing undo_function_call\n", + "This function restores the VM's state to its state before the call.\n", + "It collects the RA result, cleans the stack, and restores the caller-saved records." + ] }, { "cell_type": "code", "execution_count": null, - "id": "2aa8a8fc", + "id": "2e8a479b", "metadata": {}, "outputs": [], - "source": [] + "source": [ + "def undo_function_call(input_params: list, output_return: list):\n", + " \n", + " # Collect results from return registers\n", + " # Up to 16 bytes (128 bits) reside in RA, RB, RC, RD\n", + " # ─────────────────────────────────────────\n", + " total_return_size = sum(p['size'] for p in output_return)\n", + " return_registers = ['RA', 'RB', 'RC', 'RD']\n", + " result = {}\n", + " \n", + " if total_return_size <= 128:\n", + " regs_needed = (total_return_size + 31) // 32 # How many registers do we need\n", + " for i, param in enumerate(output_return):\n", + " if i < len(return_registers):\n", + " result[param['name']] = spider_vm['regs'][return_registers[i]]\n", + " else:\n", + " # The result was in the reserved space of the stack.\n", + " for entry in spider_vm['stack']:\n", + " if entry.get('type') == 'return_space':\n", + " result['large_return'] = entry['value']\n", + " break\n", + " \n", + " # Clean the argument stack and return space\n", + " \n", + " spider_vm['stack'] = [\n", + " entry for entry in spider_vm['stack']\n", + " if entry.get('type') == 'caller_saved'\n", + " ]\n", + " \n", + " # # Restore caller saved registers in reverse order\n", + " \n", + " for entry in reversed(spider_vm['stack'].copy()):\n", + " if entry['type'] == 'caller_saved':\n", + " spider_vm['regs'][entry['reg']] = entry['value']\n", + " \n", + " # Clean caller_saved entries from the stack\n", + " spider_vm['stack'] = []\n", + " \n", + " # Update RS\n", + " spider_vm['regs']['RS'] = len(spider_vm['stack'])\n", + " \n", + " print(\"=== State after undo_function_call ===\")\n", + " print(f\"Clean stack: {spider_vm['stack']}\")\n", + " print(f\"RS: {spider_vm['regs']['RS']}\")\n", + " print(f\"Collected result: {result}\")\n", + " return result" + ] + }, + { + "cell_type": "markdown", + "id": "2b76da93", + "metadata": {}, + "source": [ + "## Test Cases\n", + "Three cases are tested: a single argument, multiple arguments that overflow the registers, and boolean arguments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "85e645fc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "===== PRUEBA 1: un argumento de 1 byte =====\n", + "=== Estado después de do_function_call ===\n", + "Registros usados: ['RA']\n", + "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}]\n", + "RS apunta a: 4\n", + "=== Estado después de undo_function_call ===\n", + "Stack limpio: []\n", + "RS: 0\n", + "Resultado recogido: {'resultado': {'type': 'result', 'value': 42}}\n", + "\n", + "===== PRUEBA 2: 8 argumentos, algunos al stack =====\n", + "=== Estado después de do_function_call ===\n", + "Registros usados: ['RA', 'RB', 'RC', 'RD', 'R8', 'R9']\n", + "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}]\n", + "RS apunta a: 6\n", + "=== Estado después de undo_function_call ===\n", + "Stack limpio: []\n", + "RS: 0\n", + "Resultado recogido: {'resultado': {'type': 'result', 'value': 99}}\n", + "\n", + "===== PRUEBA 3: argumentos booleanos =====\n", + "=== Estado después de do_function_call ===\n", + "Registros usados: ['RA', 'RB']\n", + "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}]\n", + "RS apunta a: 4\n", + "=== Estado después de undo_function_call ===\n", + "Stack limpio: []\n", + "RS: 0\n", + "Resultado recogido: {'ok': {'type': 'result', 'value': True}}\n" + ] + }, + { + "data": { + "text/plain": [ + "{'ok': {'type': 'result', 'value': True}}" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# TEST 1: A single function with one argument\n", + "\n", + "print(\"\\n===== TEST 1: a 1 byte argument =====\")\n", + "input_params = [{'name': 'x', 'size': 8}]\n", + "output_return = [{'name': 'result', 'size': 8}]\n", + "\n", + "do_function_call(input_params, output_return)\n", + "spider_vm['regs']['RA'] = {'type': 'result', 'value': 42} # Simulate return\n", + "undo_function_call(input_params, output_return)\n", + "\n", + "# TEST 2: Multiple arguments, some go on the stack\n", + "\n", + "print(\"\\n===== TEST 2: 8 arguments, some on the stack =====\")\n", + "input_params = [\n", + " {'name': 'a', 'size': 8},\n", + " {'name': 'b', 'size': 16},\n", + " {'name': 'c', 'size': 32},\n", + " {'name': 'd', 'size': 8},\n", + " {'name': 'e', 'size': 8},\n", + " {'name': 'f', 'size': 8},\n", + " {'name': 'g', 'size': 64}, # This will go on the stack\n", + " {'name': 'h', 'size': 64}, # this one too\n", + "]\n", + "output_return = [{'name': 'result', 'size': 32}]\n", + "\n", + "do_function_call(input_params, output_return)\n", + "spider_vm['regs']['RA'] = {'type': 'result', 'value': 99}\n", + "undo_function_call(input_params, output_return)\n", + "\n", + "# TEST 3: Booleans\n", + "\n", + "print(\"\\n===== TEST 3: Boolean arguments =====\")\n", + "input_params = [\n", + " {'name': 'flag1', 'size': 1},\n", + " {'name': 'flag2', 'size': 1},\n", + " {'name': 'flag3', 'size': 1},\n", + " {'name': 'x', 'size': 8},\n", + "]\n", + "output_return = [{'name': 'ok', 'size': 1}]\n", + "do_function_call(input_params, output_return)\n", + "spider_vm['regs']['RA'] = {'type': 'result', 'value': True}\n", + "undo_function_call(input_params, output_return)" + ] } ], "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.5" } }, "nbformat": 4,