Complete step464 assembly annotation mapping with tests
This commit is contained in:
@@ -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)
|
||||
|
||||
128
editor/src/ast/AssemblyAnnotationMapper.h
Normal file
128
editor/src/ast/AssemblyAnnotationMapper.h
Normal file
@@ -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 <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct AssemblyAnnotationReport {
|
||||
std::string functionName;
|
||||
std::vector<std::string> 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<std::string> uniqueRegs;
|
||||
std::string target = "x86";
|
||||
for (auto* node : fn.getChildren("body")) {
|
||||
if (node->conceptType == "AssemblyInstruction") {
|
||||
auto* i = static_cast<AssemblyInstruction*>(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<AssemblyDirective*>(node);
|
||||
if (d->type == AssemblyDirectiveType::Align) {
|
||||
report.annotations.push_back("@Align(" + d->value + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
report.registerPressure = static_cast<int>(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<AssemblyRegister>& 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<unsigned char>(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<char>(std::tolower(static_cast<unsigned char>(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;
|
||||
}
|
||||
};
|
||||
177
editor/tests/step464_test.cpp
Normal file
177
editor/tests/step464_test.cpp
Normal file
@@ -0,0 +1,177 @@
|
||||
// Step 464: Assembly Annotation Mapping Tests (12 tests)
|
||||
|
||||
#include "ast/AssemblyAnnotationMapper.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
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;
|
||||
}
|
||||
38
progress.md
38
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)
|
||||
|
||||
Reference in New Issue
Block a user