diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index c50f5cf..77f1bb4 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -3055,4 +3055,13 @@ target_link_libraries(step463_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step464_test tests/step464_test.cpp) +target_include_directories(step464_test PRIVATE src) +target_link_libraries(step464_test PRIVATE + nlohmann_json::nlohmann_json + unofficial::tree-sitter::tree-sitter + tree_sitter_python tree_sitter_cpp tree_sitter_elisp + tree_sitter_javascript tree_sitter_typescript + tree_sitter_java tree_sitter_rust tree_sitter_go) + # Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies) diff --git a/editor/src/ast/AssemblyAnnotationMapper.h b/editor/src/ast/AssemblyAnnotationMapper.h new file mode 100644 index 0000000..72bcd77 --- /dev/null +++ b/editor/src/ast/AssemblyAnnotationMapper.h @@ -0,0 +1,128 @@ +#pragma once + +// Step 464: Assembly Annotation Mapping +// Maps assembly constructs to semantic annotations and lightweight cross-language +// lift/lower helpers. + +#include "AssemblyNodes.h" +#include "Function.h" + +#include +#include +#include +#include + +struct AssemblyAnnotationReport { + std::string functionName; + std::vector annotations; + int instructionCount = 0; + int branchCount = 0; + int registerPressure = 0; + std::string complexity; // low/medium/high +}; + +class AssemblyAnnotationMapper { +public: + static AssemblyAnnotationReport analyzeFunction(const Function& fn) { + AssemblyAnnotationReport report; + report.functionName = fn.name; + report.annotations.push_back("@Exec(native)"); + report.annotations.push_back("@Risk(high)"); + + std::set uniqueRegs; + std::string target = "x86"; + for (auto* node : fn.getChildren("body")) { + if (node->conceptType == "AssemblyInstruction") { + auto* i = static_cast(node); + ++report.instructionCount; + if (isBranch(i->opcode)) ++report.branchCount; + for (const auto& r : i->registers) uniqueRegs.insert(r.name); + if (looksArmRegisterSet(i->registers)) target = "arm"; + } else if (node->conceptType == "AssemblyDirective") { + auto* d = static_cast(node); + if (d->type == AssemblyDirectiveType::Align) { + report.annotations.push_back("@Align(" + d->value + ")"); + } + } + } + + report.registerPressure = static_cast(uniqueRegs.size()); + report.complexity = inferComplexity( + report.instructionCount, report.branchCount, report.registerPressure); + report.annotations.push_back("@Complexity(" + report.complexity + ")"); + report.annotations.push_back("@Target(" + target + ")"); + return report; + } + + static std::string lowerCToAssembly(const std::string& cFunctionSignature, + const std::string& arch = "x86") { + std::string name = extractFunctionName(cFunctionSignature); + std::ostringstream out; + if (arch == "arm") { + out << ".text\n.global " << name << "\n" << name << ":\n"; + out << " @ lowered from C function\n"; + out << " ret\n"; + } else { + out << ".text\n.global " << name << "\n" << name << ":\n"; + out << " ; lowered from C function\n"; + out << " ret\n"; + } + return out.str(); + } + + static std::string liftAssemblyToC(const Function& fn) { + std::ostringstream out; + out << "int " << (fn.name.empty() ? "lifted_function" : fn.name) << "() {\n"; + out << " // lifted from assembly (" << fn.getChildren("body").size() << " nodes)\n"; + out << " return 0;\n"; + out << "}\n"; + return out.str(); + } + +private: + static bool isBranch(const std::string& opRaw) { + std::string op = toLower(opRaw); + return op == "jmp" || op == "call" || op == "ret" || + op == "b" || op == "bl" || op == "beq" || op == "bne" || + op == "bgt" || op == "blt" || op == "bge" || op == "ble"; + } + + static bool looksArmRegisterSet(const std::vector& regs) { + for (const auto& r : regs) { + if (r.name == "sp" || r.name == "lr" || r.name == "pc") return true; + if (!r.name.empty() && (r.name[0] == 'r' || r.name[0] == 'x')) { + bool numericSuffix = r.name.size() > 1; + for (size_t i = 1; i < r.name.size(); ++i) { + if (!std::isdigit(static_cast(r.name[i]))) { + numericSuffix = false; + break; + } + } + if (numericSuffix) return true; // r0-r15, x0-x30 style + } + } + return false; + } + + static std::string inferComplexity(int instructions, int branches, int regPressure) { + int score = instructions + branches * 2 + regPressure; + if (score <= 8) return "low"; + if (score <= 18) return "medium"; + return "high"; + } + + static std::string toLower(std::string s) { + for (char& c : s) c = static_cast(std::tolower(static_cast(c))); + return s; + } + + static std::string extractFunctionName(const std::string& cSig) { + size_t paren = cSig.find('('); + if (paren == std::string::npos) return "function"; + std::string left = cSig.substr(0, paren); + size_t sp = left.find_last_of(" \t"); + if (sp == std::string::npos) return left; + std::string name = left.substr(sp + 1); + return name.empty() ? "function" : name; + } +}; diff --git a/editor/tests/step464_test.cpp b/editor/tests/step464_test.cpp new file mode 100644 index 0000000..885a7df --- /dev/null +++ b/editor/tests/step464_test.cpp @@ -0,0 +1,177 @@ +// Step 464: Assembly Annotation Mapping Tests (12 tests) + +#include "ast/AssemblyAnnotationMapper.h" + +#include + +static int passed = 0, failed = 0; +#define TEST(name) { std::cout << " " << #name << "... "; } +#define PASS() { std::cout << "PASS\n"; ++passed; } +#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; } +#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {} + +static Function makeX86FunctionSample() { + Function fn("fn1", "main"); + auto* lbl = new AssemblyLabel("main", true); + fn.addChild("body", lbl); + auto* mov = new AssemblyInstruction("mov", {"rax", "rbx"}); + mov->registers.emplace_back("rax", 64); + mov->registers.emplace_back("rbx", 64); + fn.addChild("body", mov); + auto* add = new AssemblyInstruction("add", {"rax", "1"}); + add->registers.emplace_back("rax", 64); + fn.addChild("body", add); + auto* ret = new AssemblyInstruction("ret", {}); + fn.addChild("body", ret); + return fn; +} + +void test_adds_exec_native_annotation() { + TEST(adds_exec_native_annotation); + auto fn = makeX86FunctionSample(); + auto r = AssemblyAnnotationMapper::analyzeFunction(fn); + bool found = false; + for (const auto& a : r.annotations) if (a == "@Exec(native)") found = true; + CHECK(found, "missing @Exec(native)"); + PASS(); +} + +void test_adds_target_x86_annotation() { + TEST(adds_target_x86_annotation); + auto fn = makeX86FunctionSample(); + auto r = AssemblyAnnotationMapper::analyzeFunction(fn); + bool found = false; + for (const auto& a : r.annotations) if (a == "@Target(x86)") found = true; + CHECK(found, "missing @Target(x86)"); + PASS(); +} + +void test_adds_risk_high_annotation() { + TEST(adds_risk_high_annotation); + auto fn = makeX86FunctionSample(); + auto r = AssemblyAnnotationMapper::analyzeFunction(fn); + bool found = false; + for (const auto& a : r.annotations) if (a == "@Risk(high)") found = true; + CHECK(found, "missing @Risk(high)"); + PASS(); +} + +void test_align_directive_maps_to_align_annotation() { + TEST(align_directive_maps_to_align_annotation); + Function fn("fn2", "aligned"); + auto* align = new AssemblyDirective(AssemblyDirectiveType::Align, "16"); + fn.addChild("body", align); + fn.addChild("body", new AssemblyInstruction("ret", {})); + auto r = AssemblyAnnotationMapper::analyzeFunction(fn); + bool found = false; + for (const auto& a : r.annotations) if (a == "@Align(16)") found = true; + CHECK(found, "missing @Align annotation"); + PASS(); +} + +void test_complexity_low_for_small_function() { + TEST(complexity_low_for_small_function); + Function fn("fn3", "small"); + fn.addChild("body", new AssemblyInstruction("ret", {})); + auto r = AssemblyAnnotationMapper::analyzeFunction(fn); + CHECK(r.complexity == "low", "expected low complexity"); + PASS(); +} + +void test_complexity_high_for_branch_dense_function() { + TEST(complexity_high_for_branch_dense_function); + Function fn("fn4", "complex"); + for (int i = 0; i < 10; ++i) { + auto* j = new AssemblyInstruction("jmp", {"L" + std::to_string(i)}); + fn.addChild("body", j); + } + auto r = AssemblyAnnotationMapper::analyzeFunction(fn); + CHECK(r.complexity == "high", "expected high complexity"); + PASS(); +} + +void test_register_pressure_count_tracks_unique_registers() { + TEST(register_pressure_count_tracks_unique_registers); + Function fn("fn5", "regs"); + auto* i = new AssemblyInstruction("mov", {"rax", "rbx"}); + i->registers.emplace_back("rax", 64); + i->registers.emplace_back("rbx", 64); + fn.addChild("body", i); + auto* j = new AssemblyInstruction("add", {"rcx", "rdx"}); + j->registers.emplace_back("rcx", 64); + j->registers.emplace_back("rdx", 64); + fn.addChild("body", j); + auto r = AssemblyAnnotationMapper::analyzeFunction(fn); + CHECK(r.registerPressure == 4, "expected 4 unique registers"); + PASS(); +} + +void test_branch_count_detected() { + TEST(branch_count_detected); + Function fn("fn6", "branches"); + fn.addChild("body", new AssemblyInstruction("jmp", {"L1"})); + fn.addChild("body", new AssemblyInstruction("call", {"helper"})); + fn.addChild("body", new AssemblyInstruction("ret", {})); + auto r = AssemblyAnnotationMapper::analyzeFunction(fn); + CHECK(r.branchCount == 3, "expected branch count 3"); + PASS(); +} + +void test_detects_arm_target_from_registers() { + TEST(detects_arm_target_from_registers); + Function fn("fn7", "armfn"); + auto* i = new AssemblyInstruction("mov", {"x0", "x1"}); + i->registers.emplace_back("x0", 64); + i->registers.emplace_back("x1", 64); + fn.addChild("body", i); + auto r = AssemblyAnnotationMapper::analyzeFunction(fn); + bool found = false; + for (const auto& a : r.annotations) if (a == "@Target(arm)") found = true; + CHECK(found, "expected @Target(arm)"); + PASS(); +} + +void test_lower_c_to_x86_assembly_contains_signature_name() { + TEST(lower_c_to_x86_assembly_contains_signature_name); + auto asmCode = AssemblyAnnotationMapper::lowerCToAssembly("int add(int a, int b)", "x86"); + CHECK(asmCode.find("add:") != std::string::npos, "missing function label"); + CHECK(asmCode.find(".global add") != std::string::npos, "missing global directive"); + PASS(); +} + +void test_lower_c_to_arm_assembly_uses_arm_comment_prefix_style() { + TEST(lower_c_to_arm_assembly_uses_arm_comment_prefix_style); + auto asmCode = AssemblyAnnotationMapper::lowerCToAssembly("int f()", "arm"); + CHECK(asmCode.find("@ lowered from C function") != std::string::npos, "missing arm-style comment"); + PASS(); +} + +void test_lift_assembly_to_c_generates_stub() { + TEST(lift_assembly_to_c_generates_stub); + auto fn = makeX86FunctionSample(); + auto c = AssemblyAnnotationMapper::liftAssemblyToC(fn); + CHECK(c.find("int main()") != std::string::npos, "missing function signature"); + CHECK(c.find("return 0;") != std::string::npos, "missing return stub"); + PASS(); +} + +int main() { + std::cout << "Step 464: Assembly Annotation Mapping Tests\n"; + + test_adds_exec_native_annotation(); // 1 + test_adds_target_x86_annotation(); // 2 + test_adds_risk_high_annotation(); // 3 + test_align_directive_maps_to_align_annotation(); // 4 + test_complexity_low_for_small_function(); // 5 + test_complexity_high_for_branch_dense_function(); // 6 + test_register_pressure_count_tracks_unique_registers(); // 7 + test_branch_count_detected(); // 8 + test_detects_arm_target_from_registers(); // 9 + test_lower_c_to_x86_assembly_contains_signature_name(); // 10 + test_lower_c_to_arm_assembly_uses_arm_comment_prefix_style(); // 11 + test_lift_assembly_to_c_generates_stub(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) + << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index d48c896..a5479b8 100644 --- a/progress.md +++ b/progress.md @@ -6170,3 +6170,41 @@ cross-architecture instruction mapper for straightforward x86→ARM projection. **Architecture gate check:** - `editor/src/ast/AssemblyGenerator.h` within header-size limit (`167` <= `600`) - `editor/tests/step463_test.cpp` within test-file size guidance (`147` lines) + +### Step 464: Assembly Annotation Mapping +**Status:** PASS (12/12 tests) + +Adds assembly-aware semantic annotation mapping with complexity heuristics, +register-pressure analysis, alignment mapping, and lightweight C↔assembly +projection helpers. + +**Files added:** +- `editor/src/ast/AssemblyAnnotationMapper.h` — annotation mapper: + - baseline annotations for assembly functions: + - `@Exec(native)` + - `@Risk(high)` + - `@Target(x86|arm)` + - `@Complexity(low|medium|high)` + - `.align` directive mapping to `@Align(...)` + - complexity scoring from instruction count, branch density, register pressure + - register-pressure estimation via unique register cardinality + - cross-language helpers: + - `lowerCToAssembly(...)` + - `liftAssemblyToC(...)` +- `editor/tests/step464_test.cpp` — 12 tests covering: + - required annotation emission + - complexity tiering behavior + - branch/register-pressure metrics + - target architecture inference + - C→assembly lowering and assembly→C lifting stubs + - alignment annotation derivation +- `editor/CMakeLists.txt` — `step464_test` target + +**Verification run:** +- `cmake --build editor/build-native --target step464_test` — PASS +- `./editor/build-native/step464_test` — PASS (12/12) +- `./editor/build-native/step463_test` — PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/ast/AssemblyAnnotationMapper.h` within header-size limit (`128` <= `600`) +- `editor/tests/step464_test.cpp` within test-file size guidance (`177` lines)