mirror of
https://github.com/arabine/open-story-teller.git
synced 2025-12-06 17:09:06 +01:00
(wip) Instructions unit test (complete)
This commit is contained in:
parent
9db4bae9fd
commit
ee6958a729
4 changed files with 725 additions and 74 deletions
|
|
@ -210,8 +210,8 @@ bool Assembler::CompileMnemonicArguments(std::shared_ptr<Instr> 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> 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> 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];
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public:
|
|||
m_syscallHandler = std::bind(&Machine::HandleSyscall, this,
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2);
|
||||
ram.resize(1024);
|
||||
ram.resize(2048);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 <iostream>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue