merged jump/call implementation, removed jumpr
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled

This commit is contained in:
anthony@rabine.fr 2025-10-18 23:14:30 +02:00
parent ee6958a729
commit 149a8b6276
9 changed files with 114 additions and 74 deletions

View file

@ -58,7 +58,7 @@ static const uint32_t NbRegs = sizeof(AllRegs) / sizeof(AllRegs[0]);
// Keep same order than the opcodes list!! // Keep same order than the opcodes list!!
static const std::string Mnemonics[] = { static const std::string Mnemonics[] = {
"nop", "halt", "syscall", "lcons", "mov", "push", "pop", "store", "load", "add", "addi", "sub", "subi", "mul", "div", "nop", "halt", "syscall", "lcons", "mov", "push", "pop", "store", "load", "add", "addi", "sub", "subi", "mul", "div",
"shiftl", "shiftr", "ishiftr", "and", "or", "xor", "not", "call", "ret", "jump", "jumpr", "skipz", "skipnz", "shiftl", "shiftr", "ishiftr", "and", "or", "xor", "not", "call", "ret", "jump", "skipz", "skipnz",
"eq", "gt", "lt" "eq", "gt", "lt"
}; };
@ -200,7 +200,7 @@ bool Assembler::CompileMnemonicArguments(std::shared_ptr<Instr> instr)
instr->compiledArgs.push_back(ra); instr->compiledArgs.push_back(ra);
// Detect address or immedate value // Detect address or immedate value
if ((instr->args[1].at(0) == '$') || (instr->args[1].at(0) == '.')) { if ((instr->args[1].at(0) == '$') || (instr->args[1].at(0) == '.')) {
instr->useLabel = true; instr->labelIndex = 1;
leu32_put(instr->compiledArgs, 0); // reserve 4 bytes leu32_put(instr->compiledArgs, 0); // reserve 4 bytes
} else { // immediate value } else { // immediate value
leu32_put(instr->compiledArgs, convertStringToLong(instr->args[1])); leu32_put(instr->compiledArgs, convertStringToLong(instr->args[1]));
@ -210,7 +210,6 @@ 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_JUMPR:
case OP_NOT: 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);
@ -246,10 +245,40 @@ bool Assembler::CompileMnemonicArguments(std::shared_ptr<Instr> instr)
} }
case OP_CALL: case OP_CALL:
case OP_JUMP: case OP_JUMP:
{
// 5 bytes
// first byte is option: register based or address
// Then 4 bytes (address or just one byte for register)
// We allow two forms of writing:
// - call @r0 ; call the address located in R0
// - jump .myFunction ; jump to the label
//
char prefix = instr->args[0].at(0);
if (prefix == '@')
{
instr->compiledArgs.push_back(0); // option zero
GET_REG(instr->args[0], ra);
instr->compiledArgs.push_back(ra);
// Three more bytes to keep same size
instr->compiledArgs.push_back(0);
instr->compiledArgs.push_back(0);
instr->compiledArgs.push_back(0);
}
else if (prefix == '.')
{
// Reserve 4 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->labelIndex = 1;
instr->compiledArgs.reserve(4); instr->compiledArgs.push_back(1); // option 1
leu32_put(instr->compiledArgs, 0); // reserve 4 bytes
}
else
{
CHIP32_CHECK(instr, false, "Jump/Call argument must be @R0 or .myLabel")
}
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")
instr->args[0].erase(0, 1); instr->args[0].erase(0, 1);
@ -270,17 +299,23 @@ bool Assembler::CompileMnemonicArguments(std::shared_ptr<Instr> instr)
// Register based // Register based
if (prefix == '@') if (prefix == '@')
{ {
// 3 bytes total for arguments
instr->args[1].erase(0, 1); // delete @ character instr->args[1].erase(0, 1); // delete @ character
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);
instr->compiledArgs.push_back(rb); instr->compiledArgs.push_back(rb);
instr->compiledArgs.push_back(static_cast<uint32_t>(strtol(instr->args[2].c_str(), NULL, 0))); instr->compiledArgs.push_back(static_cast<uint32_t>(strtol(instr->args[2].c_str(), NULL, 0)));
// Three more bytes to keep same size
instr->compiledArgs.push_back(0);
instr->compiledArgs.push_back(0);
instr->compiledArgs.push_back(0);
} }
// Variable based // Variable based
else if (prefix == '$') else if (prefix == '$')
{ {
instr->useLabel = true; // 6 bytes
instr->labelIndex = 1;
GET_REG(instr->args[0], ra); GET_REG(instr->args[0], ra);
instr->compiledArgs.push_back(ra | 0x80); // Flag this register with a bit to indicate an immediate address is following instr->compiledArgs.push_back(ra | 0x80); // Flag this register with a bit to indicate an immediate address is following
leu32_put(instr->compiledArgs, 0); // reserve 4 bytes leu32_put(instr->compiledArgs, 0); // reserve 4 bytes
@ -312,7 +347,7 @@ bool Assembler::CompileMnemonicArguments(std::shared_ptr<Instr> instr)
bool Assembler::CompileConstantArgument(std::shared_ptr<Instr> instr, const std::string &a) bool Assembler::CompileConstantArgument(std::shared_ptr<Instr> instr, const std::string &a)
{ {
instr->compiledArgs.clear(); instr->args.clear(); instr->useLabel = false; instr->compiledArgs.clear(); instr->args.clear(); instr->labelIndex = -1;
// Check string // Check string
if (a.size() > 2) if (a.size() > 2)
@ -332,7 +367,7 @@ bool Assembler::CompileConstantArgument(std::shared_ptr<Instr> instr, const std:
{ {
// Label must be 32-bit, throw an error if not the case // Label must be 32-bit, throw an error if not the case
CHIP32_CHECK(instr, instr->dataTypeSize == 32, "Labels must be stored in a 32-bit area (DC32)") CHIP32_CHECK(instr, instr->dataTypeSize == 32, "Labels must be stored in a 32-bit area (DC32)")
instr->useLabel = true; instr->labelIndex = 1;
instr->args.push_back(a); instr->args.push_back(a);
leu32_put(instr->compiledArgs, 0); // reserve 4 bytes leu32_put(instr->compiledArgs, 0); // reserve 4 bytes
return true; return true;
@ -555,13 +590,21 @@ Position of Data in RAM
// 3. Third pass: replace all label or RAM data by the real address in memory // 3. Third pass: replace all label or RAM data by the real address in memory
for (auto &instr : m_instructions) for (auto &instr : m_instructions)
{ {
if (instr->useLabel && (instr->args.size() > 0)) if ((instr->labelIndex >=0 ) && (instr->args.size() > 0))
{ {
// label is the first argument for JUMP and CALL, 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) || (instr->code.opcode == OP_CALL)) { // Look where is the label
argsIndex = 0; uint16_t argsIndex = 0;
for (auto &arg : instr->args)
{
if ((arg[0] == '.') || (arg[0] == '$'))
{
break;
} }
argsIndex++;
}
std::string label = instr->args[argsIndex]; std::string label = instr->args[argsIndex];
CHIP32_CHECK(instr, m_labels.count(label) > 0, "label not found: " + label); CHIP32_CHECK(instr, m_labels.count(label) > 0, "label not found: " + label);
uint32_t addr = m_labels[label]->addr; uint32_t addr = m_labels[label]->addr;
@ -570,11 +613,10 @@ Position of Data in RAM
{ {
addr |= CHIP32_RAM_OFFSET; addr |= CHIP32_RAM_OFFSET;
} }
instr->compiledArgs[instr->labelIndex] = (uint8_t)(addr & 0xFF);
instr->compiledArgs[argsIndex] = addr & 0xFF; instr->compiledArgs[instr->labelIndex + 1] = (uint8_t)((addr >> 8) & 0xFF);
instr->compiledArgs[argsIndex+1] = (addr >> 8U) & 0xFF; instr->compiledArgs[instr->labelIndex + 2] = (uint8_t)((addr >> 16) & 0xFF);
instr->compiledArgs[argsIndex+2] = (addr >> 16U) & 0xFF; instr->compiledArgs[instr->labelIndex + 3] = (uint8_t)((addr >> 24) & 0xFF);
instr->compiledArgs[argsIndex+3] = (addr >> 24U) & 0xFF;
} }
} }

View file

@ -50,9 +50,9 @@ struct Instr {
std::string mnemonic; std::string mnemonic;
uint16_t dataTypeSize{0}; uint16_t dataTypeSize{0};
uint16_t dataLen{0}; uint16_t dataLen{0};
int8_t labelIndex{-1}; // index of label in compiled argument (-1 means no label)
bool isLabel{false}; //!< If true, this is a label, otherwise it is an instruction bool isLabel{false}; //!< If true, this is a label, otherwise it is an instruction
bool useLabel{false}; //!< If true, the instruction uses a label
bool isRomData{false}; //!< True if constant data in ROM (DC) bool isRomData{false}; //!< True if constant data in ROM (DC)
bool isRamData{false}; //!< True if RAM variable (DV or DZ) bool isRamData{false}; //!< True if RAM variable (DV or DZ)
bool isZeroData{false}; //!< True if zero-initialized RAM (DZ only) bool isZeroData{false}; //!< True if zero-initialized RAM (DZ only)

View file

@ -204,30 +204,16 @@ chip32_result_t chip32_step(chip32_ctx_t *ctx)
ctx->registers[reg] = pop(ctx); ctx->registers[reg] = pop(ctx);
break; break;
} }
case OP_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;
// Save Tx registers on stack
_CHECK_CAN_PUSH(10)
for (int i = 0; i < 10; i++) {
push(ctx, ctx->registers[T0 + i]);
}
break;
}
case OP_RET: case OP_RET:
{ {
ctx->registers[PC] = ctx->registers[RA] - 1; ctx->registers[PC] = ctx->registers[RA] - 1;
_CHECK_CAN_POP(11)
_CHECK_CAN_POP(10)
// restore Tx registers from stack // restore Tx registers from stack
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
ctx->registers[T9 - i] = pop(ctx); ctx->registers[T9 - i] = pop(ctx);
} }
// restore previous RA
ctx->registers[RA] = pop(ctx);
break; break;
} }
case OP_STORE: case OP_STORE:
@ -273,6 +259,7 @@ chip32_result_t chip32_step(chip32_ctx_t *ctx)
_CHECK_REGISTER_VALID(reg2); _CHECK_REGISTER_VALID(reg2);
size = _NEXT_BYTE; size = _NEXT_BYTE;
addr = ctx->registers[reg2]; addr = ctx->registers[reg2];
ctx->registers[PC] += 3; // skip unused 3 bytes
} }
bool isRam = addr & CHIP32_RAM_OFFSET; bool isRam = addr & CHIP32_RAM_OFFSET;
@ -406,18 +393,48 @@ chip32_result_t chip32_step(chip32_ctx_t *ctx)
ctx->registers[reg1] = ~ctx->registers[reg1]; ctx->registers[reg1] = ~ctx->registers[reg1];
break; break;
} }
case OP_CALL:
case OP_JUMP: case OP_JUMP:
{ {
ctx->registers[PC] = _NEXT_SHORT(ctx) - 1; const uint8_t option = _NEXT_BYTE;
break; uint32_t target_addr;
}
case OP_JUMPR: if (option == 0)
{ {
// Register-based: @R0
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; target_addr = ctx->registers[reg];
// Skip 3 padding bytes
ctx->registers[PC] += 3;
}
else // option == 1
{
// Address-based: .myLabel
target_addr = _NEXT_INT(ctx);
}
// If CALL, we have more work to do, saving state to prepare a ret
if (instr == OP_CALL)
{
_CHECK_CAN_PUSH(1)
push(ctx, ctx->registers[RA]); // save previous RA
ctx->registers[RA] = ctx->registers[PC] + 1;
// Save Tx registers on stack
_CHECK_CAN_PUSH(10)
for (int i = 0; i < 10; i++) {
push(ctx, ctx->registers[T0 + i]);
}
}
// Perform jump
ctx->registers[PC] = target_addr - 1;
break; break;
} }
case OP_SKIPZ: case OP_SKIPZ:
case OP_SKIPNZ: case OP_SKIPNZ:
{ {

View file

@ -80,14 +80,13 @@ typedef enum
OP_CALL = 22, ///< set register RA to the next instruction and jump to subroutine, e.g.: call 0x10 0x00 OP_CALL = 22, ///< set register RA to the next instruction and jump to subroutine, e.g.: call 0x10 0x00
OP_RET = 23, ///< return to the address of last callee (RA), e.g.: ret OP_RET = 23, ///< return to the address of last callee (RA), e.g.: ret
OP_JUMP = 24, ///< jump to address (can use label or address), e.g.: jump .my_label OP_JUMP = 24, ///< jump to address (can use label or address), e.g.: jump .my_label
OP_JUMPR = 25, ///< jump to address contained in a register, e.g.: jumpr t9 OP_SKIPZ = 25, ///< skip next instruction if zero, e.g.: skipz r0
OP_SKIPZ = 26, ///< skip next instruction if zero, e.g.: skipz r0 OP_SKIPNZ = 26, ///< skip next instruction if not zero, e.g.: skipnz r2
OP_SKIPNZ = 27, ///< skip next instruction if not zero, e.g.: skipnz r2
// Comparison // Comparison
OP_CMP_EQ = 28, ///< compare two registers for equality, result in first e.g.: eq r4, r0, r1 ; similar to: r4 = (r0 == r1 ? 1 : 0 OP_CMP_EQ = 27, ///< compare two registers for equality, result in first e.g.: eq r4, r0, r1 ; similar to: r4 = (r0 == r1 ? 1 : 0
OP_CMP_GT = 29, ///< compare if first register is greater than the second, result in first e.g.: gt r4, r0, r1 ; similar to: r4 = (r0 > r1 ? 1 : 0 OP_CMP_GT = 28, ///< compare if first register is greater than the second, result in first e.g.: gt r4, r0, r1 ; similar to: r4 = (r0 > r1 ? 1 : 0
OP_CMP_LT = 30, ///< compare if first register is less than the second, result in first e.g.: lt r4, r0, r1 ; similar to: r4 = (r0 < r1 ? 1 : 0 OP_CMP_LT = 29, ///< compare if first register is less than the second, result in first e.g.: lt r4, r0, r1 ; similar to: r4 = (r0 < r1 ? 1 : 0
INSTRUCTION_COUNT INSTRUCTION_COUNT
} chip32_instruction_t; } chip32_instruction_t;
@ -161,11 +160,11 @@ typedef struct {
#define OPCODES_LIST { { OP_NOP, 0, 0 }, { OP_HALT, 0, 0 }, { OP_SYSCALL, 1, 1 }, { OP_LCONS, 2, 5 }, \ #define OPCODES_LIST { { OP_NOP, 0, 0 }, { OP_HALT, 0, 0 }, { OP_SYSCALL, 1, 1 }, { OP_LCONS, 2, 5 }, \
{ OP_MOV, 2, 2 }, { OP_PUSH, 1, 1 }, {OP_POP, 1, 1 }, \ { OP_MOV, 2, 2 }, { OP_PUSH, 1, 1 }, {OP_POP, 1, 1 }, \
{ OP_STORE, 3, 3 }, { OP_LOAD, 3, 3 }, \ { OP_STORE, 3, 3 }, { OP_LOAD, 3, 6 }, \
{ OP_ADD, 2, 2 }, { OP_ADDI, 2, 2 }, { OP_SUB, 2, 2 }, { OP_SUBI, 2, 2 }, { OP_MUL, 2, 2 }, \ { OP_ADD, 2, 2 }, { OP_ADDI, 2, 2 }, { OP_SUB, 2, 2 }, { OP_SUBI, 2, 2 }, { OP_MUL, 2, 2 }, \
{ OP_DIV, 2, 2 }, { OP_SHL, 2, 2 }, { OP_SHR, 2, 2 }, { OP_ISHR, 2, 2 }, { OP_AND, 2, 2 }, \ { OP_DIV, 2, 2 }, { OP_SHL, 2, 2 }, { OP_SHR, 2, 2 }, { OP_ISHR, 2, 2 }, { OP_AND, 2, 2 }, \
{ OP_OR, 2, 2 }, { OP_XOR, 2, 2 }, { OP_NOT, 1, 1 }, { OP_CALL, 1, 1 }, { OP_RET, 0, 0 }, \ { OP_OR, 2, 2 }, { OP_XOR, 2, 2 }, { OP_NOT, 1, 1 }, { OP_CALL, 1, 5 }, { OP_RET, 0, 0 }, \
{ OP_JUMP, 1, 2 }, { OP_JUMPR, 1, 1 }, { OP_SKIPZ, 1, 1 }, { OP_SKIPNZ, 1, 1 }, \ { OP_JUMP, 1, 5 }, { OP_SKIPZ, 1, 1 }, { OP_SKIPNZ, 1, 1 }, \
{ OP_CMP_EQ, 3, 3 }, { OP_CMP_GT, 3, 3 }, { OP_CMP_LT, 3, 3 } } { OP_CMP_EQ, 3, 3 }, { OP_CMP_GT, 3, 3 }, { OP_CMP_LT, 3, 3 } }
/** /**

View file

@ -310,19 +310,6 @@ TEST_CASE_METHOD(VmTestContext, "JUMP - Unconditional jump", "[vm][control][jump
REQUIRE(machine.ctx.registers[R0] == 42); 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 // CONTROL FLOW - SKIP
// =================================================================== // ===================================================================

View file

@ -48,7 +48,7 @@ void httpServer() async {
}); });
// Démarrer le serveur // Démarrer le serveur
io.serve(router, 'localhost', 8080).then((server) { io.serve(router.call, 'localhost', 8080).then((server) {
logger.d('Serving at http://${server.address.host}:${server.port}'); logger.d('Serving at http://${server.address.host}:${server.port}');
}); });
} }

View file

@ -1,4 +1,4 @@
library libstory; library;
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';

View file

@ -1,4 +1,4 @@
library libstory; library;
import 'dart:ffi'; import 'dart:ffi';
import 'dart:io'; import 'dart:io';

View file

@ -1,8 +1,6 @@
import 'package:flutter/material.dart' hide Router; import 'package:flutter/material.dart' hide Router;
import 'package:path/path.dart';
import 'dart:io'; import 'dart:io';
import 'dart:convert';
import 'dart:async'; import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
@ -10,10 +8,7 @@ import 'dart:typed_data';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:audioplayers/audioplayers.dart'; import 'package:audioplayers/audioplayers.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:external_path/external_path.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:logger/logger.dart'; import 'package:logger/logger.dart';
import 'package:file_picker/file_picker.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'libstory/storyvm.dart'; import 'libstory/storyvm.dart';