Files
whetstone_DSL/editor/src/MemoryStrategyInference.h

385 lines
14 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
// Step 65: Memory strategy inference
//
// MemoryStrategyInference: analyses an AST and suggests appropriate
// memory annotations based on the module's target language and
// usage patterns. Suggestions are returned as data — they are
// never auto-applied to the AST.
#include <string>
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <cctype>
#include "ast/ASTNode.h"
#include "ast/Module.h"
#include "ast/Function.h"
#include "ast/Variable.h"
#include "ast/Type.h"
#include "ast/Expression.h"
#include "ast/Annotation.h"
class MemoryStrategyInference {
public:
struct Suggestion {
std::string nodeId;
std::string annotationType; // e.g. "ReclaimAnnotation"
std::string strategy; // e.g. "Tracing"
std::string reason;
double confidence; // 0.0 1.0
};
struct FeedbackStats {
int accepted = 0;
int rejected = 0;
};
// Analyse the AST and return suggestions. Does NOT modify the AST.
std::vector<Suggestion> inferAnnotations(const ASTNode* root) const {
std::vector<Suggestion> out;
if (!root) return out;
inferNode(root, out);
applyFeedback(out);
return out;
}
void recordFeedback(const Suggestion& suggestion, bool accepted) const {
auto& stats = feedback_[feedbackKey(suggestion)];
if (accepted) {
stats.accepted += 1;
} else {
stats.rejected += 1;
}
}
private:
inline static std::unordered_map<std::string, FeedbackStats> feedback_;
void inferNode(const ASTNode* node,
std::vector<Suggestion>& out) const {
if (!node) return;
if (node->conceptType == "Module") {
inferModule(static_cast<const Module*>(node), out);
}
// Recurse — but we already handle functions inside inferModule,
// so only recurse into non-Module children here for nested modules.
for (auto* child : node->allChildren()) {
if (child->conceptType == "Module") {
inferNode(child, out);
}
}
}
void inferModule(const Module* mod,
std::vector<Suggestion>& out) const {
const auto& lang = mod->targetLanguage;
// --- language-level defaults ---
if (lang == "python" || lang == "elisp" || lang == "ruby" ||
lang == "javascript" || lang == "java" || lang == "csharp" ||
lang == "kotlin" || lang == "common-lisp" || lang == "commonlisp" ||
lang == "lisp" || lang == "cl" || lang == "scheme" || lang == "scm") {
// GC languages → @Reclaim(Tracing) on the module
out.push_back({mod->id, "ReclaimAnnotation", "Tracing",
lang + " uses tracing garbage collection",
0.95});
if (lang == "common-lisp" || lang == "commonlisp" ||
lang == "lisp" || lang == "cl" || lang == "scheme" || lang == "scm") {
out.push_back({mod->id, "OwnerAnnotation", "Shared_GC",
"Lisp-family values are GC-managed shared objects",
0.92});
}
}
else if (lang == "go") {
out.push_back({mod->id, "ReclaimAnnotation", "Escape",
"Go uses escape-analysis-based GC", 0.90});
}
else if (lang == "cpp") {
inferCppModule(mod, out);
}
else if (lang == "c") {
inferCModule(mod, out);
}
else if (lang == "rust") {
out.push_back({mod->id, "OwnerAnnotation", "Single",
"Rust uses single-owner lifetime tracking", 0.95});
}
else if (lang == "wat" || lang == "wasm") {
inferWatModule(mod, out);
}
else if (lang == "swift" || lang == "objc") {
out.push_back({mod->id, "OwnerAnnotation", "Shared_ARC",
lang + " uses automatic reference counting", 0.90});
}
// --- per-function analysis ---
for (auto* child : mod->getChildren("functions")) {
if (child->conceptType == "Function") {
inferFunction(static_cast<const Function*>(child), lang, out);
}
}
}
void inferCppModule(const Module* mod,
std::vector<Suggestion>& out) const {
// Default C++ suggestion: RAII is idiomatic
out.push_back({mod->id, "LifetimeAnnotation", "RAII",
"C++ idiom: RAII with scope-based cleanup", 0.70});
}
void inferCModule(const Module* mod,
std::vector<Suggestion>& out) const {
out.push_back({mod->id, "OwnerAnnotation", "Manual",
"C defaults to manual ownership", 0.95});
out.push_back({mod->id, "LifetimeAnnotation", "Scope",
"C locals default to lexical scope lifetime", 0.92});
out.push_back({mod->id, "ReclaimAnnotation", "Explicit",
"C uses explicit reclamation (free)", 0.95});
}
void inferWatModule(const Module* mod,
std::vector<Suggestion>& out) const {
out.push_back({mod->id, "OwnerAnnotation", "Manual",
"WAT linear memory is manually managed", 0.92});
out.push_back({mod->id, "ReclaimAnnotation", "Explicit",
"WAT has explicit memory lifetime operations", 0.88});
}
void inferFunction(const Function* fn, const std::string& lang,
std::vector<Suggestion>& out) const {
// Skip if function already has a memory annotation
if (hasMemoryAnnotation(fn)) return;
if (lang == "c") {
inferCFunction(fn, out);
return;
}
if (lang == "wat" || lang == "wasm") {
inferWatFunction(fn, out);
return;
}
// Check body for patterns
bool hasAlloc = false;
bool hasDealloc = false;
bool allConst = true;
for (auto* child : fn->getChildren("body")) {
if (child->conceptType == "Variable") {
// Any mutable variable means not all-const
allConst = false;
}
scanForAllocDealloc(child, hasAlloc, hasDealloc);
}
if (lang == "cpp") {
if (hasAlloc && !hasDealloc) {
out.push_back({fn->id, "LifetimeAnnotation", "RAII",
"Function allocates without explicit dealloc — "
"RAII recommended",
0.75});
} else if (hasAlloc && hasDealloc) {
out.push_back({fn->id, "DeallocateAnnotation", "Explicit",
"Function uses explicit alloc/dealloc pattern",
0.80});
}
}
// Immutable function body (no variables) → static candidate
if (allConst && fn->getChildren("body").empty() == false) {
// Only suggest if not already covered by a language-level rule
if (lang == "cpp" || lang == "c") {
out.push_back({fn->id, "AllocateAnnotation", "Static",
"Function body contains no mutable state — "
"static allocation candidate",
0.50});
}
}
}
void inferCFunction(const Function* fn,
std::vector<Suggestion>& out) const {
bool hasAlloc = false;
for (auto* paramNode : fn->getChildren("parameters")) {
if (paramNode->conceptType != "Parameter") continue;
auto* typeNode = paramNode->getChild("type");
if (isPointerType(typeNode)) {
out.push_back({paramNode->id, "OwnerAnnotation", "Borrowed",
"Pointer parameters in C are borrowed by default",
0.86});
}
}
for (auto* stmt : fn->getChildren("body")) {
hasAlloc = hasAlloc || containsAllocCall(stmt);
inferCLocalLifetime(stmt, out);
}
auto* retType = fn->getChild("returnType");
if (isPointerType(retType)) {
out.push_back({fn->id, "OwnerAnnotation", inferCReturnOwner(fn->name),
"C pointer returns are convention-based ownership transfers",
0.72});
}
if (hasAlloc) {
out.push_back({fn->id, "OwnerAnnotation", "Manual",
"malloc/calloc allocation indicates manual ownership",
0.94});
out.push_back({fn->id, "ReclaimAnnotation", "Explicit",
"malloc/calloc requires explicit free", 0.94});
}
}
void inferWatFunction(const Function* fn,
std::vector<Suggestion>& out) const {
bool hasMemoryOps = false;
for (auto* stmt : fn->getChildren("body")) {
hasMemoryOps = hasMemoryOps || containsWatMemoryOp(stmt);
}
if (hasMemoryOps) {
out.push_back({fn->id, "OwnerAnnotation", "Manual",
"WAT memory/load/store operations require manual ownership",
0.90});
}
}
static bool hasMemoryAnnotation(const ASTNode* node) {
for (auto* anno : node->getChildren("annotations")) {
const auto& ct = anno->conceptType;
if (ct == "DeallocateAnnotation" || ct == "LifetimeAnnotation" ||
ct == "ReclaimAnnotation" || ct == "OwnerAnnotation" ||
ct == "AllocateAnnotation")
return true;
}
return false;
}
static void scanForAllocDealloc(const ASTNode* node,
bool& hasAlloc, bool& hasDealloc) {
if (!node) return;
if (node->conceptType == "FunctionCall") {
auto* fc = static_cast<const FunctionCall*>(node);
if (fc->functionName == "malloc" || fc->functionName == "new" ||
fc->functionName == "calloc" || fc->functionName == "allocate")
hasAlloc = true;
if (fc->functionName == "free" || fc->functionName == "delete" ||
fc->functionName == "release" || fc->functionName == "deallocate")
hasDealloc = true;
}
for (auto* child : node->allChildren()) {
scanForAllocDealloc(child, hasAlloc, hasDealloc);
}
}
static bool containsAllocCall(const ASTNode* node) {
if (!node) return false;
if (node->conceptType == "FunctionCall") {
auto* fc = static_cast<const FunctionCall*>(node);
if (fc->functionName == "malloc" || fc->functionName == "calloc") {
return true;
}
}
for (auto* child : node->allChildren()) {
if (containsAllocCall(child)) return true;
}
return false;
}
static bool containsWatMemoryOp(const ASTNode* node) {
if (!node) return false;
if (node->conceptType == "FunctionCall") {
auto* fc = static_cast<const FunctionCall*>(node);
std::string fn = lowerAscii(fc->functionName);
if (fn.find("memory.") != std::string::npos ||
fn.find(".load") != std::string::npos ||
fn.find(".store") != std::string::npos ||
fn == "memory.grow")
return true;
}
for (auto* child : node->allChildren()) {
if (containsWatMemoryOp(child)) return true;
}
return false;
}
static void inferCLocalLifetime(const ASTNode* node,
std::vector<Suggestion>& out) {
if (!node) return;
if (node->conceptType == "Variable") {
auto* v = static_cast<const Variable*>(node);
auto* typeNode = v->getChild("type");
std::string typeKind = typeString(typeNode);
std::string lowered = lowerAscii(typeKind);
if (lowered.find("static") != std::string::npos) {
out.push_back({v->id, "LifetimeAnnotation", "Static",
"Static storage duration detected in C declaration",
0.90});
} else {
out.push_back({v->id, "LifetimeAnnotation", "Scope",
"Local C variable uses scope-bound lifetime", 0.88});
}
}
for (auto* child : node->allChildren()) {
inferCLocalLifetime(child, out);
}
}
static std::string inferCReturnOwner(const std::string& fnName) {
std::string lower = lowerAscii(fnName);
if (startsWith(lower, "get") || startsWith(lower, "borrow") ||
startsWith(lower, "peek") || startsWith(lower, "view")) {
return "Borrowed";
}
return "Transferred";
}
static bool isPointerType(const ASTNode* typeNode) {
return typeString(typeNode).find('*') != std::string::npos;
}
static std::string typeString(const ASTNode* typeNode) {
if (!typeNode) return "";
if (typeNode->conceptType == "PrimitiveType") {
return static_cast<const PrimitiveType*>(typeNode)->kind;
}
if (typeNode->conceptType == "CustomType") {
return static_cast<const CustomType*>(typeNode)->typeName;
}
return "";
}
static std::string lowerAscii(const std::string& input) {
std::string out = input;
std::transform(out.begin(), out.end(), out.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return out;
}
static bool startsWith(const std::string& value, const std::string& prefix) {
return value.rfind(prefix, 0) == 0;
}
static std::string feedbackKey(const Suggestion& suggestion) {
return suggestion.annotationType + ":" + suggestion.strategy;
}
void applyFeedback(std::vector<Suggestion>& out) const {
for (auto& s : out) {
auto it = feedback_.find(feedbackKey(s));
if (it == feedback_.end()) continue;
const auto& stats = it->second;
int total = stats.accepted + stats.rejected;
if (total <= 0) continue;
double bias = (double)(stats.accepted - stats.rejected) /
(double)total;
double factor = 1.0 + 0.25 * bias;
s.confidence = std::clamp(s.confidence * factor, 0.0, 1.0);
}
}
};