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!!
static const std::string Mnemonics[] = {
"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"
};
@ -200,7 +200,7 @@ bool Assembler::CompileMnemonicArguments(std::shared_ptr<Instr> instr)
instr->compiledArgs.push_back(ra);
// Detect address or immedate value
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
} else { // immediate value
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_SKIPZ:
case OP_SKIPNZ:
case OP_JUMPR:
case OP_NOT:
GET_REG(instr->args[0], ra);
instr->compiledArgs.push_back(ra);
@ -246,10 +245,40 @@ bool Assembler::CompileMnemonicArguments(std::shared_ptr<Instr> instr)
}
case OP_CALL:
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
instr->useLabel = true;
instr->compiledArgs.reserve(4);
instr->labelIndex = 1;
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;
}
case OP_STORE: // store @r4, r1, 2
CHIP32_CHECK(instr, instr->args[0].at(0) == '@', "Missing @ sign before register")
instr->args[0].erase(0, 1);
@ -270,17 +299,23 @@ bool Assembler::CompileMnemonicArguments(std::shared_ptr<Instr> instr)
// Register based
if (prefix == '@')
{
// 3 bytes total for arguments
instr->args[1].erase(0, 1); // delete @ character
GET_REG(instr->args[0], ra);
GET_REG(instr->args[1], rb);
instr->compiledArgs.push_back(ra);
instr->compiledArgs.push_back(rb);
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
else if (prefix == '$')
{
instr->useLabel = true;
// 6 bytes
instr->labelIndex = 1;
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
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)
{
instr->compiledArgs.clear(); instr->args.clear(); instr->useLabel = false;
instr->compiledArgs.clear(); instr->args.clear(); instr->labelIndex = -1;
// Check string
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
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);
leu32_put(instr->compiledArgs, 0); // reserve 4 bytes
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
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
uint16_t argsIndex = 1;
if ((instr->code.opcode == OP_JUMP) || (instr->code.opcode == OP_CALL)) {
argsIndex = 0;
// Look where is the label
uint16_t argsIndex = 0;
for (auto &arg : instr->args)
{
if ((arg[0] == '.') || (arg[0] == '$'))
{
break;
}
argsIndex++;
}
std::string label = instr->args[argsIndex];
CHIP32_CHECK(instr, m_labels.count(label) > 0, "label not found: " + label);
uint32_t addr = m_labels[label]->addr;
@ -570,11 +613,10 @@ Position of Data in RAM
{
addr |= CHIP32_RAM_OFFSET;
}
instr->compiledArgs[argsIndex] = addr & 0xFF;
instr->compiledArgs[argsIndex+1] = (addr >> 8U) & 0xFF;
instr->compiledArgs[argsIndex+2] = (addr >> 16U) & 0xFF;
instr->compiledArgs[argsIndex+3] = (addr >> 24U) & 0xFF;
instr->compiledArgs[instr->labelIndex] = (uint8_t)(addr & 0xFF);
instr->compiledArgs[instr->labelIndex + 1] = (uint8_t)((addr >> 8) & 0xFF);
instr->compiledArgs[instr->labelIndex + 2] = (uint8_t)((addr >> 16) & 0xFF);
instr->compiledArgs[instr->labelIndex + 3] = (uint8_t)((addr >> 24) & 0xFF);
}
}

View file

@ -50,9 +50,9 @@ struct Instr {
std::string mnemonic;
uint16_t dataTypeSize{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 useLabel{false}; //!< If true, the instruction uses a label
bool isRomData{false}; //!< True if constant data in ROM (DC)
bool isRamData{false}; //!< True if RAM variable (DV or DZ)
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);
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:
{
ctx->registers[PC] = ctx->registers[RA] - 1;
_CHECK_CAN_POP(10)
_CHECK_CAN_POP(11)
// restore Tx registers from stack
for (int i = 0; i < 10; i++) {
ctx->registers[T9 - i] = pop(ctx);
}
// restore previous RA
ctx->registers[RA] = pop(ctx);
break;
}
case OP_STORE:
@ -273,6 +259,7 @@ chip32_result_t chip32_step(chip32_ctx_t *ctx)
_CHECK_REGISTER_VALID(reg2);
size = _NEXT_BYTE;
addr = ctx->registers[reg2];
ctx->registers[PC] += 3; // skip unused 3 bytes
}
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];
break;
}
case OP_CALL:
case OP_JUMP:
{
ctx->registers[PC] = _NEXT_SHORT(ctx) - 1;
break;
}
case OP_JUMPR:
const uint8_t option = _NEXT_BYTE;
uint32_t target_addr;
if (option == 0)
{
// Register-based: @R0
const uint8_t reg = _NEXT_BYTE;
_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;
}
case OP_SKIPZ:
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_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_JUMPR = 25, ///< jump to address contained in a register, e.g.: jumpr t9
OP_SKIPZ = 26, ///< skip next instruction if zero, e.g.: skipz r0
OP_SKIPNZ = 27, ///< skip next instruction if not zero, e.g.: skipnz r2
OP_SKIPZ = 25, ///< skip next instruction if zero, e.g.: skipz r0
OP_SKIPNZ = 26, ///< skip next instruction if not zero, e.g.: skipnz r2
// 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_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_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_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 = 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 = 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
} 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 }, \
{ 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_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_JUMP, 1, 2 }, { OP_JUMPR, 1, 1 }, { OP_SKIPZ, 1, 1 }, { OP_SKIPNZ, 1, 1 }, \
{ OP_OR, 2, 2 }, { OP_XOR, 2, 2 }, { OP_NOT, 1, 1 }, { OP_CALL, 1, 5 }, { OP_RET, 0, 0 }, \
{ 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 } }
/**

View file

@ -310,19 +310,6 @@ TEST_CASE_METHOD(VmTestContext, "JUMP - Unconditional jump", "[vm][control][jump
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
// ===================================================================

View file

@ -48,7 +48,7 @@ void httpServer() async {
});
// 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}');
});
}

View file

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

View file

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

View file

@ -1,8 +1,6 @@
import 'package:flutter/material.dart' hide Router;
import 'package:path/path.dart';
import 'dart:io';
import 'dart:convert';
import 'dart:async';
import 'dart:typed_data';
@ -10,10 +8,7 @@ import 'dart:typed_data';
import 'package:path_provider/path_provider.dart';
import 'package:audioplayers/audioplayers.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:file_picker/file_picker.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'libstory/storyvm.dart';