"""Local analysis of AMD GPU code objects (.co files).
This module provides local analysis of .co files using ROCm LLVM tools
(llvm-objdump, llvm-readelf, clang-offload-bundler) when available.
If tools are not available, analysis should fall back to API server.
"""
import os
import re
import shutil
import subprocess
import tempfile
from dataclasses import dataclass
from pathlib import Path
from typing import Optional
from wafer_core.gpu_detect import detect_local_gpu
@dataclass(frozen=True)
class ISACheckResult:
    """Result of checking ISA analysis tool availability.
    
    Attributes:
        available: Whether ROCm LLVM tools are available
        llvm_objdump_path: Path to llvm-objdump if found
        llvm_readelf_path: Path to llvm-readelf if found
        clang_offload_bundler_path: Path to clang-offload-bundler if found
        checked_paths: List of paths that were checked
        missing_tools: List of tool names that are missing
        install_command: Installation instructions if not available
    """
    available: bool
    llvm_objdump_path: Optional[str] = None
    llvm_readelf_path: Optional[str] = None
    clang_offload_bundler_path: Optional[str] = None
    checked_paths: Optional[list[str]] = None
    missing_tools: Optional[list[str]] = None
    install_command: Optional[str] = None
def _find_rocm_llvm_bin() -> Optional[Path]:
    """Find ROCm LLVM bin directory, checking multiple possible locations.
    
    Checks in order:
    1. Environment variable ROCM_LLVM_BIN
    2. System location /opt/rocm/llvm/bin
    3. User-local location ~/.local/rocm/llvm/bin
    4. PATH (via shutil.which for individual binaries)
    
    Returns Path to bin directory if found, None otherwise.
    """
    # 1. Environment variable (set during installation or deployment)
    env_path = os.environ.get("ROCM_LLVM_BIN")
    if env_path:
        bin_path = Path(env_path)
        if bin_path.exists() and (bin_path / "llvm-objdump").exists():
            return bin_path
    
    # 2. System location
    system_path = Path("/opt/rocm/llvm/bin")
    if system_path.exists() and (system_path / "llvm-objdump").exists():
        return system_path
    
    # 3. User-writable location
    user_path = Path.home() / ".local" / "rocm" / "llvm" / "bin"
    if user_path.exists() and (user_path / "llvm-objdump").exists():
        return user_path
    
    # 4. Check PATH for individual binaries (system LLVM might work)
    # If llvm-objdump is in PATH, return its parent directory
    llvm_objdump = shutil.which("llvm-objdump")
    if llvm_objdump:
        return Path(llvm_objdump).parent
    
    return None
def _get_binary_path(binary_name: str) -> str:
    """Get full path to a ROCm LLVM binary.
    
    Checks the ROCm LLVM bin directory first, then falls back to PATH.
    
    Raises RuntimeError if binary not found.
    """
    bin_dir = _find_rocm_llvm_bin()
    if bin_dir:
        binary_path = bin_dir / binary_name
        if binary_path.exists():
            return str(binary_path)
    
    which_path = shutil.which(binary_name)
    if which_path:
        return which_path
    
    raise RuntimeError(f"Binary not found: {binary_name}")
def _check_local_is_amd() -> tuple[bool, Optional[str], list[str]]:
    """Check if local machine has AMD GPU detected.
    
    Returns:
        Tuple of (is_amd, gpu_name, checked_methods)
        - is_amd: True if AMD GPU detected
        - gpu_name: GPU name if detected, None otherwise
        - checked_methods: List of methods that were checked
    """
    checked_methods = []
    
    try:
        result = subprocess.run(
            ["rocm-smi", "--showproductname"],
            capture_output=True,
            text=True,
            timeout=10,
        )
        checked_methods.append("rocm-smi --showproductname")
        if result.returncode == 0 and result.stdout.strip():
            gpu_info = detect_local_gpu()
            if gpu_info and gpu_info.vendor == "amd":
                return True, gpu_info.gpu_name, checked_methods
    except (FileNotFoundError, subprocess.TimeoutExpired):
        pass
    
    try:
        result = subprocess.run(
            ["rocminfo"],
            capture_output=True,
            text=True,
            timeout=10,
        )
        checked_methods.append("rocminfo")
        if result.returncode == 0 and "GPU" in result.stdout:
            return True, "AMD GPU (detected via rocminfo)", checked_methods
    except (FileNotFoundError, subprocess.TimeoutExpired):
        pass
    
    kfd_path = Path("/dev/kfd")
    checked_methods.append("/dev/kfd")
    if kfd_path.exists():
        # Device exists, but we can't determine GPU name from it
        return True, "AMD GPU (detected via /dev/kfd)", checked_methods
    
    return False, None, checked_methods
def _check_llvm_rocm_available() -> ISACheckResult:
    """Check if ROCm LLVM tools are available with detailed diagnostics.
    
    Returns:
        ISACheckResult with availability status and detailed information
    """
    required_tools = ["llvm-objdump", "llvm-readelf", "clang-offload-bundler"]
    checked_paths = []
    found_tools = {}
    missing_tools = []
    
    paths_to_check = []
    
    # 1. Environment variable
    env_path = os.environ.get("ROCM_LLVM_BIN")
    if env_path:
        paths_to_check.append(env_path)
        checked_paths.append(f"ROCM_LLVM_BIN={env_path}")
    
    # 2. System location
    system_path = "/opt/rocm/llvm/bin"
    paths_to_check.append(system_path)
    checked_paths.append(system_path)
    
    # 3. User-local location
    user_path = str(Path.home() / ".local" / "rocm" / "llvm" / "bin")
    paths_to_check.append(user_path)
    checked_paths.append(user_path)
    
    # 4. Check PATH
    checked_paths.append("PATH")
    
    for tool in required_tools:
        found = False
        
        for path_str in paths_to_check:
            if path_str == "PATH":
                continue
            tool_path = Path(path_str) / tool
            if tool_path.exists() and os.access(tool_path, os.X_OK):
                found_tools[tool] = str(tool_path)
                found = True
                break
        
        if not found:
            which_path = shutil.which(tool)
            if which_path:
                found_tools[tool] = which_path
                found = True
        
        if not found:
            missing_tools.append(tool)
    
    available = len(missing_tools) == 0
    
    # Generate install command
    install_command = None
    if not available:
        install_command = (
            "Install ROCm toolkit: https://rocm.docs.amd.com/en/latest/deploy/linux/install.html\n"
            "Or install ROCm LLVM tools:\n"
            "  sudo apt-get install rocm-llvm"
        )
    
    return ISACheckResult(
        available=available,
        llvm_objdump_path=found_tools.get("llvm-objdump"),
        llvm_readelf_path=found_tools.get("llvm-readelf"),
        clang_offload_bundler_path=found_tools.get("clang-offload-bundler"),
        checked_paths=checked_paths,
        missing_tools=missing_tools if missing_tools else None,
        install_command=install_command,
    )
def check_installation() -> ISACheckResult:
    """Check if ROCm LLVM tools are installed (mirrors rocprof-compute pattern).
    
    Returns:
        ISACheckResult with installation status and details
    """
    return _check_llvm_rocm_available()
def _run_command(cmd: list[str], cwd: Optional[Path] = None) -> tuple[str, str, int]:
    """Run a command and return stdout, stderr, returncode."""
    result = subprocess.run(cmd, capture_output=True, text=True, cwd=cwd, timeout=30)
    return result.stdout, result.stderr, result.returncode
def _list_bundle_targets(co_path: Path) -> Optional[list[str]]:
    """List targets in the offload bundle.
    
    Returns None if the file is not an offload bundle (standalone HSA code object).
    """
    bundler_path = _get_binary_path("clang-offload-bundler")
    cmd = [
        bundler_path,
        "--list",
        "--type=o",
        f"--input={co_path}",
    ]
    stdout, stderr, rc = _run_command(cmd)
    if rc != 0:
        # File might be a standalone HSA code object, not an offload bundle
        return None
    targets = [line.strip() for line in stdout.strip().split("\n") if line.strip()]
    return targets if targets else None
def _is_gpu_code_object(co_path: Path) -> bool:
    """Check if file is a valid GPU code object by trying to read its metadata."""
    readelf_path = _get_binary_path("llvm-readelf")
    cmd = [
        readelf_path,
        "--file-header",
        str(co_path),
    ]
    stdout, stderr, rc = _run_command(cmd)
    return rc == 0 and ("AMDGPU" in stdout or "amdgcn" in stdout.lower())
def _extract_gpu_code(co_path: Path, target: str, output_path: Path) -> None:
    """Extract GPU code object from offload bundle."""
    bundler_path = _get_binary_path("clang-offload-bundler")
    cmd = [
        bundler_path,
        "--type=o",
        f"--input={co_path}",
        f"--output={output_path}",
        "--unbundle",
        f"--targets={target}",
    ]
    stdout, stderr, rc = _run_command(cmd)
    if rc != 0:
        raise ValueError(f"Failed to extract GPU code: {stderr}")
def _disassemble(gpu_co_path: Path) -> str:
    """Disassemble GPU code object to ISA."""
    objdump_path = _get_binary_path("llvm-objdump")
    cmd = [
        objdump_path,
        "-d",
        str(gpu_co_path),
    ]
    stdout, stderr, rc = _run_command(cmd)
    if rc != 0:
        raise ValueError(f"Failed to disassemble: {stderr}")
    return stdout
def _get_metadata(gpu_co_path: Path) -> str:
    """Extract kernel metadata from GPU code object."""
    readelf_path = _get_binary_path("llvm-readelf")
    cmd = [
        readelf_path,
        "--notes",
        str(gpu_co_path),
    ]
    stdout, stderr, rc = _run_command(cmd)
    if rc != 0:
        raise ValueError(f"Failed to read metadata: {stderr}")
    return stdout
def _parse_metadata(metadata: str) -> dict:
    """Parse kernel metadata YAML from llvm-readelf output."""
    result = {
        "kernel_name": "unknown",
        "architecture": "unknown",
        "vgpr_count": 0,
        "sgpr_count": 0,
        "agpr_count": 0,
        "vgpr_spill_count": 0,
        "sgpr_spill_count": 0,
        "lds_bytes": 0,
    }
    arch_match = re.search(r"amdhsa\.target:\s+amdgcn-amd-amdhsa--(gfx[0-9a-zA-Z]+)", metadata)
    if arch_match:
        result["architecture"] = arch_match.group(1)
    name_match = re.search(r"\.name:\s+(\S+)", metadata)
    if name_match:
        result["kernel_name"] = name_match.group(1)
    vgpr_match = re.search(r"\.vgpr_count:\s+(\d+)", metadata)
    if vgpr_match:
        result["vgpr_count"] = int(vgpr_match.group(1))
    sgpr_match = re.search(r"\.sgpr_count:\s+(\d+)", metadata)
    if sgpr_match:
        result["sgpr_count"] = int(sgpr_match.group(1))
    agpr_match = re.search(r"\.agpr_count:\s+(\d+)", metadata)
    if agpr_match:
        result["agpr_count"] = int(agpr_match.group(1))
    vgpr_spill_match = re.search(r"\.vgpr_spill_count:\s+(\d+)", metadata)
    if vgpr_spill_match:
        result["vgpr_spill_count"] = int(vgpr_spill_match.group(1))
    sgpr_spill_match = re.search(r"\.sgpr_spill_count:\s+(\d+)", metadata)
    if sgpr_spill_match:
        result["sgpr_spill_count"] = int(sgpr_spill_match.group(1))
    lds_match = re.search(r"\.group_segment_fixed_size:\s+(\d+)", metadata)
    if lds_match:
        result["lds_bytes"] = int(lds_match.group(1))
    return result
def _analyze_isa(isa_text: str) -> dict:
    """Analyze ISA text for instruction counts."""
    result = {
        "global_loads": 0,
        "global_stores": 0,
        "lds_ops": 0,
        "mfma_count": 0,
        "fma_count": 0,
        "packed_ops_count": 0,
        "waitcnt_full_stalls": 0,
        "barriers": 0,
    }
    for line in isa_text.split("\n"):
        line_lower = line.lower()
        # Global memory operations
        if "global_load" in line_lower:
            result["global_loads"] += 1
        if "global_store" in line_lower:
            result["global_stores"] += 1
        # LDS operations
        if "ds_read" in line_lower or "ds_write" in line_lower:
            result["lds_ops"] += 1
        # MFMA instructions
        if "v_mfma" in line_lower:
            result["mfma_count"] += 1
        # FMA instructions (v_fmac, v_fma)
        if "v_fmac" in line_lower or "v_fma_" in line_lower:
            result["fma_count"] += 1
        # Packed operations
        if "v_pk_" in line_lower:
            result["packed_ops_count"] += 1
        # Waitcnt with full stalls (vmcnt(0) or lgkmcnt(0))
        if "s_waitcnt" in line_lower and ("vmcnt(0)" in line_lower or "lgkmcnt(0)" in line_lower):
            result["waitcnt_full_stalls"] += 1
        # Barriers
        if "s_barrier" in line_lower:
            result["barriers"] += 1
    return result
def _get_instruction_explanation(line: str) -> str | None:
    """Get human-readable explanation for an instruction."""
    line_lower = line.lower()
    
    # MFMA (Matrix Fused Multiply-Add) - Tensor core operations
    if "v_mfma" in line_lower:
        return "MFMA: Matrix fused multiply-accumulate (tensor core operation, high throughput)"
    
    # FMA instructions
    if "v_fmac" in line_lower or "v_fma_" in line_lower:
        return "FMA: Fused multiply-add (high throughput FP32/FP64)"
    
    # Global memory loads
    if "global_load" in line_lower:
        if "v_gbuf_load" in line_lower or "buffer_load" in line_lower:
            return "Global Load: Reads from global memory (high latency, cacheable)"
        return "Global Load: Reads from global memory (high latency)"
    
    # Global memory stores
    if "global_store" in line_lower:
        if "v_gbuf_store" in line_lower or "buffer_store" in line_lower:
            return "Global Store: Writes to global memory (high latency, cacheable)"
        return "Global Store: Writes to global memory (high latency)"
    
    # LDS (Local Data Share) operations
    if "ds_read" in line_lower:
        return "LDS Read: Reads from shared local memory (low latency, high bandwidth)"
    if "ds_write" in line_lower:
        return "LDS Write: Writes to shared local memory (low latency, high bandwidth)"
    
    # Packed operations
    if "v_pk_" in line_lower:
        return "Packed Op: Processes multiple data elements in one instruction (2x throughput)"
    
    # Waitcnt - synchronization
    if "s_waitcnt" in line_lower:
        if "vmcnt(0)" in line_lower or "lgkmcnt(0)" in line_lower:
            return "Waitcnt (FULL STALL): Waits for all pending memory operations (may cause pipeline stall)"
        return "Waitcnt: Waits for pending memory operations to complete"
    
    # Barriers
    if "s_barrier" in line_lower:
        return "Barrier: Synchronizes all threads in workgroup (all threads wait here)"
    
    # Register moves
    if "v_mov" in line_lower or "s_mov" in line_lower:
        return "Move: Copies register value"
    
    # ALU operations
    if "v_add" in line_lower or "s_add" in line_lower:
        return "Add: Performs addition"
    if "v_sub" in line_lower or "s_sub" in line_lower:
        return "Subtract: Performs subtraction"
    if "v_mul" in line_lower or "s_mul" in line_lower:
        return "Multiply: Performs multiplication"
    
    # Comparisons
    if "v_cmp" in line_lower or "s_cmp" in line_lower:
        return "Compare: Compares values and sets condition codes"
    
    # Branches
    if "s_branch" in line_lower or "s_cbranch" in line_lower:
        return "Branch: Conditional or unconditional jump"
    
    # Vector operations
    if "v_and" in line_lower or "v_or" in line_lower or "v_xor" in line_lower:
        return "Bitwise Op: Performs bitwise AND/OR/XOR operation"
    
    return None
def _extract_registers(line: str) -> tuple[list[str], list[str]]:
    """Extract VGPR and SGPR registers from an instruction line."""
    vgprs = []
    sgprs = []
    
    # VGPR pattern: v0, v1, v[0], v[0:1], v0.64, etc.
    vgpr_pattern = re.compile(r'\bv(\d+|\[\d+(?::\d+)?\](?:\.\d+)?)\b', re.IGNORECASE)
    for match in vgpr_pattern.finditer(line):
        vgprs.append(match.group(0))
    
    # SGPR pattern: s0, s1, s[0], s[0:1], etc.
    sgpr_pattern = re.compile(r'\bs(\d+|\[\d+(?::\d+)?\])\b', re.IGNORECASE)
    for match in sgpr_pattern.finditer(line):
        sgprs.append(match.group(0))
    
    # AGPR pattern: a0, a1, etc.
    agpr_pattern = re.compile(r'\ba(\d+|\[\d+(?::\d+)?\])\b', re.IGNORECASE)
    for match in agpr_pattern.finditer(line):
        vgprs.append(match.group(0))  # AGPRs are accumulator VGPRs
    
    return vgprs, sgprs
def _annotate_isa(
    isa_text: str,
    meta: dict,
    isa_analysis: dict,
) -> str:
    """Annotate ISA with performance hints, explanations, and register usage.
    
    Adds comments explaining:
    - Performance characteristics (stalls, spills, high-throughput ops)
    - Instruction explanations (what each instruction does)
    - Register usage patterns (VGPR/SGPR usage)
    """
    lines = isa_text.split("\n")
    annotated_lines = []
    
    vgpr_usage = set()
    sgpr_usage = set()
    
    for line in lines:
        line_stripped = line.strip()
        line_lower = line.lower()
        
        if not line_stripped or line_stripped.startswith("Disassembly") or line_stripped.startswith(":"):
            annotated_lines.append(line)
            continue
        
        annotations = []
        
        # Performance annotations
        if "v_mfma" in line_lower:
            annotations.append("⚡ HIGH THROUGHPUT: Tensor core operation")
        
        if "global_load" in line_lower:
            annotations.append("⚠️ HIGH LATENCY: Global memory access")
        if "global_store" in line_lower:
            annotations.append("⚠️ HIGH LATENCY: Global memory write")
        
        if "s_waitcnt" in line_lower and ("vmcnt(0)" in line_lower or "lgkmcnt(0)" in line_lower):
            annotations.append("🚨 FULL STALL: Waiting for all memory operations (may cause pipeline stall)")
        
        if "s_barrier" in line_lower:
            annotations.append("🔒 BARRIER: All threads synchronize here")
        
        # Register usage annotations
        vgprs, sgprs = _extract_registers(line)
        if vgprs:
            vgpr_usage.update(vgprs)
            if len(vgprs) <= 3:
                annotations.append(f"VGPRs: {', '.join(vgprs[:3])}")
            else:
                annotations.append(f"VGPRs: {', '.join(vgprs[:3])}... ({len(vgprs)} total)")
        
        if sgprs:
            sgpr_usage.update(sgprs)
            if len(sgprs) <= 3:
                annotations.append(f"SGPRs: {', '.join(sgprs[:3])}")
            else:
                annotations.append(f"SGPRs: {', '.join(sgprs[:3])}... ({len(sgprs)} total)")
        
        # Instruction explanation
        explanation = _get_instruction_explanation(line)
        if explanation:
            annotations.append(explanation)
        
        annotated_lines.append(line)
        
        if annotations:

            indent = len(line) - len(line.lstrip())
            indent_str = " " * indent
            
            for annotation in annotations:
                annotated_lines.append(f"{indent_str}; {annotation}")
    
    summary_lines = [
        "; ============================================================================",
        "; Annotated ISA Analysis",
        "; ============================================================================",
        f"; Kernel: {meta.get('kernel_name', 'unknown')}",
        f"; Architecture: {meta.get('architecture', 'unknown')}",
        f";",
        f"; Register Usage:",
        f";   VGPRs: {meta.get('vgpr_count', 0)} ({meta.get('vgpr_spill_count', 0)} spills)",
        f";   SGPRs: {meta.get('sgpr_count', 0)} ({meta.get('sgpr_spill_count', 0)} spills)",
        f";   AGPRs: {meta.get('agpr_count', 0)}",
        f";",
        f"; Performance Metrics:",
        f";   MFMA ops: {isa_analysis.get('mfma_count', 0)} (tensor core operations)",
        f";   Global loads: {isa_analysis.get('global_loads', 0)}",
        f";   Global stores: {isa_analysis.get('global_stores', 0)}",
        f";   LDS ops: {isa_analysis.get('lds_ops', 0)}",
        f";   Full stalls: {isa_analysis.get('waitcnt_full_stalls', 0)}",
        f";   Barriers: {isa_analysis.get('barriers', 0)}",
        f";",
        f"; Legend:",
        f";   ⚡ = High throughput operation",
        f";   ⚠️  = High latency operation",
        f";   🚨 = Performance concern (stall/spill)",
        f";   🔒 = Synchronization point",
        f"; ============================================================================",
        "",
    ]
    
    return "\n".join(summary_lines + annotated_lines)
def can_analyze_locally() -> bool:
    """Check if ROCm LLVM tools are available for local analysis.
    
    Returns True if llvm-objdump, llvm-readelf, and clang-offload-bundler are available.
    """
    try:
        _get_binary_path("llvm-objdump")
        _get_binary_path("llvm-readelf")
        _get_binary_path("clang-offload-bundler")
        return True
    except RuntimeError:
        return False
def analyze_code_object_local(co_path: Path) -> dict:
    """Analyze AMD GPU code object (.co file) locally using ROCm LLVM tools.
    
    This function performs the same analysis as the API server but runs locally.
    Requires ROCm LLVM tools (llvm-objdump, llvm-readelf, clang-offload-bundler) to be installed.
    
    Args:
        co_path: Path to the .co file to analyze
        
    Returns:
        Dictionary with analysis results matching API server format:
        {
            "kernel_name": str,
            "architecture": str,
            "vgpr_count": int,
            "sgpr_count": int,
            "agpr_count": int,
            "vgpr_spill_count": int,
            "sgpr_spill_count": int,
            "lds_bytes": int,
            "global_loads": int,
            "global_stores": int,
            "lds_ops": int,
            "mfma_count": int,
            "fma_count": int,
            "packed_ops_count": int,
            "waitcnt_full_stalls": int,
            "barriers": int,
            "isa_text": str,
            "metadata_yaml": str,
            "annotated_isa_text": str,
        }
        
    Raises:
        RuntimeError: If ROCm LLVM tools are not available
        ValueError: If file is not a valid GPU code object
    """
    if not can_analyze_locally():
        raise RuntimeError(
            "ROCm LLVM tools not available for local analysis. "
            "Required: llvm-objdump, llvm-readelf, clang-offload-bundler. "
            "Install ROCm toolkit or use API server for analysis."
        )
    
    with tempfile.TemporaryDirectory() as tmpdir:
        tmpdir_path = Path(tmpdir)
        
        # Write input file
        input_co_path = tmpdir_path / "input.co"
        input_co_path.write_bytes(co_path.read_bytes())
        
        # Try to list bundle targets (returns None for standalone HSA code objects)
        targets = _list_bundle_targets(input_co_path)
        
        if targets is not None:
            # File is an offload bundle - extract GPU code
            gpu_targets = [t for t in targets if "amdgcn" in t]
            if not gpu_targets:
                raise ValueError(f"No GPU target found in bundle. Targets: {targets}")
            
            gpu_target = gpu_targets[0]
            
            gpu_co_path = tmpdir_path / "gpu.co"
            _extract_gpu_code(input_co_path, gpu_target, gpu_co_path)
        else:
            # File might be a standalone GPU code object (not a bundle)
            # Verify it's a valid GPU code object
            if not _is_gpu_code_object(input_co_path):
                raise ValueError("File is not a valid AMD GPU code object or offload bundle")
            gpu_co_path = input_co_path
        
        # Disassemble
        isa_text = _disassemble(gpu_co_path)
        
        metadata_yaml = _get_metadata(gpu_co_path)
        
        meta = _parse_metadata(metadata_yaml)
        
        # Analyze ISA
        isa_analysis = _analyze_isa(isa_text)
        
        # Generate annotated ISA using shared annotation logic
        annotated_isa_text = _annotate_isa(isa_text, meta, isa_analysis)
        
        return {
            "kernel_name": meta["kernel_name"],
            "architecture": meta["architecture"],
            "vgpr_count": meta["vgpr_count"],
            "sgpr_count": meta["sgpr_count"],
            "agpr_count": meta["agpr_count"],
            "vgpr_spill_count": meta["vgpr_spill_count"],
            "sgpr_spill_count": meta["sgpr_spill_count"],
            "lds_bytes": meta["lds_bytes"],
            "global_loads": isa_analysis["global_loads"],
            "global_stores": isa_analysis["global_stores"],
            "lds_ops": isa_analysis["lds_ops"],
            "mfma_count": isa_analysis["mfma_count"],
            "fma_count": isa_analysis["fma_count"],
            "packed_ops_count": isa_analysis["packed_ops_count"],
            "waitcnt_full_stalls": isa_analysis["waitcnt_full_stalls"],
            "barriers": isa_analysis["barriers"],
            "isa_text": isa_text,
            "metadata_yaml": metadata_yaml,
            "annotated_isa_text": annotated_isa_text,
        }
