| import subprocess |
| import re |
| import numpy as np |
| from pathlib import Path |
|
|
| class IRFeatureExtractor: |
| FEATURE_NAMES = [ |
| "loop_depth_ratio", |
| "memory_op_ratio", |
| "branch_ratio", |
| "arithmetic_ratio", |
| "function_call_ratio", |
| "phi_node_ratio", |
| "bb_size_normalized", |
| "num_functions_normalized", |
| "call_per_bb_normalized", |
| ] |
|
|
| def extract(self, bc_path: str) -> np.ndarray: |
| ll_text = self._disassemble(bc_path) |
| return self._parse_features(ll_text) |
|
|
| def _disassemble(self, bc_path: str) -> str: |
| result = subprocess.run( |
| ["llvm-dis", bc_path, "-o", "-"], |
| capture_output=True, text=True |
| ) |
| if result.returncode != 0: |
| raise RuntimeError(f"llvm-dis ์คํจ: {result.stderr}") |
| return result.stdout |
|
|
| def _parse_features(self, ll_text: str) -> np.ndarray: |
| lines = ll_text.split("\n") |
| total_instr = 0 |
| mem_ops = 0 |
| branches = 0 |
| arith_ops = 0 |
| call_ops = 0 |
| phi_nodes = 0 |
| bb_sizes = [] |
| current_bb_size = 0 |
| loop_keywords = 0 |
| num_functions = 0 |
|
|
| for line in lines: |
| stripped = line.strip() |
| if not stripped or stripped.startswith(";"): |
| continue |
| |
| if re.match(r"^define ", stripped): |
| num_functions += 1 |
| continue |
| if re.match(r"^\w[\w.]*:$", stripped) or stripped.endswith(":"): |
| if current_bb_size > 0: |
| bb_sizes.append(current_bb_size) |
| current_bb_size = 0 |
| continue |
| if re.search(r"\bcall\b", stripped): |
| call_ops += 1 |
| total_instr += 1 |
| current_bb_size += 1 |
| elif any(op in stripped for op in ["load ", "store ", "getelementptr"]): |
| mem_ops += 1 |
| total_instr += 1 |
| current_bb_size += 1 |
| elif stripped.startswith("br ") or stripped.startswith("switch "): |
| branches += 1 |
| total_instr += 1 |
| current_bb_size += 1 |
| elif stripped.startswith("phi "): |
| phi_nodes += 1 |
| total_instr += 1 |
| current_bb_size += 1 |
| elif any(stripped.startswith(op) for op in |
| ["%", "add ", "sub ", "mul ", "div ", |
| "fadd", "fsub", "fmul", "fdiv", "icmp", "fcmp"]): |
| arith_ops += 1 |
| total_instr += 1 |
| current_bb_size += 1 |
| if "!llvm.loop" in stripped or "loop" in stripped.lower(): |
| loop_keywords += 1 |
|
|
| if current_bb_size > 0: |
| bb_sizes.append(current_bb_size) |
|
|
| safe_total = max(total_instr, 1) |
| num_bbs = max(len(bb_sizes), 1) |
| avg_bb_size = np.mean(bb_sizes) if bb_sizes else 1.0 |
|
|
| raw = np.array([ |
| min(loop_keywords / 10.0, 1.0), |
| mem_ops / safe_total, |
| branches / safe_total, |
| arith_ops / safe_total, |
| call_ops / safe_total, |
| phi_nodes / safe_total, |
| min(avg_bb_size / 50.0, 1.0), |
| min(num_functions / 20.0, 1.0), |
| min(call_ops / num_bbs / 2.0, 1.0), |
| ], dtype=np.float32) |
| return np.clip(raw, 0.0, 1.0) |
|
|
| if __name__ == "__main__": |
| extractor = IRFeatureExtractor() |
| features = extractor.extract("test_loop.bc") |
| print("=== IR ํน์ง ๋ฒกํฐ (9์ฐจ์) ===") |
| for name, val in zip(IRFeatureExtractor.FEATURE_NAMES, features): |
| bar = "โ" * int(val * 20) |
| print(f" {name:<30} {val:.4f} {bar}") |
|
|
| def extract_features(bc_path: str) -> np.ndarray: |
| return IRFeatureExtractor().extract(bc_path) |
|
|