From ee6958a7295f4af8eca8cb3c95b54939569edd5a Mon Sep 17 00:00:00 2001 From: Anthony Rabine Date: Sat, 18 Oct 2025 14:23:58 +0200 Subject: [PATCH] (wip) Instructions unit test (complete) --- core/chip32/chip32_assembler.cpp | 13 +- core/chip32/chip32_machine.h | 2 +- core/chip32/chip32_vm.c | 35 +- core/tests/test_vm.cpp | 749 ++++++++++++++++++++++++++++--- 4 files changed, 725 insertions(+), 74 deletions(-) diff --git a/core/chip32/chip32_assembler.cpp b/core/chip32/chip32_assembler.cpp index 4c64633..2f02ac6 100644 --- a/core/chip32/chip32_assembler.cpp +++ b/core/chip32/chip32_assembler.cpp @@ -210,8 +210,8 @@ bool Assembler::CompileMnemonicArguments(std::shared_ptr instr) case OP_PUSH: case OP_SKIPZ: case OP_SKIPNZ: - case OP_CALL: case OP_JUMPR: + case OP_NOT: GET_REG(instr->args[0], ra); instr->compiledArgs.push_back(ra); break; @@ -226,7 +226,6 @@ bool Assembler::CompileMnemonicArguments(std::shared_ptr instr) case OP_AND: case OP_OR: case OP_XOR: - case OP_NOT: GET_REG(instr->args[0], ra); GET_REG(instr->args[1], rb); instr->compiledArgs.push_back(ra); @@ -245,11 +244,11 @@ bool Assembler::CompileMnemonicArguments(std::shared_ptr instr) leu32_put(instr->compiledArgs, op); break; } + case OP_CALL: case OP_JUMP: - // Reserve 2 bytes for address, it will be filled at the end + // Reserve 4 bytes for address, it will be filled at the end instr->useLabel = true; - instr->compiledArgs.push_back(0); - instr->compiledArgs.push_back(0); + instr->compiledArgs.reserve(4); break; case OP_STORE: // store @r4, r1, 2 CHIP32_CHECK(instr, instr->args[0].at(0) == '@', "Missing @ sign before register") @@ -558,9 +557,9 @@ Position of Data in RAM { if (instr->useLabel && (instr->args.size() > 0)) { - // label is the first argument for jump, second position for LCONS and LOAD + // label is the first argument for JUMP and CALL, second position for LCONS and LOAD uint16_t argsIndex = 1; - if (instr->code.opcode == OP_JUMP) { + if ((instr->code.opcode == OP_JUMP) || (instr->code.opcode == OP_CALL)) { argsIndex = 0; } std::string label = instr->args[argsIndex]; diff --git a/core/chip32/chip32_machine.h b/core/chip32/chip32_machine.h index f1a6629..bdb135e 100644 --- a/core/chip32/chip32_machine.h +++ b/core/chip32/chip32_machine.h @@ -43,7 +43,7 @@ public: m_syscallHandler = std::bind(&Machine::HandleSyscall, this, std::placeholders::_1, std::placeholders::_2); - ram.resize(1024); + ram.resize(2048); } diff --git a/core/chip32/chip32_vm.c b/core/chip32/chip32_vm.c index 61d0a02..515294f 100644 --- a/core/chip32/chip32_vm.c +++ b/core/chip32/chip32_vm.c @@ -35,6 +35,23 @@ THE SOFTWARE. #define _NEXT_BYTE ctx->rom.mem[++ctx->registers[PC]] + +static inline uint32_t deserialize_le32(const uint8_t *ptr) +{ + return (uint32_t)ptr[0] | + ((uint32_t)ptr[1] << 8) | + ((uint32_t)ptr[2] << 16) | + ((uint32_t)ptr[3] << 24); +} +static inline void serialize_le32(uint8_t *ptr, uint32_t value) +{ + ptr[0] = (uint8_t)(value & 0xFF); + ptr[1] = (uint8_t)((value >> 8) & 0xFF); + ptr[2] = (uint8_t)((value >> 16) & 0xFF); + ptr[3] = (uint8_t)((value >> 24) & 0xFF); +} + + static inline uint16_t _NEXT_SHORT (chip32_ctx_t *ctx) { ctx->registers[PC] += 2; @@ -45,8 +62,7 @@ static inline uint32_t _NEXT_INT (chip32_ctx_t *ctx) { ctx->registers[PC] += 4; - return ctx->rom.mem[ctx->registers[PC] - 3] | ctx->rom.mem[ctx->registers[PC] - 2] << 8 | - ctx->rom.mem[ctx->registers[PC] - 1] << 16 | ctx->rom.mem[ctx->registers[PC]] << 24; + return deserialize_le32(&ctx->rom.mem[ctx->registers[PC] - 3]); } #define _CHECK_SKIP if (skip) continue; @@ -88,11 +104,11 @@ static const uint16_t OpCodesSize = sizeof(OpCodes) / sizeof(OpCodes[0]); static void push(chip32_ctx_t *ctx, uint32_t val) { ctx->registers[SP] -= 4; - ctx->ram.mem[ctx->registers[SP]] = val; + serialize_le32(&ctx->ram.mem[ctx->registers[SP]], val); } static uint32_t pop(chip32_ctx_t *ctx) { - uint32_t val = ctx->ram.mem[ctx->registers[SP]]; + uint32_t val = deserialize_le32(&ctx->ram.mem[ctx->registers[SP]]); ctx->registers[SP] += 4; return val; } @@ -190,7 +206,7 @@ chip32_result_t chip32_step(chip32_ctx_t *ctx) } case OP_CALL: { - ctx->registers[RA] = ctx->registers[PC] + 2; // set return address to next instruction after CALL + ctx->registers[RA] = ctx->registers[PC] + 4; // set return address to next instruction after CALL (+4 is for address size) const uint8_t reg = _NEXT_BYTE; _CHECK_REGISTER_VALID(reg) ctx->registers[PC] = ctx->registers[reg] - 1; @@ -319,7 +335,14 @@ chip32_result_t chip32_step(chip32_ctx_t *ctx) const uint8_t reg2 = _NEXT_BYTE; _CHECK_REGISTER_VALID(reg1) _CHECK_REGISTER_VALID(reg2) - ctx->registers[reg1] = ctx->registers[reg1] / ctx->registers[reg2]; + if (ctx->registers[reg2] != 0) + { + ctx->registers[reg1] = ctx->registers[reg1] / ctx->registers[reg2]; + } + else + { + ctx->registers[reg1] = 0; + } break; } case OP_SHL: diff --git a/core/tests/test_vm.cpp b/core/tests/test_vm.cpp index f8d408d..752a44e 100644 --- a/core/tests/test_vm.cpp +++ b/core/tests/test_vm.cpp @@ -1,87 +1,716 @@ -/* -The MIT License - -Copyright (c) 2022 Anthony Rabine - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ +// =================================================================== +// test_vm.cpp - Tests exhaustifs de toutes les instructions Chip32 +// =================================================================== #include #include -#include "chip32_machine.h" // Inclure chip32_machine.h au lieu de assembler et vm - -/* -Purpose: test all opcodes -*/ - -// Suppression des fonctions et classes de configuration de la VM -// qui sont maintenant encapsulées dans Chip32::Machine. +#include "chip32_machine.h" class VmTestContext { public: - VmTestContext() { - // La RAM est allouée et initialisée à l'intérieur de QuickExecute - // de la classe Machine. On peut laisser le constructeur vide. - } + VmTestContext() {} void Execute(const std::string &assemblyCode) { - // Utiliser la méthode QuickExecute de Machine pour Parse, Build et Run machine.QuickExecute(assemblyCode); - - // Vérification de base: le parsing et le build doivent réussir - REQUIRE( machine.parseResult == true ); - REQUIRE( machine.buildResult == true ); - - // Vérification que l'exécution a fini normalement (HALT) - REQUIRE( machine.runResult == VM_FINISHED ); + REQUIRE(machine.parseResult == true); + REQUIRE(machine.buildResult == true); + REQUIRE(machine.runResult == VM_FINISHED); } - Chip32::Machine machine; // Instance de la Machine à utiliser pour les tests + Chip32::Machine machine; }; +// =================================================================== +// ARITHMETIC OPERATIONS +// =================================================================== -TEST_CASE_METHOD(VmTestContext, "MUL", "[vm]") { - static const std::string test1 = R"( +TEST_CASE_METHOD(VmTestContext, "ADD - Addition", "[vm][arithmetic][add]") { + static const std::string test = R"( + lcons r0, 10 + lcons r1, 32 + add r0, r1 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 42); +} + +TEST_CASE_METHOD(VmTestContext, "ADDI - Addition immediate (if supported)", "[vm][arithmetic][addi]") { + // Note: Vérifier si ADDI est implémenté dans votre VM + static const std::string test = R"( + lcons r0, 10 + addi r0, 32 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 42); +} + +TEST_CASE_METHOD(VmTestContext, "SUB - Subtraction", "[vm][arithmetic][sub]") { + static const std::string test = R"( + lcons r0, 50 + lcons r1, 8 + sub r0, r1 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 42); +} + +TEST_CASE_METHOD(VmTestContext, "SUBI - Subtraction immediate (if supported)", "[vm][arithmetic][subi]") { + static const std::string test = R"( + lcons r0, 50 + subi r0, 8 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 42); +} + +TEST_CASE_METHOD(VmTestContext, "MUL - Multiplication", "[vm][arithmetic][mul]") { + static const std::string test = R"( lcons r0, 37 lcons r1, 0x695 mul r0, r1 halt )"; - Execute(test1); - - // Accéder directement aux registres de la Machine pour vérifier le résultat - uint32_t result = machine.ctx.registers[R0]; - REQUIRE (result == 37 * 0x695); + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 37 * 0x695); } -TEST_CASE_METHOD(VmTestContext, "DIV", "[vm]") { - static const std::string test1 = R"( - lcons r0, 37 - lcons r1, 8 +TEST_CASE_METHOD(VmTestContext, "DIV - Division", "[vm][arithmetic][div]") { + static const std::string test = R"( + lcons r0, 84 + lcons r1, 2 div r0, r1 halt )"; - Execute(test1); - - // Accéder directement aux registres de la Machine pour vérifier le résultat - uint32_t result = machine.ctx.registers[R0]; - REQUIRE (result == (int)(37/8)); + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 42); +} + +TEST_CASE_METHOD(VmTestContext, "DIV - Division by zero protection", "[vm][arithmetic][div]") { + static const std::string test = R"( + lcons r0, 42 + lcons r1, 0 + div r0, r1 + halt + )"; + Execute(test); + // Vérifier que la VM ne crash pas (comportement peut varier) +} + +// =================================================================== +// BITWISE OPERATIONS +// =================================================================== + +TEST_CASE_METHOD(VmTestContext, "AND - Bitwise AND", "[vm][bitwise][and]") { + static const std::string test = R"( + lcons r0, 0xFF + lcons r1, 0x0F + and r0, r1 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 0x0F); +} + +TEST_CASE_METHOD(VmTestContext, "OR - Bitwise OR", "[vm][bitwise][or]") { + static const std::string test = R"( + lcons r0, 0xF0 + lcons r1, 0x0F + or r0, r1 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 0xFF); +} + +TEST_CASE_METHOD(VmTestContext, "XOR - Bitwise XOR", "[vm][bitwise][xor]") { + static const std::string test = R"( + lcons r0, 0xFF + lcons r1, 0xAA + xor r0, r1 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 0x55); +} + +TEST_CASE_METHOD(VmTestContext, "NOT - Bitwise NOT", "[vm][bitwise][not]") { + static const std::string test = R"( + lcons r0, 0x00000000 + not r0 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 0xFFFFFFFF); +} + +TEST_CASE_METHOD(VmTestContext, "SHIFTL - Shift left", "[vm][bitwise][shiftl]") { + static const std::string test = R"( + lcons r0, 0x01 + lcons r1, 4 + shiftl r0, r1 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 0x10); +} + +TEST_CASE_METHOD(VmTestContext, "SHIFTR - Shift right (logical)", "[vm][bitwise][shiftr]") { + static const std::string test = R"( + lcons r0, 0x80 + lcons r1, 4 + shiftr r0, r1 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 0x08); +} + +TEST_CASE_METHOD(VmTestContext, "ISHIFTR - Shift right (arithmetic)", "[vm][bitwise][ishiftr]") { + static const std::string test = R"( + lcons r0, 0xFFFFFF80 + lcons r1, 2 + ishiftr r0, r1 + halt + )"; + Execute(test); + // Shift arithmétique conserve le signe + REQUIRE((int32_t)machine.ctx.registers[R0] == -32); +} + +// =================================================================== +// DATA MOVEMENT +// =================================================================== + +TEST_CASE_METHOD(VmTestContext, "MOV - Move register", "[vm][data][mov]") { + static const std::string test = R"( + lcons r0, 42 + mov r1, r0 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R1] == 42); +} + +TEST_CASE_METHOD(VmTestContext, "LCONS - Load constant", "[vm][data][lcons]") { + static const std::string test = R"( + lcons r0, 0x12345678 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 0x12345678); +} + +// =================================================================== +// MEMORY OPERATIONS +// =================================================================== + +TEST_CASE_METHOD(VmTestContext, "STORE and LOAD - Memory operations", "[vm][memory]") { + static const std::string test = R"( +$myVar DV32, 0 + +.main: + lcons r0, 42 + lcons r1, $myVar + store @r1, r0, 4 + + lcons r2, 0 + load r2, @r1, 4 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R2] == 42); +} + +TEST_CASE_METHOD(VmTestContext, "LOAD - Direct address", "[vm][memory][load]") { + static const std::string test = R"( +$value DV32, 123 + +.main: + load r0, $value, 4 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 123); +} + +// =================================================================== +// STACK OPERATIONS +// =================================================================== + +TEST_CASE_METHOD(VmTestContext, "PUSH and POP", "[vm][stack]") { + static const std::string test = R"( + lcons r0, 42 + lcons r1, 100 + + push r0 + push r1 + + lcons r0, 0 + lcons r1, 0 + + pop r1 + pop r0 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 42); + REQUIRE(machine.ctx.registers[R1] == 100); +} + +TEST_CASE_METHOD(VmTestContext, "PUSH/POP - Multiple values", "[vm][stack]") { + static const std::string test = R"( + lcons r0, 1 + lcons r1, 2 + lcons r2, 3 + lcons r3, 4 + + push r0 + push r1 + push r2 + push r3 + + lcons r0, 0 + lcons r1, 0 + lcons r2, 0 + lcons r3, 0 + + pop r3 + pop r2 + pop r1 + pop r0 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 1); + REQUIRE(machine.ctx.registers[R1] == 2); + REQUIRE(machine.ctx.registers[R2] == 3); + REQUIRE(machine.ctx.registers[R3] == 4); +} + +// =================================================================== +// CONTROL FLOW - JUMP +// =================================================================== + +TEST_CASE_METHOD(VmTestContext, "JUMP - Unconditional jump", "[vm][control][jump]") { + static const std::string test = R"( + lcons r0, 0 + jump .target + lcons r0, 99 +.target: + lcons r0, 42 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 42); +} + +TEST_CASE_METHOD(VmTestContext, "JUMPR - Jump to register", "[vm][control][jumpr]") { + static const std::string test = R"( + lcons r0, .target + jumpr r0 + lcons r1, 99 +.target: + lcons r1, 42 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R1] == 42); +} + +// =================================================================== +// CONTROL FLOW - SKIP +// =================================================================== + +TEST_CASE_METHOD(VmTestContext, "SKIPZ - Skip if zero (condition true)", "[vm][control][skipz]") { + static const std::string test = R"( + lcons r0, 0 + skipz r0 + jump .non_zero + lcons r1, 42 + halt + .non_zero: + lcons r1, 99 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R1] == 42); +} + +TEST_CASE_METHOD(VmTestContext, "SKIPZ - Skip if zero (condition false)", "[vm][control][skipz]") { + static const std::string test = R"( + lcons r0, 1 + skipz r0 + jump .non_zero + lcons r1, 42 + halt + .non_zero: + lcons r1, 99 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R1] == 99); +} + +TEST_CASE_METHOD(VmTestContext, "SKIPNZ - Skip if not zero (condition true)", "[vm][control][skipnz]") { + static const std::string test = R"( + lcons r0, 1 + skipnz r0 + jump .it_is_zero + lcons r1, 42 + halt + .it_is_zero: + lcons r1, 99 + halt + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R1] == 42); +} + +TEST_CASE_METHOD(VmTestContext, "SKIPNZ - Skip if not zero (condition false)", "[vm][control][skipnz]") { + static const std::string test = R"( + lcons r0, 0 + skipnz r0 + jump .it_is_zero + lcons r1, 42 + halt + .it_is_zero: + lcons r1, 99 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R1] == 99); +} + +TEST_CASE_METHOD(VmTestContext, "SKIPZ - Multiple instructions", "[vm][control][skipz]") { + static const std::string test = R"( + lcons r0, 0 + lcons r1, 0 + skipz r0 + lcons r1, 10 + add r1, r0 + halt + )"; + Execute(test); + // Skip lcons, donc r1 reste 0, puis add 0 + 0 = 0 + REQUIRE(machine.ctx.registers[R1] == 0); +} + +// =================================================================== +// COMPARISONS +// =================================================================== + +TEST_CASE_METHOD(VmTestContext, "EQ - Equal (true)", "[vm][comparison][eq]") { + static const std::string test = R"( + lcons r1, 42 + lcons r2, 42 + eq r0, r1, r2 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 1); +} + +TEST_CASE_METHOD(VmTestContext, "EQ - Equal (false)", "[vm][comparison][eq]") { + static const std::string test = R"( + lcons r1, 42 + lcons r2, 10 + eq r0, r1, r2 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 0); +} + +TEST_CASE_METHOD(VmTestContext, "GT - Greater than (true)", "[vm][comparison][gt]") { + static const std::string test = R"( + lcons r1, 50 + lcons r2, 10 + gt r0, r1, r2 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 1); +} + +TEST_CASE_METHOD(VmTestContext, "GT - Greater than (false)", "[vm][comparison][gt]") { + static const std::string test = R"( + lcons r1, 10 + lcons r2, 50 + gt r0, r1, r2 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 0); +} + +TEST_CASE_METHOD(VmTestContext, "GT - Greater than (equal)", "[vm][comparison][gt]") { + static const std::string test = R"( + lcons r1, 42 + lcons r2, 42 + gt r0, r1, r2 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 0); +} + +TEST_CASE_METHOD(VmTestContext, "LT - Less than (true)", "[vm][comparison][lt]") { + static const std::string test = R"( + lcons r1, 10 + lcons r2, 50 + lt r0, r1, r2 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 1); +} + +TEST_CASE_METHOD(VmTestContext, "LT - Less than (false)", "[vm][comparison][lt]") { + static const std::string test = R"( + lcons r1, 50 + lcons r2, 10 + lt r0, r1, r2 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 0); +} + +TEST_CASE_METHOD(VmTestContext, "LT - Less than (equal)", "[vm][comparison][lt]") { + static const std::string test = R"( + lcons r1, 42 + lcons r2, 42 + lt r0, r1, r2 + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 0); +} + +// =================================================================== +// FUNCTION CALLS +// =================================================================== + +TEST_CASE_METHOD(VmTestContext, "CALL and RET - Function call", "[vm][function]") { + static const std::string test = R"( + lcons r0, 10 + call .myFunction + lcons r1, 100 + halt + +.myFunction: + lcons r0, 42 + ret + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 42); + REQUIRE(machine.ctx.registers[R1] == 100); +} + +TEST_CASE_METHOD(VmTestContext, "CALL - Nested function calls", "[vm][function]") { + static const std::string test = R"( + lcons r0, 0 + call .func1 + halt + +.func1: + lcons r0, 1 + call .func2 + ret + +.func2: + lcons r0, 42 + ret + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 42); +} + +// =================================================================== +// SPECIAL INSTRUCTIONS +// =================================================================== + +TEST_CASE_METHOD(VmTestContext, "NOP - No operation", "[vm][special][nop]") { + static const std::string test = R"( + lcons r0, 42 + nop + nop + nop + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 42); +} + +TEST_CASE_METHOD(VmTestContext, "HALT - Program termination", "[vm][special][halt]") { + static const std::string test = R"( + lcons r0, 42 + halt + lcons r0, 99 + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 42); +} + +// =================================================================== +// COMPLEX SCENARIOS +// =================================================================== + +TEST_CASE_METHOD(VmTestContext, "Complex - Factorial calculation", "[vm][complex]") { + static const std::string test = R"( +; Calculate 5! = 120 + lcons r0, 5 ; n + lcons r1, 1 ; result + +.loop: + mul r1, r0 + lcons r2, 1 + sub r0, r2 + skipz r0 + jump .loop + + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R1] == 120); +} + +TEST_CASE_METHOD(VmTestContext, "Complex - Fibonacci calculation", "[vm][complex]") { + static const std::string test = R"( +; Calculate 10th Fibonacci number = 55 + lcons r0, 0 ; fib(n-2) + lcons r1, 1 ; fib(n-1) + lcons r2, 10 ; counter + +.loop: + lcons r3, 1 + sub r2, r3 + skipz r2 + jump .continue + jump .done + +.continue: + mov r3, r1 + add r1, r0 + mov r0, r3 + jump .loop + +.done: + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R1] == 55); +} + +TEST_CASE_METHOD(VmTestContext, "Complex - Array sum", "[vm][complex][array]") { + static const std::string test = R"( +$array DV32, 10, 20, 30, 40, 50 + +.main: + lcons r0, 0 ; sum + lcons r1, $array ; pointer + lcons r2, 5 ; count + +.loop: + load r3, @r1, 4 + add r0, r3 + + lcons r4, 4 + add r1, r4 + + lcons r4, 1 + sub r2, r4 + skipz r2 + jump .loop + + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 150); +} + +TEST_CASE_METHOD(VmTestContext, "Complex - Conditional branches", "[vm][complex][branch]") { + static const std::string test = R"( + lcons r0, 10 + lcons r1, 5 + + gt r2, r0, r1 + skipz r2 + jump .greater + + lcons r3, 1 + jump .end + +.greater: + lcons r3, 2 + +.end: + halt + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R3] == 2); +} + +// =================================================================== +// EDGE CASES +// =================================================================== + +TEST_CASE_METHOD(VmTestContext, "Edge - Maximum positive integer", "[vm][edge]") { + static const std::string test = R"( + lcons r0, 0x7FFFFFFF + lcons r1, 1 + add r0, r1 + halt + )"; + Execute(test); + // Overflow vers valeur négative + REQUIRE((int32_t)machine.ctx.registers[R0] < 0); +} + +TEST_CASE_METHOD(VmTestContext, "Edge - All registers", "[vm][edge][registers]") { + static const std::string test = R"( + lcons r0, 0 + lcons r1, 1 + lcons r2, 2 + lcons r3, 3 + lcons r4, 4 + lcons r5, 5 + lcons r6, 6 + lcons r7, 7 + lcons r8, 8 + lcons r9, 9 + halt + )"; + Execute(test); + for (int i = 0; i < 10; i++) { + REQUIRE(machine.ctx.registers[i] == i); + } +} + +TEST_CASE_METHOD(VmTestContext, "Edge - Deep nested calls", "[vm][edge][stack]") { + static const std::string test = R"( + call .level1 + halt + +.level1: + call .level2 + ret + +.level2: + call .level3 + ret + +.level3: + lcons r0, 42 + ret + )"; + Execute(test); + REQUIRE(machine.ctx.registers[R0] == 42); }