open-story-teller/core/chip32/chip32_machine.h
Anthony Rabine ee6958a729
Some checks are pending
Build-StoryEditor / build_linux (push) Waiting to run
Build-StoryEditor / build_win32 (push) Waiting to run
(wip) Instructions unit test (complete)
2025-10-18 14:23:58 +02:00

610 lines
21 KiB
C++

/*
* Chip32 Machine - VM wrapper with binary format support
* Updated to use chip32_binary_format for proper DV/DZ handling
*/
#pragma once
#include <iostream>
#include <thread>
#include <stdarg.h>
#include <string.h>
#include <sstream>
#include <functional>
#include <cstring>
#include <iomanip>
#include <fstream>
#include <vector>
#include <iterator>
#include "chip32_assembler.h"
#include "chip32_binary_format.h"
#include "chip32_macros.h"
namespace Chip32
{
class Machine
{
public:
bool parseResult{false};
bool buildResult{false};
chip32_result_t runResult{VM_OK};
std::string printOutput;
chip32_ctx_t ctx; // Public pour accès aux registres dans les tests
std::vector<uint8_t> ram; // RAM storage
std::vector<uint8_t> program;
Chip32::Assembler assembler;
chip32_binary_header_t header;
chip32_binary_error_t error;
Machine() {
// Bind syscall handler to this instance
m_syscallHandler = std::bind(&Machine::HandleSyscall, this,
std::placeholders::_1,
std::placeholders::_2);
ram.resize(2048);
}
bool BuildBinary(Assembler &assembler, std::vector<uint8_t> &program, chip32_binary_stats_t &stats)
{
program.clear();
// ========================================================================
// PHASE 1: Créer les sections temporaires
// ========================================================================
std::vector<uint8_t> constSection; // DC - ROM constants only
std::vector<uint8_t> codeSection; // Executable code
std::vector<uint8_t> initDataSection; // DV values + DZ zeros (RAM init)
chip32_binary_header_init(&header);
std::shared_ptr<Chip32::Instr> mainInstr;
if (assembler.GetMain(mainInstr))
{
header.entry_point = mainInstr->addr;
}
// else: no main found, we try to start at address zero...
for (auto instr : assembler)
{
// ====================================================================
// CODE INSTRUCTIONS
// ====================================================================
if (instr->isRomCode())
{
// Ajouter l'opcode
codeSection.push_back(instr->code.opcode);
// Ajouter les arguments compilés
std::copy(instr->compiledArgs.begin(),
instr->compiledArgs.end(),
std::back_inserter(codeSection));
}
// ====================================================================
// ROM DATA - Distinguer DC (vraies constantes) de DV init data
// ====================================================================
else if (instr->isRomData && !instr->isRamData)
{
std::copy(instr->compiledArgs.begin(),
instr->compiledArgs.end(),
std::back_inserter(constSection));
}
// ====================================================================
// RAM VARIABLES
// ====================================================================
else if (instr->isRamData)
{
// ====================================================================
// ZEROED DATA (DZ)
// ====================================================================
if (instr->isZeroData)
{
header.bss_size += instr->dataLen;
}
// ====================================================================
// INITIALIZED RAM VARIABLES (DV)
// ====================================================================
else
{
// Ces données appartiennent à cette variable DV
initDataSection.insert(initDataSection.end(),
instr->compiledArgs.begin(),
instr->compiledArgs.end());
}
}
}
// ========================================================================
// PHASE 5: Créer le header binaire
// ========================================================================
header.const_size = static_cast<uint32_t>(constSection.size());
header.code_size = static_cast<uint32_t>(codeSection.size());
header.data_size = static_cast<uint32_t>(initDataSection.size());
if (initDataSection.size() > 0)
{
header.flags |= CHIP32_FLAG_HAS_INIT_DATA;
}
// ========================================================================
// PHASE 6: Assembler le binaire final
// ========================================================================
uint32_t totalSize = chip32_binary_calculate_size(&header);
program.resize(totalSize);
uint32_t bytesWritten = chip32_binary_write(
&header,
constSection.empty() ? nullptr : constSection.data(),
codeSection.empty() ? nullptr : codeSection.data(),
initDataSection.empty() ? nullptr : initDataSection.data(),
program.data(),
static_cast<uint32_t>(program.size())
);
if (bytesWritten == 0 || bytesWritten != totalSize)
{
// Erreur lors de l'écriture
program.clear();
return false;
}
// ========================================================================
// PHASE 7: Remplir les statistiques
// ========================================================================
chip32_binary_build_stats(&header, &stats);
return true;
}
bool GetAssemblyLine(uint32_t pointer_counter, uint32_t &line)
{
bool success = false;
// On recherche quelle est la ligne qui possède une instruction à cette adresse
for (auto instr : assembler)
{
if ((instr->addr == pointer_counter) && instr->isRomCode())
{
line = instr->line;
success = true;
break;
}
}
return success;
}
bool LoadBinary(const std::string &filename)
{
bool success = false;
std::ifstream file(filename, std::ios::binary);
if (file)
{
// Méthode 1 : La plus simple avec les itérateurs
program = std::vector<uint8_t>(
std::istreambuf_iterator<char>(file),
std::istreambuf_iterator<char>()
);
success = true;
}
return success;
}
void SaveBinary(const std::string &filename)
{
std::ofstream o(filename , std::ios::out | std::ios::binary);
o.write(reinterpret_cast<const char*>(program.data()), program.size());
o.close();
}
bool Build(const std::string &assemblyCode)
{
chip32_binary_stats_t stats;
// Parse
parseResult = assembler.Parse(assemblyCode);
if (!parseResult) {
std::cout << "Parse error: " << assembler.GetLastError().ToString() << std::endl;
return false;
}
// Build binary with new format
buildResult = BuildBinary(assembler, program, stats);
chip32_binary_print_stats(&stats);
if (!buildResult) {
std::cout << "Build error: " << assembler.GetLastError().ToString() << std::endl;
return false;
}
if (!LoadCurrentProgram()) {
std::cout << "Binary load error: " << chip32_binary_error_string(error) << std::endl;
buildResult = false;
return false;
}
chip32_binary_build_stats(&header, &stats);
chip32_binary_print_stats(&stats);
return true;
}
void SetEvent(uint32_t ev) {
ctx.registers[R0] = ev;
}
bool LoadCurrentProgram()
{
// Load binary using executable format
error = chip32_binary_load(
&ctx,
program.data(),
static_cast<uint32_t>(program.size()),
ram.data(),
static_cast<uint32_t>(ram.size()),
&header
);
return error == CHIP32_BIN_OK;
}
// ========================================================================
// Méthode principale : Parse, Build, Execute
// ========================================================================
void QuickExecute(const std::string &assemblyCode)
{
Build(assemblyCode);
// Set syscall handler using wrapper
ctx.syscall = SyscallWrapper;
ctx.user_data = this;
std::cout << "Starting execution at PC=0x" << std::hex << ctx.registers[PC]
<< std::dec << std::endl;
// Run
runResult = chip32_run(&ctx);
std::cout << "Execution finished with result: " << runResult << std::endl;
}
// ========================================================================
// Helper functions
// ========================================================================
static std::string GetStringFromMemory(chip32_ctx_t *ctx, uint32_t addr)
{
if (!ctx) {
throw std::runtime_error("Invalid context in GetStringFromMemory");
}
bool isRam = (addr & 0x80000000) != 0;
addr &= 0xFFFF;
const uint8_t* source_mem = nullptr;
size_t mem_size = 0;
if (isRam) {
if (addr >= ctx->ram.size) {
throw std::out_of_range("RAM address out of bounds: " + std::to_string(addr));
}
source_mem = ctx->ram.mem;
mem_size = ctx->ram.size;
} else {
if (addr >= ctx->rom.size) {
throw std::out_of_range("ROM address out of bounds: " + std::to_string(addr));
}
source_mem = ctx->rom.mem;
mem_size = ctx->rom.size;
}
size_t max_len = mem_size - addr;
const char* str_start = reinterpret_cast<const char*>(&source_mem[addr]);
const char* null_pos = static_cast<const char*>(
std::memchr(str_start, '\0', max_len)
);
if (null_pos) {
return std::string(str_start, null_pos - str_start);
} else {
return std::string(str_start, max_len);
}
}
static std::string FormatStringWithPlaceholders(chip32_ctx_t *ctx,
const std::string& format,
const std::vector<uint32_t>& args)
{
std::ostringstream result;
size_t pos = 0;
while (pos < format.length()) {
// Chercher le prochain placeholder '{'
if (format[pos] == '{' && pos + 1 < format.length()) {
char nextChar = format[pos + 1];
// Vérifier si c'est un placeholder valide {0} à {3}
if (nextChar >= '0' && nextChar <= '3') {
int argIndex = nextChar - '0';
// Vérifier si on a assez d'arguments
if (argIndex >= static_cast<int>(args.size())) {
result << "{" << argIndex << ":?}";
pos += 3;
continue;
}
// Vérifier le type (i ou s)
if (pos + 2 < format.length() && format[pos + 2] == ':') {
if (pos + 3 < format.length()) {
char typeChar = format[pos + 3];
uint32_t argValue = args[argIndex];
if (typeChar == 's') {
// String: argValue est une adresse
try {
std::string str = GetStringFromMemory(ctx, argValue);
result << str;
pos += 5; // Avancer de "{0:s}"
continue;
} catch (const std::exception& e) {
result << "{str_error}";
pos += 5;
continue;
}
} else if (typeChar == 'i' || typeChar == 'd') {
// Integer
result << static_cast<int32_t>(argValue);
pos += 5;
continue;
}
}
} else if (pos + 2 < format.length() && format[pos + 2] == '}') {
// Format simple {0} - traiter comme int par défaut
uint32_t argValue = args[argIndex];
result << static_cast<int32_t>(argValue);
pos += 3;
continue;
}
}
}
// Caractère normal, copier tel quel
result << format[pos];
pos++;
}
return result.str();
}
// ========================================================================
// Syscall handler
// ========================================================================
uint8_t HandleSyscall(chip32_ctx_t *ctx, uint8_t code)
{
try {
if (code == 4) // Printf
{
std::string format = GetStringFromMemory(ctx, ctx->registers[R0]);
int arg_count = ctx->registers[R1];
std::vector<uint32_t> args;
for (int i = 0; i < arg_count && i < 4; ++i) {
args.push_back(ctx->registers[R2 + i]);
}
printOutput = FormatStringWithPlaceholders(ctx, format, args);
std::cout << "[SYSCALL PRINT] " << printOutput << std::endl;
}
else if (code == 5) // WAIT
{
std::this_thread::sleep_for(
std::chrono::milliseconds(ctx->registers[R0])
);
}
else
{
std::cerr << "Unknown syscall code: " << static_cast<int>(code) << std::endl;
return SYSCALL_RET_ERROR;
}
return SYSCALL_RET_OK;
} catch (const std::exception& e) {
std::cerr << "Syscall error: " << e.what() << std::endl;
return SYSCALL_RET_ERROR;
}
}
// ========================================================================
// Hexdump utilities
// ========================================================================
void HexDump(const uint8_t* data, uint32_t size, uint32_t base_addr = 0, const std::string& title = "")
{
if (!title.empty()) {
std::cout << "\n=== " << title << " ===" << std::endl;
}
const int bytes_per_line = 16;
for (uint32_t i = 0; i < size; i += bytes_per_line) {
// Adresse
std::cout << std::hex << std::setfill('0') << std::setw(8)
<< (base_addr + i) << ": ";
// Octets en hexadécimal
for (int j = 0; j < bytes_per_line; j++) {
if (i + j < size) {
std::cout << std::setw(2) << static_cast<int>(data[i + j]) << " ";
} else {
std::cout << " ";
}
// Séparateur au milieu
if (j == 7) {
std::cout << " ";
}
}
std::cout << " |";
// Représentation ASCII
for (int j = 0; j < bytes_per_line && i + j < size; j++) {
uint8_t byte = data[i + j];
if (byte >= 32 && byte <= 126) {
std::cout << static_cast<char>(byte);
} else {
std::cout << '.';
}
}
std::cout << "|" << std::dec << std::endl;
}
std::cout << std::endl;
}
void DumpRom()
{
if (ctx.rom.mem == nullptr || ctx.rom.size == 0) {
std::cout << "ROM is empty or not loaded" << std::endl;
return;
}
std::cout << "\n" << std::string(80, '=') << std::endl;
std::cout << "ROM DUMP (Total: " << ctx.rom.size << " bytes)" << std::endl;
std::cout << std::string(80, '=') << std::endl;
uint32_t offset = 0;
// Dump CONST section
if (header.const_size > 0) {
HexDump(ctx.rom.mem + offset, header.const_size, offset,
"CONST Section (" + std::to_string(header.const_size) + " bytes)");
offset += header.const_size;
}
// Dump CODE section
if (header.code_size > 0) {
HexDump(ctx.rom.mem + offset, header.code_size, offset,
"CODE Section (" + std::to_string(header.code_size) + " bytes)");
offset += header.code_size;
}
// Dump INIT DATA section
if (header.data_size > 0) {
HexDump(ctx.rom.mem + offset, header.data_size, offset,
"INIT DATA Section (" + std::to_string(header.data_size) + " bytes)");
}
}
void DumpRam()
{
if (ctx.ram.mem == nullptr || ctx.ram.size == 0) {
std::cout << "RAM is empty or not allocated" << std::endl;
return;
}
std::cout << "\n" << std::string(80, '=') << std::endl;
std::cout << "RAM DUMP (Total: " << ctx.ram.size << " bytes)" << std::endl;
std::cout << std::string(80, '=') << std::endl;
uint32_t offset = 0;
// Dump DATA section (initialized variables)
if (header.data_size > 0) {
HexDump(ctx.ram.mem + offset, header.data_size, 0x80000000 + offset,
"DATA Section (DV - " + std::to_string(header.data_size) + " bytes)");
offset += header.data_size;
}
// Dump BSS section (zero-initialized variables)
if (header.bss_size > 0) {
HexDump(ctx.ram.mem + offset, header.bss_size, 0x80000000 + offset,
"BSS Section (DZ - " + std::to_string(header.bss_size) + " bytes)");
offset += header.bss_size;
}
// Dump remaining RAM (heap/stack area)
uint32_t remaining = ctx.ram.size - offset;
if (remaining > 0) {
// Limiter l'affichage à 256 bytes pour le reste
uint32_t display_size = std::min(remaining, 256u);
HexDump(ctx.ram.mem + offset, display_size, 0x80000000 + offset,
"Heap/Stack area (showing first " + std::to_string(display_size) +
" of " + std::to_string(remaining) + " bytes)");
if (remaining > display_size) {
std::cout << "... (" << (remaining - display_size)
<< " more bytes not shown)" << std::endl;
}
}
}
void DumpMemory()
{
DumpRom();
DumpRam();
}
// Variante pour dumper une région spécifique de RAM
void DumpRamRegion(uint32_t start_addr, uint32_t size)
{
// Enlever le bit RAM si présent
uint32_t addr = start_addr & 0x7FFFFFFF;
if (ctx.ram.mem == nullptr || ctx.ram.size == 0) {
std::cout << "RAM is not allocated" << std::endl;
return;
}
if (addr >= ctx.ram.size) {
std::cout << "Start address 0x" << std::hex << addr
<< " is out of RAM bounds (size: 0x" << ctx.ram.size
<< ")" << std::dec << std::endl;
return;
}
uint32_t actual_size = std::min(size, ctx.ram.size - addr);
std::cout << "\n" << std::string(80, '=') << std::endl;
std::cout << "RAM Region Dump" << std::endl;
std::cout << "Address: 0x" << std::hex << (0x80000000 | addr)
<< " - 0x" << (0x80000000 | (addr + actual_size - 1))
<< std::dec << std::endl;
std::cout << std::string(80, '=') << std::endl;
HexDump(ctx.ram.mem + addr, actual_size, 0x80000000 | addr, "");
}
private:
std::function<uint8_t(chip32_ctx_t*, uint8_t)> m_syscallHandler;
// Wrapper statique qui récupère l'instance et appelle la méthode membre
static uint8_t SyscallWrapper(chip32_ctx_t *ctx, uint8_t code)
{
if (!ctx || !ctx->user_data) {
return SYSCALL_RET_ERROR;
}
Machine* instance = static_cast<Machine*>(ctx->user_data);
return instance->HandleSyscall(ctx, code);
}
};
} // namespace Chip32