diff --git a/core/chip32/chip32_assembler.cpp b/core/chip32/chip32_assembler.cpp index 2f02ac6..dc6f1d8 100644 --- a/core/chip32/chip32_assembler.cpp +++ b/core/chip32/chip32_assembler.cpp @@ -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->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) 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) } case OP_CALL: case OP_JUMP: - // Reserve 4 bytes for address, it will be filled at the end - instr->useLabel = true; - instr->compiledArgs.reserve(4); + { + // 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->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) // 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(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) bool Assembler::CompileConstantArgument(std::shared_ptr 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, 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); } } diff --git a/core/chip32/chip32_assembler.h b/core/chip32/chip32_assembler.h index ac56a3b..fde3527 100644 --- a/core/chip32/chip32_assembler.h +++ b/core/chip32/chip32_assembler.h @@ -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) diff --git a/core/chip32/chip32_vm.c b/core/chip32/chip32_vm.c index 515294f..e61fae2 100644 --- a/core/chip32/chip32_vm.c +++ b/core/chip32/chip32_vm.c @@ -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 reg = _NEXT_BYTE; - _CHECK_REGISTER_VALID(reg) - ctx->registers[PC] = ctx->registers[reg] - 1; + 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) + 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: { diff --git a/core/chip32/chip32_vm.h b/core/chip32/chip32_vm.h index f52eb78..6b99df7 100644 --- a/core/chip32/chip32_vm.h +++ b/core/chip32/chip32_vm.h @@ -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 } } /** diff --git a/core/tests/test_vm.cpp b/core/tests/test_vm.cpp index 752a44e..45adab9 100644 --- a/core/tests/test_vm.cpp +++ b/core/tests/test_vm.cpp @@ -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 // =================================================================== diff --git a/story-player/lib/httpserver.dart b/story-player/lib/httpserver.dart index 81727af..02ed3bd 100644 --- a/story-player/lib/httpserver.dart +++ b/story-player/lib/httpserver.dart @@ -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}'); }); } diff --git a/story-player/lib/libstory/indexfile.dart b/story-player/lib/libstory/indexfile.dart index ad15f07..1a735eb 100644 --- a/story-player/lib/libstory/indexfile.dart +++ b/story-player/lib/libstory/indexfile.dart @@ -1,4 +1,4 @@ -library libstory; +library; import 'dart:io'; import 'dart:typed_data'; diff --git a/story-player/lib/libstory/storyvm.dart b/story-player/lib/libstory/storyvm.dart index dbebf6e..c5f9343 100644 --- a/story-player/lib/libstory/storyvm.dart +++ b/story-player/lib/libstory/storyvm.dart @@ -1,4 +1,4 @@ -library libstory; +library; import 'dart:ffi'; import 'dart:io'; diff --git a/story-player/lib/main.dart b/story-player/lib/main.dart index 8ee5907..2f8bc5a 100644 --- a/story-player/lib/main.dart +++ b/story-player/lib/main.dart @@ -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';