Phase 3g — Optimization Pipeline (17/17 tests): Step 68: TransformEngine — constant folding, dead code elimination, OptimizationLock warning Step 69: StrategyAwareOptimizer — annotation-constrained (@Reclaim allows, @Owner blocks duplication, @Deallocate blocks reorder, @Allocate blocks dynamic) Step 70: StrategyValidator — post-optimization invariants (use-after-free, leak, aliasing) Step 71: IncrementalOptimizer — transform journal, undo by ID, provenance tracking Phase 3h — Integration & Validation (26/26 tests): Step 72: Pipeline — end-to-end parse→infer→validate→optimize→generate for Python/C++ Step 73: Error handling — empty ASTs, null roots, nonexistent IDs, unannotated code Step 74: Performance benchmarks — 1000-fn AST 1ms, JSON round-trip 4ms, 50 transforms+undo 1ms Step 75: APIDocGenerator — structured docs for 23 components across 6 categories Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
253 lines
8.9 KiB
C++
253 lines
8.9 KiB
C++
// Step 74 TDD Test: Performance profiling and benchmarks
|
|
//
|
|
// Tests that core operations complete efficiently:
|
|
// 1. Large AST creation (1000 functions with body) completes quickly
|
|
// 2. TransformEngine constant folding on large AST
|
|
// 3. StrategyValidator on deeply nested AST
|
|
// 4. MemoryStrategyInference on large module
|
|
// 5. Serialization round-trip on large AST
|
|
// 6. Multiple IncrementalOptimizer transform passes
|
|
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <cassert>
|
|
#include <chrono>
|
|
#include <memory>
|
|
#include "ast/ASTNode.h"
|
|
#include "ast/Module.h"
|
|
#include "ast/Function.h"
|
|
#include "ast/Variable.h"
|
|
#include "ast/Statement.h"
|
|
#include "ast/Expression.h"
|
|
#include "ast/Annotation.h"
|
|
#include "ast/Serialization.h"
|
|
#include "TransformEngine.h"
|
|
#include "StrategyValidator.h"
|
|
#include "MemoryStrategyInference.h"
|
|
#include "IncrementalOptimizer.h"
|
|
|
|
using Clock = std::chrono::high_resolution_clock;
|
|
|
|
// Helper: build a function with foldable body (return A + B)
|
|
static Function* makeFoldableFunction(int index) {
|
|
std::string id = "f" + std::to_string(index);
|
|
Function* fn = new Function(id, "func_" + std::to_string(index));
|
|
|
|
Return* ret = new Return();
|
|
ret->id = "r" + std::to_string(index);
|
|
BinaryOperation* binOp = new BinaryOperation("b" + std::to_string(index), "+");
|
|
IntegerLiteral* left = new IntegerLiteral("il" + std::to_string(index), index);
|
|
IntegerLiteral* right = new IntegerLiteral("ir" + std::to_string(index), index * 2);
|
|
binOp->setChild("left", left);
|
|
binOp->setChild("right", right);
|
|
ret->setChild("value", binOp);
|
|
fn->addChild("body", ret);
|
|
|
|
return fn;
|
|
}
|
|
|
|
int main() {
|
|
int passed = 0;
|
|
int failed = 0;
|
|
|
|
// --- Test 1: Large AST creation (1000 functions) ---
|
|
{
|
|
auto start = Clock::now();
|
|
|
|
Module mod("m1", "Benchmark", "python");
|
|
for (int i = 0; i < 1000; ++i) {
|
|
mod.addChild("functions", makeFoldableFunction(i));
|
|
}
|
|
|
|
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
Clock::now() - start).count();
|
|
|
|
auto functions = mod.getChildren("functions");
|
|
assert(functions.size() == 1000 && "Should have 1000 functions");
|
|
assert(elapsed < 5000 && "1000 function AST creation should complete in <5s");
|
|
|
|
std::cout << "Test 1 PASS: Large AST creation (" << elapsed << "ms for 1000 functions)" << std::endl;
|
|
++passed;
|
|
|
|
// Clean up
|
|
for (auto* fn : functions) {
|
|
auto body = fn->getChildren("body");
|
|
for (auto* stmt : body) {
|
|
auto* val = stmt->getChild("value");
|
|
if (val && val->conceptType == "IntegerLiteral") {
|
|
// Folded — just the literal
|
|
} else if (val) {
|
|
// Original BinaryOp
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Test 2: Constant folding on large AST ---
|
|
{
|
|
Module mod("m1", "Benchmark", "python");
|
|
for (int i = 0; i < 500; ++i) {
|
|
mod.addChild("functions", makeFoldableFunction(i));
|
|
}
|
|
|
|
auto start = Clock::now();
|
|
|
|
TransformEngine engine;
|
|
engine.setRoot(&mod);
|
|
auto result = engine.constantFolding();
|
|
|
|
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
Clock::now() - start).count();
|
|
|
|
assert(result.applied && "Should fold constants in 500 functions");
|
|
assert(result.nodesModified >= 500 && "Should modify at least 500 nodes");
|
|
assert(elapsed < 5000 && "Folding 500 functions should complete in <5s");
|
|
|
|
std::cout << "Test 2 PASS: Constant folding on 500 functions (" << elapsed << "ms, "
|
|
<< result.nodesModified << " nodes modified)" << std::endl;
|
|
++passed;
|
|
}
|
|
|
|
// --- Test 3: StrategyValidator on deeply nested AST ---
|
|
{
|
|
Module mod("m1", "Benchmark", "cpp");
|
|
Function* fn = new Function("f1", "deep");
|
|
|
|
DeallocateAnnotation* anno = new DeallocateAnnotation();
|
|
anno->id = "a1";
|
|
anno->strategy = "Explicit";
|
|
fn->addChild("annotations", anno);
|
|
|
|
// Create 100 variables (potential leak sources)
|
|
for (int i = 0; i < 100; ++i) {
|
|
Variable* var = new Variable("v" + std::to_string(i), "ptr" + std::to_string(i));
|
|
fn->addChild("body", var);
|
|
}
|
|
mod.addChild("functions", fn);
|
|
|
|
auto start = Clock::now();
|
|
|
|
StrategyValidator validator;
|
|
auto violations = validator.validateInvariants(&mod);
|
|
|
|
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
Clock::now() - start).count();
|
|
|
|
// Should detect leaks for all 100 variables
|
|
int leakCount = 0;
|
|
for (const auto& v : violations) {
|
|
if (v.category == "leak") leakCount++;
|
|
}
|
|
assert(leakCount == 100 && "Should detect 100 leaked allocations");
|
|
assert(elapsed < 5000 && "Validating 100 variables should complete in <5s");
|
|
|
|
std::cout << "Test 3 PASS: StrategyValidator on 100 variables (" << elapsed << "ms, "
|
|
<< leakCount << " leaks detected)" << std::endl;
|
|
++passed;
|
|
}
|
|
|
|
// --- Test 4: MemoryStrategyInference on large module ---
|
|
{
|
|
Module mod("m1", "Benchmark", "cpp");
|
|
for (int i = 0; i < 200; ++i) {
|
|
Function* fn = new Function("f" + std::to_string(i), "func_" + std::to_string(i));
|
|
// Add a variable to each function
|
|
Variable* var = new Variable("v" + std::to_string(i), "data");
|
|
fn->addChild("body", var);
|
|
mod.addChild("functions", fn);
|
|
}
|
|
|
|
auto start = Clock::now();
|
|
|
|
MemoryStrategyInference inferrer;
|
|
auto suggestions = inferrer.inferAnnotations(&mod);
|
|
|
|
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
Clock::now() - start).count();
|
|
|
|
assert(!suggestions.empty() && "Should produce suggestions for C++ module");
|
|
assert(elapsed < 5000 && "Inference on 200 functions should complete in <5s");
|
|
|
|
std::cout << "Test 4 PASS: MemoryStrategyInference on 200 functions (" << elapsed << "ms, "
|
|
<< suggestions.size() << " suggestions)" << std::endl;
|
|
++passed;
|
|
}
|
|
|
|
// --- Test 5: JSON serialization round-trip on large AST ---
|
|
{
|
|
Module mod("m1", "Benchmark", "python");
|
|
for (int i = 0; i < 100; ++i) {
|
|
mod.addChild("functions", makeFoldableFunction(i));
|
|
}
|
|
|
|
auto start = Clock::now();
|
|
|
|
json j = toJson(&mod);
|
|
std::string serialized = j.dump();
|
|
|
|
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
Clock::now() - start).count();
|
|
|
|
assert(!serialized.empty() && "Serialization should produce non-empty output");
|
|
assert(serialized.size() > 1000 && "Serialized 100 functions should be >1KB");
|
|
assert(elapsed < 5000 && "Serializing 100 functions should complete in <5s");
|
|
|
|
// Deserialize back
|
|
auto start2 = Clock::now();
|
|
json parsed = json::parse(serialized);
|
|
ASTNode* restored = fromJson(parsed);
|
|
auto elapsed2 = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
Clock::now() - start2).count();
|
|
|
|
assert(restored != nullptr && "Deserialization should produce a node");
|
|
assert(restored->conceptType == "Module" && "Should restore a Module");
|
|
|
|
std::cout << "Test 5 PASS: JSON round-trip on 100 functions (serialize: " << elapsed
|
|
<< "ms, deserialize: " << elapsed2 << "ms, " << serialized.size() << " bytes)" << std::endl;
|
|
++passed;
|
|
|
|
deleteTree(restored);
|
|
}
|
|
|
|
// --- Test 6: Multiple IncrementalOptimizer passes with undo ---
|
|
{
|
|
Module mod("m1", "Benchmark", "python");
|
|
for (int i = 0; i < 50; ++i) {
|
|
mod.addChild("functions", makeFoldableFunction(i));
|
|
}
|
|
|
|
auto start = Clock::now();
|
|
|
|
IncrementalOptimizer opt;
|
|
opt.setRoot(&mod);
|
|
|
|
// Apply 50 constant-fold transforms
|
|
std::vector<std::string> transformIds;
|
|
for (int i = 0; i < 50; ++i) {
|
|
std::string tid = opt.applyTransform("constant-fold");
|
|
transformIds.push_back(tid);
|
|
}
|
|
|
|
auto history = opt.getTransformHistory();
|
|
assert(history.size() == 50 && "Should have 50 transforms in history");
|
|
|
|
// Undo all transforms
|
|
for (int i = 49; i >= 0; --i) {
|
|
bool undone = opt.undoLast();
|
|
// First undo should work, subsequent may be no-ops if nothing to fold
|
|
}
|
|
|
|
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
Clock::now() - start).count();
|
|
|
|
assert(elapsed < 5000 && "50 transforms + undo should complete in <5s");
|
|
|
|
std::cout << "Test 6 PASS: 50 IncrementalOptimizer passes + undo (" << elapsed << "ms)" << std::endl;
|
|
++passed;
|
|
}
|
|
|
|
// --- Summary ---
|
|
std::cout << "\n=== Step 74 Results: " << passed << " passed, " << failed << " failed ===" << std::endl;
|
|
return failed > 0 ? 1 : 0;
|
|
}
|