| """ |
| 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_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.""" |
|
|
|
|
| |
| |
| |
|
|
| @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 |
|
|
|
|
| |
| |
| |
|
|
| 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 |
|
|
| |
| 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)...") |
|
|
| |
| error_list = self._format_errors(report) |
|
|
| |
| 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 |
|
|
| |
| 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}") |
|
|
|
|
| |
| |
| |
|
|
| 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) |
|
|
| |
| initial_errors = sum(r.initial_errors for r in repair_results) |
| final_errors = sum(r.final_errors for r in repair_results) |
|
|
| |
| 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 |
|
|