Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
5f93738
test: add t3d as a test case
HailToDodongo May 18, 2026
38b5be5
wip: c++ port
HailToDodongo May 19, 2026
efc5695
wip: c++ port
HailToDodongo May 19, 2026
46da506
wip: c++ port
HailToDodongo May 19, 2026
e1091f2
wip: c++ port
HailToDodongo May 19, 2026
e76db8f
wip: c++ port
HailToDodongo May 19, 2026
6f56dd0
wip: c++ port
HailToDodongo May 19, 2026
edf711b
wip: c++ port
HailToDodongo May 19, 2026
ebfa8ea
wip: c++ port
HailToDodongo May 19, 2026
d8772b6
wip: c++ port
HailToDodongo May 19, 2026
34defff
wip: c++ port
HailToDodongo May 20, 2026
491baf2
wip: c++ port
HailToDodongo May 20, 2026
3a94675
wip: c++ port
HailToDodongo May 20, 2026
252ed50
wip: c++ port
HailToDodongo May 20, 2026
ab5e608
wip: c++ port
HailToDodongo May 20, 2026
ad938a1
wip: c++ port
HailToDodongo May 20, 2026
1351702
wip: c++ port
HailToDodongo May 20, 2026
ecd2630
wip: c++ port
HailToDodongo May 20, 2026
ad121b7
wip: c++ port
HailToDodongo May 20, 2026
83c66a3
wip: c++ port
HailToDodongo May 20, 2026
0ed0909
wip: c++ port
HailToDodongo May 20, 2026
0901470
wip: c++ port
HailToDodongo May 20, 2026
a474314
wip: c++ port
HailToDodongo May 20, 2026
a96ca0b
chg: more tests
HailToDodongo May 20, 2026
b51b751
wip: c++ port
HailToDodongo May 20, 2026
f546d39
wip: c++ port
HailToDodongo May 20, 2026
e16478e
chg: reorder logic, add metrics
HailToDodongo May 21, 2026
acbdc97
chg: remove string use
HailToDodongo May 21, 2026
e0b9660
opt: reorder speed
HailToDodongo May 21, 2026
ec97a41
opt: reorder speed
HailToDodongo May 21, 2026
3cfa1ea
opt: reorder speed
HailToDodongo May 21, 2026
16b1a22
opt: reorder speed
HailToDodongo May 22, 2026
7a10abd
opt: reorder speed
HailToDodongo May 22, 2026
5239992
opt: reorder speed
HailToDodongo May 22, 2026
47a2dda
opt: reorder speed
HailToDodongo May 22, 2026
d7d77f0
opt: reorder speed
HailToDodongo May 22, 2026
82463e1
opt: reorder speed
HailToDodongo May 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cpp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
build
cmake-*
132 changes: 132 additions & 0 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
cmake_minimum_required(VERSION 3.20)
project(rspl VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# Dependencies via FetchContent
include(FetchContent)

FetchContent_Declare(
nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3
)
FetchContent_MakeAvailable(nlohmann_json)

FetchContent_Declare(
Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.7.1
)
FetchContent_MakeAvailable(Catch2)

# --- Library sources ------------------------------------------------------

set(RSPL_SOURCES
src/asm.cpp
src/asm.h
src/asm_normalize.cpp
src/asm_normalize.h
src/asm_writer.cpp
src/asm_writer.h
src/ast.cpp
src/ast.h
src/ast2asm.cpp
src/ast2asm.h
src/astCalcNormalizer.cpp
src/astCalcNormalizer.h
src/builtins.cpp
src/builtins.h
src/operations/branch.cpp
src/operations/branch.h
src/operations/scalar.cpp
src/operations/scalar.h
src/operations/user_function.cpp
src/operations/user_function.h
src/operations/vector.cpp
src/operations/vector.h
src/optimizer/asm_optimizer.cpp
src/optimizer/asm_optimizer.h
src/optimizer/asm_scan_deps.cpp
src/optimizer/asm_scan_deps.h
src/optimizer/eval_cost.cpp
src/optimizer/eval_cost.h
src/pipeline.cpp
src/pipeline.h
src/preproc.cpp
src/preproc.h
src/registers.cpp
src/registers.h
src/state.cpp
src/state.h
src/swizzle.cpp
src/swizzle.h
src/types.cpp
src/types.h
)

add_library(rspl_core STATIC ${RSPL_SOURCES})
target_include_directories(rspl_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_link_libraries(rspl_core PUBLIC nlohmann_json::nlohmann_json)

# --- CLI executable -------------------------------------------------------

add_executable(rspl src/main.cpp)
target_link_libraries(rspl PRIVATE rspl_core)

# --- Tests ----------------------------------------------------------------

set(TEST_SOURCES
tests/test_annotations.cpp
tests/test_ast.cpp
tests/test_branchConst.cpp
tests/test_builtins.cpp
tests/test_builtinsDebug.cpp
tests/test_builtinsAll.cpp
tests/test_branchVar.cpp
tests/test_branchZero.cpp
tests/test_compare.cpp
tests/test_const.cpp
tests/test_control.cpp
tests/test_debugInfo.cpp
tests/test_defineAsm.cpp
tests/test_dma.cpp
tests/test_immediateScalar.cpp
tests/test_labels.cpp
tests/test_load.cpp
tests/test_loop.cpp
tests/test_macros.cpp
tests/test_preproc.cpp
tests/test_scalarOps.cpp
tests/test_scope.cpp
tests/test_state.cpp
tests/test_stateDataBss.cpp
tests/test_store.cpp
tests/test_swizzle.cpp
tests/test_syntaxExpansion.cpp
tests/test_optAssert.cpp
tests/test_optBranchJump.cpp
tests/test_optDeadCode.cpp
tests/test_optDedupeImm.cpp
tests/test_optDelaySlot.cpp
tests/test_optJumpDedupe.cpp
tests/test_optLabels.cpp
tests/test_optDepScanCtrl.cpp
tests/test_optDepScanMem.cpp
tests/test_optDepScanRegs.cpp
tests/test_optMergeSequence.cpp
tests/test_optRegScan.cpp
tests/test_evalCost.cpp
tests/test_evalCostExample.cpp
tests/test_examples.cpp
tests/test_vectorOps.cpp
tests/test_syntaxNumbers.cpp
tests/test_syntaxVar.cpp
)

add_executable(rspl_tests ${TEST_SOURCES})
target_link_libraries(rspl_tests PRIVATE rspl_core Catch2::Catch2WithMain)
include(CTest)
add_test(NAME rspl_tests COMMAND rspl_tests)
50 changes: 50 additions & 0 deletions cpp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# RSPL C++ Backend

Native C++ port of the RSPL transpiler. Parsing is delegated to the existing
JS parser (`scripts/parse.js`); everything downstream runs in C++.

## Build

Requirements: **CMake 3.20+**, **g++ 13+** (or clang++ with C++20), **Node.js 20+**.

```sh
cd cpp

# Debug build (no optimizations, asserts enabled, debug symbols)
cmake -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build

# Production build (-O3, no asserts, stripped)
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
```

Dependencies (nlohmann/json, Catch2) are fetched automatically by CMake.

## Run

From the repo root:

```sh
cpp/build/rspl input.rspl # full pipeline → stdout
cpp/build/rspl input.rspl -o output.S # write to file
cpp/build/rspl input.rspl --no-optimize # skip optimizer
cpp/build/rspl input.rspl --no-rspq # raw asm, no RSPQ wrapper
cpp/build/rspl input.rspl --ast-dump # dump parsed AST (debug)
cpp/build/rspl input.rspl -D FOO=42 # preprocessor define
cpp/build/rspl input.rspl --reorder # enable instruction reorder annealing
cpp/build/rspl input.rspl --opt-time=60 # optimizer time budget in seconds
```

The binary invokes `node scripts/parse.js` internally. Set `RSPL_PARSE_JS` env var
to override the script path:

```sh
RSPL_PARSE_JS=/path/to/custom/parse.js cpp/build/rspl input.rspl
```

## Tests

```sh
cpp/build/rspl_tests
```
188 changes: 188 additions & 0 deletions cpp/src/asm.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
#include "asm.h"
#include "state.h"

#include <algorithm>
#include <unordered_map>
#include <unordered_set>

namespace rspl {

// --- Opcode registry ---------------------------------------------------
// Function-local statics to avoid static-init-order fiasco across TUs.

struct OpcodeRegistry {
std::vector<std::string> names;
std::unordered_map<std::string, Opcode> map;
};
static OpcodeRegistry &opcodeReg() {
static OpcodeRegistry reg;
return reg;
}

Opcode getOpcode(const std::string &name) {
if (name.empty()) return 0;
auto &reg = opcodeReg();
auto it = reg.map.find(name);
if (it != reg.map.end()) return it->second;
Opcode idx = static_cast<Opcode>(reg.names.size() + 1);
reg.names.push_back(name);
reg.map[name] = idx;
return idx;
}

const std::string &getOpcodeName(Opcode op) {
static const std::string empty;
auto &names = opcodeReg().names;
if (op == 0 || op > names.size()) return empty;
return names[op - 1];
}

// --- Precomputed opcode → (flags, latency) map --------------------------
// Replaces the 8 separate unordered_set lookups with a single map lookup.

struct OpInfoEntry { uint32_t flags; int latency; };

static const std::unordered_map<Opcode, OpInfoEntry> OP_INFO_MAP = []() {
std::unordered_map<Opcode, OpInfoEntry> m;

auto add = [&](const char *op, uint32_t flags, int latency) {
m[getOpcode(op)] = {flags, latency};
};

// Branches
for (auto *op : {"beq","bne","bgezal","bltzal","bgez","bltz","blez","bgtz",
"j","jr","jal"})
add(op, OP_FLAG_IS_BRANCH | OP_FLAG_IS_IMMOVABLE, 0);

// Stores (also get MEM_STALL_STORE)
for (auto *op : {"sw","sh","sb","sbv","ssv","slv","sdv","sqv","spv","suv",
"shv","sfv","stv","swv","srv"})
add(op, OP_FLAG_IS_STORE | OP_FLAG_IS_MEM_STALL_STORE, 0);

// Vector loads
for (auto *op : {"lbv","lsv","llv","ldv","lqv","lpv","luv","lhv","lfv",
"ltv","lrv"})
add(op, OP_FLAG_IS_LOAD | OP_FLAG_IS_MEM_STALL_LOAD, 4);

// Scalar loads
for (auto *op : {"lw","lh","lhu","lb","lbu"})
add(op, OP_FLAG_IS_LOAD | OP_FLAG_IS_MEM_STALL_LOAD, 3);

// Stall ops (both load and store stalls)
for (auto *op : {"mfc0","mtc0","mfc2","mtc2","cfc2","ctc2"}) {
uint32_t f = OP_FLAG_IS_MEM_STALL_LOAD | OP_FLAG_IS_MEM_STALL_STORE;
int lat = 3;
if (op[2] == 'c') { // cfc2/ctc2
f |= OP_FLAG_CTC2_CFC2;
}
if (op[1] == 't' && op[2] == 'c' && op[3] == '2') lat = 4; // mtc2
add(op, f, lat);
}

// Special
add("nop", OP_FLAG_IS_NOP | OP_FLAG_IS_IMMOVABLE, 0);
add("catch", OP_FLAG_IS_MEM_STALL_LOAD | OP_FLAG_IS_MEM_STALL_STORE, 0);

return m;
}();

int getStallLatency(Opcode op) {
auto it = OP_INFO_MAP.find(op);
if (it != OP_INFO_MAP.end()) return it->second.latency;
const auto &name = getOpcodeName(op);
if (!name.empty() && name[0] == 'v') return 4;
return 0;
}

uint32_t getOpFlags(Opcode op) {
uint32_t flags = OP_FLAG_IS_LIKELY;
auto it = OP_INFO_MAP.find(op);
if (it != OP_INFO_MAP.end()) flags |= it->second.flags;
else {
const auto &name = getOpcodeName(op);
if (!name.empty() && name[0] == 'v') flags |= OP_FLAG_IS_VECTOR;
}
if (op == getOpcode("nop")) flags |= OP_FLAG_IS_NOP;

if ((flags & OP_FLAG_IS_BRANCH) && (flags & OP_FLAG_IS_LIKELY))
flags |= OP_FLAG_LIKELY_BRANCH;
return flags;
}

static AsmDebug currentDebug() {
return AsmDebug{.lineRSPL = static_cast<int>(state.line)};
}

static void applyOpInfo(AsmInst &inst, Opcode op,
AsmType type) {
inst.type = type;
inst.opFlags = getOpFlags(op);
inst.stallLatency = getStallLatency(op);
// Copy current annotations from state (but don't clear —
// clearing is done per-statement in scopedBlockToAsm to match JS)
for (const auto &ann : state.getAnnotations()) {
inst.cold->annotations.push_back({ann.name, ann.value});
}
}

// --- Factory functions ------------------------------------------------

AsmInst asmOp(const std::string &op,
const std::vector<std::string> &args) {
AsmInst inst;
inst.op = getOpcode(op);
inst.args = args;
inst.debug = currentDebug();
applyOpInfo(inst, inst.op, AsmType::OP);
return inst;
}

AsmInst asmNOP() {
AsmInst inst;
inst.op = getOpcode("nop");
inst.debug = currentDebug();
applyOpInfo(inst, inst.op, AsmType::OP);
return inst;
}

AsmInst asmLabel(const std::string &label) {
AsmInst inst;
inst.cold->label = label;
inst.op = 0;
inst.debug = currentDebug();
applyOpInfo(inst, 0, AsmType::LABEL);
return inst;
}

AsmInst asmBranch(const std::string &op,
const std::vector<std::string> &args,
const std::string &labelEnd) {
AsmInst inst = asmOp(op, args);
inst.cold->labelEnd = labelEnd;
return inst;
}

AsmInst asmInline(const std::string &op,
const std::vector<std::string> &args) {
AsmInst inst;
inst.op = getOpcode(op);
inst.args = args;
inst.debug = currentDebug();
applyOpInfo(inst, inst.op, AsmType::INLINE);
return inst;
}

AsmInst asmFunction(const std::string &target,
const std::vector<std::string> &argRegs,
bool relative) {
if (relative) {
AsmInst inst = asmOp("bgezal", {"$zero", target});
inst.cold->funcArgs = argRegs;
return inst;
}
AsmInst inst = asmOp("jal", {target});
inst.cold->funcArgs = argRegs;
return inst;
}

} // namespace rspl
Loading
Loading