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_PUSH:
|
||||||
case OP_SKIPZ:
|
case OP_SKIPZ:
|
||||||
case OP_SKIPNZ:
|
case OP_SKIPNZ:
|
||||||
case OP_CALL:
|
|
||||||
case OP_JUMPR:
|
case OP_JUMPR:
|
||||||
|
case OP_NOT:
|
||||||
GET_REG(instr->args[0], ra);
|
GET_REG(instr->args[0], ra);
|
||||||
instr->compiledArgs.push_back(ra);
|
instr->compiledArgs.push_back(ra);
|
||||||
break;
|
break;
|
||||||
|
|
@ -226,7 +226,6 @@ bool Assembler::CompileMnemonicArguments(std::shared_ptr<Instr> instr)
|
||||||
case OP_AND:
|
case OP_AND:
|
||||||
case OP_OR:
|
case OP_OR:
|
||||||
case OP_XOR:
|
case OP_XOR:
|
||||||
case OP_NOT:
|
|
||||||
GET_REG(instr->args[0], ra);
|
GET_REG(instr->args[0], ra);
|
||||||
GET_REG(instr->args[1], rb);
|
GET_REG(instr->args[1], rb);
|
||||||
instr->compiledArgs.push_back(ra);
|
instr->compiledArgs.push_back(ra);
|
||||||
|
|
@ -245,11 +244,11 @@ bool Assembler::CompileMnemonicArguments(std::shared_ptr<Instr> instr)
|
||||||
leu32_put(instr->compiledArgs, op);
|
leu32_put(instr->compiledArgs, op);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case OP_CALL:
|
||||||
case OP_JUMP:
|
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->useLabel = true;
|
||||||
instr->compiledArgs.push_back(0);
|
instr->compiledArgs.reserve(4);
|
||||||
instr->compiledArgs.push_back(0);
|
|
||||||
break;
|
break;
|
||||||
case OP_STORE: // store @r4, r1, 2
|
case OP_STORE: // store @r4, r1, 2
|
||||||
CHIP32_CHECK(instr, instr->args[0].at(0) == '@', "Missing @ sign before register")
|
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))
|
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;
|
uint16_t argsIndex = 1;
|
||||||
if (instr->code.opcode == OP_JUMP) {
|
if ((instr->code.opcode == OP_JUMP) || (instr->code.opcode == OP_CALL)) {
|
||||||
argsIndex = 0;
|
argsIndex = 0;
|
||||||
}
|
}
|
||||||
std::string label = instr->args[argsIndex];
|
std::string label = instr->args[argsIndex];
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ public:
|
||||||
m_syscallHandler = std::bind(&Machine::HandleSyscall, this,
|
m_syscallHandler = std::bind(&Machine::HandleSyscall, this,
|
||||||
std::placeholders::_1,
|
std::placeholders::_1,
|
||||||
std::placeholders::_2);
|
std::placeholders::_2);
|
||||||
ram.resize(1024);
|
ram.resize(2048);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,23 @@ THE SOFTWARE.
|
||||||
|
|
||||||
#define _NEXT_BYTE ctx->rom.mem[++ctx->registers[PC]]
|
#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)
|
static inline uint16_t _NEXT_SHORT (chip32_ctx_t *ctx)
|
||||||
{
|
{
|
||||||
ctx->registers[PC] += 2;
|
ctx->registers[PC] += 2;
|
||||||
|
|
@ -45,8 +62,7 @@ static inline uint32_t _NEXT_INT (chip32_ctx_t *ctx)
|
||||||
{
|
{
|
||||||
ctx->registers[PC] += 4;
|
ctx->registers[PC] += 4;
|
||||||
|
|
||||||
return ctx->rom.mem[ctx->registers[PC] - 3] | ctx->rom.mem[ctx->registers[PC] - 2] << 8 |
|
return deserialize_le32(&ctx->rom.mem[ctx->registers[PC] - 3]);
|
||||||
ctx->rom.mem[ctx->registers[PC] - 1] << 16 | ctx->rom.mem[ctx->registers[PC]] << 24;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define _CHECK_SKIP if (skip) continue;
|
#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) {
|
static void push(chip32_ctx_t *ctx, uint32_t val) {
|
||||||
ctx->registers[SP] -= 4;
|
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) {
|
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;
|
ctx->registers[SP] += 4;
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
@ -190,7 +206,7 @@ chip32_result_t chip32_step(chip32_ctx_t *ctx)
|
||||||
}
|
}
|
||||||
case OP_CALL:
|
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;
|
const uint8_t reg = _NEXT_BYTE;
|
||||||
_CHECK_REGISTER_VALID(reg)
|
_CHECK_REGISTER_VALID(reg)
|
||||||
ctx->registers[PC] = ctx->registers[reg] - 1;
|
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;
|
const uint8_t reg2 = _NEXT_BYTE;
|
||||||
_CHECK_REGISTER_VALID(reg1)
|
_CHECK_REGISTER_VALID(reg1)
|
||||||
_CHECK_REGISTER_VALID(reg2)
|
_CHECK_REGISTER_VALID(reg2)
|
||||||
|
if (ctx->registers[reg2] != 0)
|
||||||
|
{
|
||||||
ctx->registers[reg1] = ctx->registers[reg1] / ctx->registers[reg2];
|
ctx->registers[reg1] = ctx->registers[reg1] / ctx->registers[reg2];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ctx->registers[reg1] = 0;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case OP_SHL:
|
case OP_SHL:
|
||||||
|
|
|
||||||
|
|
@ -1,87 +1,716 @@
|
||||||
/*
|
// ===================================================================
|
||||||
The MIT License
|
// test_vm.cpp - Tests exhaustifs de toutes les instructions Chip32
|
||||||
|
// ===================================================================
|
||||||
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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
#include "chip32_machine.h" // Inclure chip32_machine.h au lieu de assembler et vm
|
#include "chip32_machine.h"
|
||||||
|
|
||||||
/*
|
|
||||||
Purpose: test all opcodes
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Suppression des fonctions et classes de configuration de la VM
|
|
||||||
// qui sont maintenant encapsulées dans Chip32::Machine.
|
|
||||||
|
|
||||||
class VmTestContext
|
class VmTestContext
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
VmTestContext() {
|
VmTestContext() {}
|
||||||
// La RAM est allouée et initialisée à l'intérieur de QuickExecute
|
|
||||||
// de la classe Machine. On peut laisser le constructeur vide.
|
|
||||||
}
|
|
||||||
|
|
||||||
void Execute(const std::string &assemblyCode)
|
void Execute(const std::string &assemblyCode)
|
||||||
{
|
{
|
||||||
// Utiliser la méthode QuickExecute de Machine pour Parse, Build et Run
|
|
||||||
machine.QuickExecute(assemblyCode);
|
machine.QuickExecute(assemblyCode);
|
||||||
|
|
||||||
// Vérification de base: le parsing et le build doivent réussir
|
|
||||||
REQUIRE(machine.parseResult == true);
|
REQUIRE(machine.parseResult == true);
|
||||||
REQUIRE(machine.buildResult == true);
|
REQUIRE(machine.buildResult == true);
|
||||||
|
|
||||||
// Vérification que l'exécution a fini normalement (HALT)
|
|
||||||
REQUIRE(machine.runResult == VM_FINISHED);
|
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]") {
|
TEST_CASE_METHOD(VmTestContext, "ADD - Addition", "[vm][arithmetic][add]") {
|
||||||
static const std::string test1 = R"(
|
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 r0, 37
|
||||||
lcons r1, 0x695
|
lcons r1, 0x695
|
||||||
mul r0, r1
|
mul r0, r1
|
||||||
halt
|
halt
|
||||||
)";
|
)";
|
||||||
Execute(test1);
|
Execute(test);
|
||||||
|
REQUIRE(machine.ctx.registers[R0] == 37 * 0x695);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_METHOD(VmTestContext, "DIV", "[vm]") {
|
TEST_CASE_METHOD(VmTestContext, "DIV - Division", "[vm][arithmetic][div]") {
|
||||||
static const std::string test1 = R"(
|
static const std::string test = R"(
|
||||||
lcons r0, 37
|
lcons r0, 84
|
||||||
lcons r1, 8
|
lcons r1, 2
|
||||||
div r0, r1
|
div r0, r1
|
||||||
halt
|
halt
|
||||||
)";
|
)";
|
||||||
Execute(test1);
|
Execute(test);
|
||||||
|
REQUIRE(machine.ctx.registers[R0] == 42);
|
||||||
// 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));
|
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