llm-compiler-lowering / repair_loop.py
VarunGowda's picture
Add repair_loop.py
106d01d verified
Raw
History Blame Contribute Delete
10.9 kB
"""
Repair Loop Architecture — Iterative validation and LLM-based repair.
Based on the AIvril 2 (arXiv:2412.04485) three-tier architecture:
Tier 1: Syntactic repair (compile errors)
Tier 2: SSA/Type repair (verification errors)
Tier 3: Semantic repair (functional errors via test comparison)
And compiler feedback approach from arXiv:2403.14714.
"""
import os
import time
import json
from dataclasses import dataclass, field, asdict
from typing import Optional
from huggingface_hub import InferenceClient
from ir_validator import validate_ir, LLVMIRValidator, ErrorCategory
# ============================================================================
# Repair prompts
# ============================================================================
REPAIR_SYSTEM_PROMPT = """You are an expert LLVM IR debugger and repair assistant.
You will be given:
1. The original C source code
2. LLVM IR that was generated but has errors
3. A list of specific validation errors
Your task: Fix the LLVM IR to be valid while preserving the original semantics.
Rules:
- Output ONLY the corrected LLVM IR, nothing else
- Fix all reported errors
- Maintain SSA form (each %name defined exactly once)
- Every basic block must have a terminator (ret, br, switch, unreachable)
- Use correct types consistently
- Preserve the function signature
- Use opaque pointers (ptr)"""
REPAIR_USER_TEMPLATE = """Original C code:
```c
{source_code}
```
Generated LLVM IR with errors:
```llvm
{generated_ir}
```
Validation errors found:
{error_list}
Please fix the LLVM IR to resolve all errors while preserving the semantics of the original C code.
Output only the corrected LLVM IR."""
# ============================================================================
# Repair result tracking
# ============================================================================
@dataclass
class RepairIteration:
iteration: int
ir_before: str
ir_after: str
errors_before: int
errors_after: int
errors_fixed: list = field(default_factory=list)
errors_introduced: list = field(default_factory=list)
repair_time_s: float = 0.0
model_used: str = ""
def to_dict(self):
return asdict(self)
@dataclass
class RepairResult:
construct_id: str
model_name: str
original_ir: str
final_ir: str
source_code: str
reference_ir: str
iterations: list = field(default_factory=list)
total_iterations: int = 0
initial_errors: int = 0
final_errors: int = 0
repair_successful: bool = False
total_repair_time_s: float = 0.0
def to_dict(self):
d = asdict(self)
d['error_reduction'] = self.initial_errors - self.final_errors
d['error_reduction_pct'] = (
round((self.initial_errors - self.final_errors) / max(self.initial_errors, 1) * 100, 1)
)
return d
# ============================================================================
# Repair Loop
# ============================================================================
class RepairLoop:
"""Iterative LLM-based repair of invalid LLVM IR."""
def __init__(self, hf_token=None, repair_model_id=None, provider="auto", max_iterations=3):
self.hf_token = hf_token or os.environ.get("HF_TOKEN")
self.repair_model_id = repair_model_id or "Qwen/Qwen2.5-Coder-32B-Instruct"
self.provider = provider
self.max_iterations = max_iterations
self.client = InferenceClient(
provider=self.provider,
api_key=self.hf_token,
)
def repair(self, construct, generated_ir: str, verbose=True) -> RepairResult:
"""
Attempt to repair generated IR through iterative validation-repair cycles.
Architecture (from AIvril 2, arXiv:2412.04485):
1. Validate IR
2. If errors found, format them as feedback
3. Ask LLM to fix
4. Repeat until valid or max_iterations reached
"""
from ir_generator import extract_ir_from_response
result = RepairResult(
construct_id=construct.id,
model_name=self.repair_model_id,
original_ir=generated_ir,
final_ir=generated_ir,
source_code=construct.source_code,
reference_ir=construct.expected_ir,
)
current_ir = generated_ir
# Initial validation
report = validate_ir(current_ir)
result.initial_errors = report.error_count
if report.is_valid:
result.repair_successful = True
result.final_ir = current_ir
if verbose:
print(f" [{construct.id}] Already valid, no repair needed")
return result
for iteration in range(self.max_iterations):
if verbose:
print(f" [{construct.id}] Repair iteration {iteration + 1}/{self.max_iterations} "
f"({report.error_count} errors)...")
# Format error feedback
error_list = self._format_errors(report)
# Build repair prompt
messages = [
{"role": "system", "content": REPAIR_SYSTEM_PROMPT},
{"role": "user", "content": REPAIR_USER_TEMPLATE.format(
source_code=construct.source_code,
generated_ir=current_ir,
error_list=error_list,
)},
]
start_time = time.time()
try:
response = self.client.chat.completions.create(
model=self.repair_model_id,
messages=messages,
max_tokens=4096,
temperature=0.1,
)
raw = response.choices[0].message.content
repaired_ir, _ = extract_ir_from_response(raw)
elapsed = time.time() - start_time
except Exception as e:
elapsed = time.time() - start_time
if verbose:
print(f" Repair API error: {e}")
break
# Validate repaired IR
new_report = validate_ir(repaired_ir)
iteration_result = RepairIteration(
iteration=iteration + 1,
ir_before=current_ir,
ir_after=repaired_ir,
errors_before=report.error_count,
errors_after=new_report.error_count,
repair_time_s=round(elapsed, 2),
model_used=self.repair_model_id,
)
result.iterations.append(iteration_result)
result.total_repair_time_s += elapsed
if verbose:
print(f" Errors: {report.error_count} -> {new_report.error_count}")
current_ir = repaired_ir
report = new_report
if report.is_valid:
result.repair_successful = True
break
result.final_ir = current_ir
result.final_errors = report.error_count
result.total_iterations = len(result.iterations)
if verbose:
status = "SUCCESS" if result.repair_successful else "PARTIAL"
print(f" [{construct.id}] Repair {status}: "
f"{result.initial_errors} -> {result.final_errors} errors "
f"({result.total_repair_time_s:.1f}s)")
return result
def repair_batch(self, constructs_and_irs: list, verbose=True) -> list:
"""
Repair multiple (construct, generated_ir) pairs.
Args: list of (construct, generated_ir) tuples.
"""
results = []
for i, (construct, gen_ir) in enumerate(constructs_and_irs):
if verbose:
print(f"\nRepairing [{i+1}/{len(constructs_and_irs)}] {construct.id}...")
result = self.repair(construct, gen_ir, verbose=verbose)
results.append(result)
return results
def _format_errors(self, report) -> str:
"""Format validation errors as a numbered list for the LLM."""
lines = []
for i, error in enumerate(report.errors, 1):
if error.severity.value == "error":
loc = f" (line {error.line_number})" if error.line_number else ""
content = f" | Code: {error.line_content}" if error.line_content else ""
lines.append(f"{i}. [{error.category.value}] {error.message}{loc}{content}")
return "\n".join(lines) if lines else "No specific errors (general validation failure)"
@staticmethod
def save_repair_results(results: list, filepath: str):
"""Save repair results to JSON."""
data = [r.to_dict() for r in results]
os.makedirs(os.path.dirname(filepath) or ".", exist_ok=True)
with open(filepath, "w") as f:
json.dump(data, f, indent=2)
print(f"Saved {len(data)} repair results to {filepath}")
# ============================================================================
# Repair Statistics
# ============================================================================
def compute_repair_statistics(repair_results: list) -> dict:
"""Compute aggregate repair statistics."""
total = len(repair_results)
successful = sum(1 for r in repair_results if r.repair_successful)
total_iters = sum(r.total_iterations for r in repair_results)
total_time = sum(r.total_repair_time_s for r in repair_results)
# Error reduction
initial_errors = sum(r.initial_errors for r in repair_results)
final_errors = sum(r.final_errors for r in repair_results)
# Per-iteration statistics
iter_improvements = []
for r in repair_results:
for it in r.iterations:
improvement = it.errors_before - it.errors_after
iter_improvements.append(improvement)
stats = {
"total_repairs_attempted": total,
"successful_repairs": successful,
"success_rate_pct": round(successful / max(total, 1) * 100, 1),
"total_iterations": total_iters,
"avg_iterations_per_repair": round(total_iters / max(total, 1), 1),
"total_initial_errors": initial_errors,
"total_final_errors": final_errors,
"total_errors_fixed": initial_errors - final_errors,
"error_reduction_pct": round(
(initial_errors - final_errors) / max(initial_errors, 1) * 100, 1
),
"total_repair_time_s": round(total_time, 1),
"avg_repair_time_s": round(total_time / max(total, 1), 1),
"avg_error_reduction_per_iteration": round(
sum(iter_improvements) / max(len(iter_improvements), 1), 2
),
"iterations_with_improvement": sum(1 for i in iter_improvements if i > 0),
"iterations_with_regression": sum(1 for i in iter_improvements if i < 0),
"iterations_with_no_change": sum(1 for i in iter_improvements if i == 0),
}
return stats