mirror of
https://github.com/LLVMParty/llvm-nanobind
synced 2026-06-21 13:43:38 +00:00
Do a bit of API cleanup
This commit is contained in:
@@ -31,9 +31,9 @@ RESET = "\033[0m"
|
||||
|
||||
def section(title: str):
|
||||
"""Print a section header."""
|
||||
print(f"\n{BOLD}{'='*60}{RESET}")
|
||||
print(f"\n{BOLD}{'=' * 60}{RESET}")
|
||||
print(f"{BOLD}{CYAN}{title}{RESET}")
|
||||
print(f"{BOLD}{'='*60}{RESET}\n")
|
||||
print(f"{BOLD}{'=' * 60}{RESET}\n")
|
||||
|
||||
|
||||
def explain(text: str):
|
||||
@@ -153,13 +153,17 @@ def exercise_2_iteration():
|
||||
|
||||
for func in mod.functions:
|
||||
print(f"Function: {BOLD}{func.name}{RESET}")
|
||||
print(f" Parameters: {func.num_params}")
|
||||
print(f" Parameters: {func.param_count}")
|
||||
print(f" Blocks: {len(list(func.basic_blocks))}")
|
||||
|
||||
for bb in func.basic_blocks:
|
||||
print(f"\n Block: {BOLD}{bb.name}{RESET}")
|
||||
for inst in bb.instructions:
|
||||
op_name = inst.opcode.name if hasattr(inst.opcode, 'name') else str(inst.opcode)
|
||||
op_name = (
|
||||
inst.opcode.name
|
||||
if hasattr(inst.opcode, "name")
|
||||
else str(inst.opcode)
|
||||
)
|
||||
is_term = " (TERMINATOR)" if inst.is_terminator_inst else ""
|
||||
print(f" {op_name}: {inst}{is_term}")
|
||||
|
||||
@@ -224,7 +228,10 @@ def exercise_3_simple_transform():
|
||||
op1 = inst.get_operand(1)
|
||||
# Try to detect if it's a constant integer
|
||||
# (This is a simplified check)
|
||||
if hasattr(op1, 'type') and op1.type.kind == llvm.TypeKind.Integer:
|
||||
if (
|
||||
hasattr(op1, "type")
|
||||
and op1.type.kind == llvm.TypeKind.Integer
|
||||
):
|
||||
to_transform.append(inst)
|
||||
|
||||
print(f"{CYAN}Found {len(to_transform)} add instructions{RESET}")
|
||||
@@ -580,9 +587,9 @@ def summary():
|
||||
|
||||
def main():
|
||||
print(f"""
|
||||
{BOLD}{'='*60}
|
||||
{BOLD}{"=" * 60}
|
||||
LLVM-NANOBIND HANDS-ON LEARNING EXERCISES
|
||||
{'='*60}{RESET}
|
||||
{"=" * 60}{RESET}
|
||||
|
||||
This interactive exercise will walk you through:
|
||||
|
||||
|
||||
@@ -21,7 +21,9 @@ BOLD = "\033[1m"
|
||||
RESET = "\033[0m"
|
||||
|
||||
|
||||
def ask(question: str, options: list[str], correct: int, explanation: str) -> bool:
|
||||
def ask(
|
||||
question: str, options: list[str], correct: int, explanation: str
|
||||
) -> bool | None:
|
||||
"""Ask a multiple choice question. Returns True if correct."""
|
||||
print(f"\n{CYAN}{BOLD}Question:{RESET}")
|
||||
print(f" {question}\n")
|
||||
@@ -32,30 +34,32 @@ def ask(question: str, options: list[str], correct: int, explanation: str) -> bo
|
||||
while True:
|
||||
try:
|
||||
answer = input(f"\n{YELLOW}Your answer (1-{len(options)}): {RESET}").strip()
|
||||
if answer.lower() == 'q':
|
||||
if answer.lower() == "q":
|
||||
return None # Signal to quit
|
||||
choice = int(answer)
|
||||
if 1 <= choice <= len(options):
|
||||
break
|
||||
print(f"Please enter a number between 1 and {len(options)}")
|
||||
except ValueError:
|
||||
print(f"Please enter a number between 1 and {len(options)} (or 'q' to quit)")
|
||||
print(
|
||||
f"Please enter a number between 1 and {len(options)} (or 'q' to quit)"
|
||||
)
|
||||
|
||||
if choice == correct:
|
||||
print(f"\n{GREEN}{BOLD}Correct!{RESET}")
|
||||
print(f"{GREEN}{explanation}{RESET}")
|
||||
return True
|
||||
else:
|
||||
print(f"\n{RED}{BOLD}Not quite.{RESET} The answer is: {options[correct-1]}")
|
||||
print(f"\n{RED}{BOLD}Not quite.{RESET} The answer is: {options[correct - 1]}")
|
||||
print(f"{YELLOW}{explanation}{RESET}")
|
||||
return False
|
||||
|
||||
|
||||
def section_intro(title: str, description: str):
|
||||
"""Print a section header."""
|
||||
print(f"\n{'='*60}")
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f"{BOLD}{title}{RESET}")
|
||||
print(f"{'='*60}")
|
||||
print(f"{'=' * 60}")
|
||||
print(f"\n{description}\n")
|
||||
input(f"{YELLOW}Press Enter to begin...{RESET}")
|
||||
|
||||
@@ -71,13 +75,13 @@ LLVM_IR_BASICS = [
|
||||
"A return instruction",
|
||||
"A terminator instruction (br, ret, switch, etc.)",
|
||||
"A call instruction",
|
||||
"Nothing special - it's just the last instruction"
|
||||
"Nothing special - it's just the last instruction",
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Every basic block must end with exactly one terminator instruction. "
|
||||
"This is invariant in LLVM IR. Terminators define control flow: where "
|
||||
"execution can go next. Without a terminator, LLVM wouldn't know what "
|
||||
"happens after the block."
|
||||
"This is invariant in LLVM IR. Terminators define control flow: where "
|
||||
"execution can go next. Without a terminator, LLVM wouldn't know what "
|
||||
"happens after the block.",
|
||||
},
|
||||
{
|
||||
"question": "What does SSA (Static Single Assignment) mean in practice?",
|
||||
@@ -85,14 +89,14 @@ LLVM_IR_BASICS = [
|
||||
"Each variable can only be read once",
|
||||
"Each value can only be assigned once",
|
||||
"Each function can only have one return",
|
||||
"Each block can only have one predecessor"
|
||||
"Each block can only have one predecessor",
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "SSA means every value is defined exactly once. You can't reassign: "
|
||||
"%x = add i32 1, 2; %x = add i32 %x, 3 is INVALID. "
|
||||
"Instead, use new names: %x = ...; %y = add i32 %x, 3. "
|
||||
"This enables powerful optimizations because the compiler knows "
|
||||
"exactly where each value comes from."
|
||||
"%x = add i32 1, 2; %x = add i32 %x, 3 is INVALID. "
|
||||
"Instead, use new names: %x = ...; %y = add i32 %x, 3. "
|
||||
"This enables powerful optimizations because the compiler knows "
|
||||
"exactly where each value comes from.",
|
||||
},
|
||||
{
|
||||
"question": "What's a PHI node used for?",
|
||||
@@ -100,14 +104,14 @@ LLVM_IR_BASICS = [
|
||||
"Representing function parameters",
|
||||
"Selecting a value based on which predecessor block we came from",
|
||||
"Performing floating-point operations",
|
||||
"Calling external functions"
|
||||
"Calling external functions",
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "PHI nodes exist because of SSA + control flow. When two blocks merge, "
|
||||
"and each defines a different version of a value, how do we pick? "
|
||||
"PHI nodes: %result = phi i32 [ %x, %block1 ], [ %y, %block2 ]. "
|
||||
"The value depends on which predecessor we came from. "
|
||||
"They're crucial for loops and conditionals."
|
||||
"and each defines a different version of a value, how do we pick? "
|
||||
"PHI nodes: %result = phi i32 [ %x, %block1 ], [ %y, %block2 ]. "
|
||||
"The value depends on which predecessor we came from. "
|
||||
"They're crucial for loops and conditionals.",
|
||||
},
|
||||
{
|
||||
"question": "Which is NOT a valid LLVM type?",
|
||||
@@ -115,12 +119,12 @@ LLVM_IR_BASICS = [
|
||||
"i1 (1-bit integer, boolean)",
|
||||
"i32 (32-bit integer)",
|
||||
"ptr (opaque pointer)",
|
||||
"str (string type)"
|
||||
"str (string type)",
|
||||
],
|
||||
"correct": 4,
|
||||
"explanation": "LLVM has no native string type. Strings are represented as arrays of i8 "
|
||||
"([13 x i8]) or pointers to i8 (ptr). i1 is valid (booleans), i32 is valid "
|
||||
"(common integer), and ptr is the opaque pointer type in modern LLVM."
|
||||
"([13 x i8]) or pointers to i8 (ptr). i1 is valid (booleans), i32 is valid "
|
||||
"(common integer), and ptr is the opaque pointer type in modern LLVM.",
|
||||
},
|
||||
]
|
||||
|
||||
@@ -131,13 +135,13 @@ BINDING_API = [
|
||||
"Because ptr is more complex",
|
||||
"It's an API inconsistency that should be fixed",
|
||||
"Because ptr takes an address space argument",
|
||||
"Because i32 is a constant, ptr is computed"
|
||||
"Because i32 is a constant, ptr is computed",
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "This is documented as an API inconsistency! The current behavior makes "
|
||||
"sense because ptr() can take an optional address space argument, but the "
|
||||
"inconsistency is confusing. The plan proposes making ptr a property that "
|
||||
"returns the default pointer type, with ptr(addrspace) as an optional method."
|
||||
"sense because ptr() can take an optional address space argument, but the "
|
||||
"inconsistency is confusing. The plan proposes making ptr a property that "
|
||||
"returns the default pointer type, with ptr(addrspace) as an optional method.",
|
||||
},
|
||||
{
|
||||
"question": "To delete an instruction, you must:",
|
||||
@@ -145,13 +149,13 @@ BINDING_API = [
|
||||
"Call inst.delete()",
|
||||
"Call inst.erase_from_parent()",
|
||||
"Call inst.remove_from_parent() then inst.delete_instruction()",
|
||||
"Set inst to None"
|
||||
"Set inst to None",
|
||||
],
|
||||
"correct": 3,
|
||||
"explanation": "The two-step process is error-prone! If you call delete_instruction() "
|
||||
"while the instruction is still in a block, LLVM will assert/crash. "
|
||||
"The plan proposes adding erase_from_parent() that does both atomically, "
|
||||
"matching the C++ API."
|
||||
"while the instruction is still in a block, LLVM will assert/crash. "
|
||||
"The plan proposes adding erase_from_parent() that does both atomically, "
|
||||
"matching the C++ API.",
|
||||
},
|
||||
{
|
||||
"question": "What does replace_all_uses_with() do?",
|
||||
@@ -159,13 +163,13 @@ BINDING_API = [
|
||||
"Replaces the instruction with a new one",
|
||||
"Changes every use of a value to point to a different value",
|
||||
"Replaces the function containing the value",
|
||||
"Copies the value to all users"
|
||||
"Copies the value to all users",
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "RAUW is fundamental to SSA-based transformations. When you create a new "
|
||||
"value that should replace an old one, you use RAUW to update every "
|
||||
"instruction that uses the old value. This is NOT currently bound - "
|
||||
"the passes implement it manually, which is error-prone and slow."
|
||||
"value that should replace an old one, you use RAUW to update every "
|
||||
"instruction that uses the old value. This is NOT currently bound - "
|
||||
"the passes implement it manually, which is error-prone and slow.",
|
||||
},
|
||||
{
|
||||
"question": "What happens if you access a Module after exiting its 'with' block?",
|
||||
@@ -173,13 +177,13 @@ BINDING_API = [
|
||||
"You get garbage data",
|
||||
"Python segfaults",
|
||||
"You get a clean Python exception (due to validity tokens)",
|
||||
"Nothing - the module is copied"
|
||||
"Nothing - the module is copied",
|
||||
],
|
||||
"correct": 3,
|
||||
"explanation": "The bindings use 'validity tokens' to track object lifetime. When the "
|
||||
"context manager exits, the underlying LLVM object is disposed, and "
|
||||
"all Python wrappers are marked invalid. Accessing them raises a clean "
|
||||
"exception instead of the undefined behavior you'd get in C++."
|
||||
"context manager exits, the underlying LLVM object is disposed, and "
|
||||
"all Python wrappers are marked invalid. Accessing them raises a clean "
|
||||
"exception instead of the undefined behavior you'd get in C++.",
|
||||
},
|
||||
{
|
||||
"question": "To iterate over an instruction's operands, you use:",
|
||||
@@ -187,12 +191,12 @@ BINDING_API = [
|
||||
"for op in inst.operands:",
|
||||
"for op in inst.get_operands():",
|
||||
"for i in range(inst.num_operands): op = inst.get_operand(i)",
|
||||
"for op in inst:"
|
||||
"for op in inst:",
|
||||
],
|
||||
"correct": 3,
|
||||
"explanation": "There's no .operands iterator! This is listed as a missing convenience. "
|
||||
"You must use index-based access. The plan proposes adding an operands "
|
||||
"property that returns an iterator for Pythonic access."
|
||||
"You must use index-based access. The plan proposes adding an operands "
|
||||
"property that returns an iterator for Pythonic access.",
|
||||
},
|
||||
]
|
||||
|
||||
@@ -203,13 +207,13 @@ OBFUSCATION = [
|
||||
"XOR always equals subtraction",
|
||||
"It exploits the relationship between arithmetic and bitwise operations in two's complement",
|
||||
"It's an approximation that's close enough",
|
||||
"The extra operations cancel out"
|
||||
"The extra operations cancel out",
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "In two's complement representation, there are deep connections between "
|
||||
"arithmetic and bitwise operations. The identity is exact for all inputs "
|
||||
"in fixed-width integers. The obfuscation works because decompilers "
|
||||
"pattern-match 'sub' to '-', but don't recognize this equivalent form."
|
||||
"arithmetic and bitwise operations. The identity is exact for all inputs "
|
||||
"in fixed-width integers. The obfuscation works because decompilers "
|
||||
"pattern-match 'sub' to '-', but don't recognize this equivalent form.",
|
||||
},
|
||||
{
|
||||
"question": "Control flow flattening hides the original CFG by:",
|
||||
@@ -217,13 +221,13 @@ OBFUSCATION = [
|
||||
"Encrypting all instructions",
|
||||
"Introducing a state machine dispatcher that controls block execution",
|
||||
"Removing all branches",
|
||||
"Inlining all functions"
|
||||
"Inlining all functions",
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "CFF creates a dispatcher that reads a state variable and branches to "
|
||||
"the appropriate block. Each block updates the state and jumps back to "
|
||||
"the dispatcher. The original 'if A then B else C' structure becomes "
|
||||
"'switch(state) { ... }' - much harder to analyze."
|
||||
"the appropriate block. Each block updates the state and jumps back to "
|
||||
"the dispatcher. The original 'if A then B else C' structure becomes "
|
||||
"'switch(state) { ... }' - much harder to analyze.",
|
||||
},
|
||||
{
|
||||
"question": "Why does the CFF pass demote PHI nodes to stack variables?",
|
||||
@@ -231,13 +235,13 @@ OBFUSCATION = [
|
||||
"PHI nodes are too slow",
|
||||
"PHI nodes encode predecessor information that's lost after flattening",
|
||||
"The LLVM API doesn't support PHI nodes",
|
||||
"Stack variables are more secure"
|
||||
"Stack variables are more secure",
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "PHI nodes say 'if we came from block1, use %x; if from block2, use %y'. "
|
||||
"After flattening, we always come from the dispatcher! The predecessor "
|
||||
"information is meaningless. By converting to explicit memory operations, "
|
||||
"we preserve the semantics without relying on control flow."
|
||||
"After flattening, we always come from the dispatcher! The predecessor "
|
||||
"information is meaningless. By converting to explicit memory operations, "
|
||||
"we preserve the semantics without relying on control flow.",
|
||||
},
|
||||
{
|
||||
"question": "The string encryption pass was abandoned because:",
|
||||
@@ -245,13 +249,13 @@ OBFUSCATION = [
|
||||
"Strings can't be encrypted",
|
||||
"The bindings encode strings as UTF-8, corrupting bytes > 127",
|
||||
"LLVM doesn't support string constants",
|
||||
"It was too slow"
|
||||
"It was too slow",
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "const_string() and const_data_array() pass strings through UTF-8 encoding. "
|
||||
"Encrypted bytes often exceed 127, which expand to multi-byte UTF-8 "
|
||||
"sequences. The resulting array is larger than expected, breaking the "
|
||||
"decryption logic. This is listed as a critical blocker in the plan."
|
||||
"Encrypted bytes often exceed 127, which expand to multi-byte UTF-8 "
|
||||
"sequences. The resulting array is larger than expected, breaking the "
|
||||
"decryption logic. This is listed as a critical blocker in the plan.",
|
||||
},
|
||||
]
|
||||
|
||||
@@ -262,13 +266,13 @@ CRITIQUE = [
|
||||
"Adding documentation for exceptions",
|
||||
"Making ptr a property instead of method",
|
||||
"Binding LLVMReplaceAllUsesWith",
|
||||
"Adding an .operands iterator"
|
||||
"Adding an .operands iterator",
|
||||
],
|
||||
"correct": 3,
|
||||
"explanation": "The plan categorizes issues by priority. Priority 1 (Critical Blockers) "
|
||||
"includes RAUW, erase_from_parent, split_basic_block, and raw bytes support. "
|
||||
"These block real use cases. API consistency issues (like ptr()) are P2, "
|
||||
"conveniences are P3, documentation is P4."
|
||||
"includes RAUW, erase_from_parent, split_basic_block, and raw bytes support. "
|
||||
"These block real use cases. API consistency issues (like ptr()) are P2, "
|
||||
"conveniences are P3, documentation is P4.",
|
||||
},
|
||||
{
|
||||
"question": "The porting guide rates the API '7/10 for code generation, 5/10 for transforms'. Why the difference?",
|
||||
@@ -276,13 +280,13 @@ CRITIQUE = [
|
||||
"Transforms are inherently harder",
|
||||
"The Builder API is good, but operations like RAUW and block splitting are missing",
|
||||
"Python is slow for transforms",
|
||||
"The documentation is better for code generation"
|
||||
"The documentation is better for code generation",
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Code generation (creating new IR) mainly uses the Builder, which is "
|
||||
"well-designed (add, sub, br, etc.). Transforms (modifying existing IR) "
|
||||
"need operations like replace_all_uses_with, erase_from_parent, split_block. "
|
||||
"These are missing or cumbersome, making transform work painful."
|
||||
"well-designed (add, sub, br, etc.). Transforms (modifying existing IR) "
|
||||
"need operations like replace_all_uses_with, erase_from_parent, split_block. "
|
||||
"These are missing or cumbersome, making transform work painful.",
|
||||
},
|
||||
{
|
||||
"question": "When reviewing API design, 'pit of success' means:",
|
||||
@@ -290,13 +294,13 @@ CRITIQUE = [
|
||||
"The API should fail loudly on errors",
|
||||
"The easy/natural path should be the correct path",
|
||||
"The API should have extensive documentation",
|
||||
"The API should be minimal"
|
||||
"The API should be minimal",
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "A 'pit of success' API design makes it hard to do the wrong thing. "
|
||||
"The current two-step instruction deletion violates this - the natural "
|
||||
"thing (just call delete) crashes. A good API would make the safe path "
|
||||
"the obvious path: inst.erase_from_parent() does everything correctly."
|
||||
"The current two-step instruction deletion violates this - the natural "
|
||||
"thing (just call delete) crashes. A good API would make the safe path "
|
||||
"the obvious path: inst.erase_from_parent() does everything correctly.",
|
||||
},
|
||||
]
|
||||
|
||||
@@ -322,9 +326,9 @@ def run_section(title: str, description: str, questions: list[dict]) -> tuple[in
|
||||
|
||||
def main():
|
||||
print(f"""
|
||||
{BOLD}{'='*60}
|
||||
{BOLD}{"=" * 60}
|
||||
LLVM-NANOBIND SELF-ASSESSMENT QUIZ
|
||||
{'='*60}{RESET}
|
||||
{"=" * 60}{RESET}
|
||||
|
||||
This quiz tests your understanding of the llvm-nanobind project
|
||||
and its transformation API. It's designed to:
|
||||
@@ -352,7 +356,7 @@ Sections:
|
||||
"Section 1: LLVM IR Fundamentals",
|
||||
"These questions test your understanding of LLVM's intermediate representation.\n"
|
||||
"This knowledge is essential for understanding what the bindings expose.",
|
||||
LLVM_IR_BASICS
|
||||
LLVM_IR_BASICS,
|
||||
)
|
||||
results.append(("LLVM IR Basics", c, t))
|
||||
|
||||
@@ -364,7 +368,7 @@ Sections:
|
||||
"Section 2: The Python Bindings API",
|
||||
"These questions test your understanding of the llvm-nanobind API design,\n"
|
||||
"including its strengths, weaknesses, and idiosyncrasies.",
|
||||
BINDING_API
|
||||
BINDING_API,
|
||||
)
|
||||
results.append(("Binding API", c, t))
|
||||
|
||||
@@ -376,7 +380,7 @@ Sections:
|
||||
"Section 3: Obfuscation Passes",
|
||||
"These questions test your understanding of the MBA and CFF obfuscation\n"
|
||||
"passes, which serve as case studies for the transformation API.",
|
||||
OBFUSCATION
|
||||
OBFUSCATION,
|
||||
)
|
||||
results.append(("Obfuscation", c, t))
|
||||
|
||||
@@ -388,14 +392,14 @@ Sections:
|
||||
"Section 4: Critical Evaluation",
|
||||
"These questions test your ability to critically evaluate the work,\n"
|
||||
"including understanding priorities and trade-offs.",
|
||||
CRITIQUE
|
||||
CRITIQUE,
|
||||
)
|
||||
results.append(("Critical Evaluation", c, t))
|
||||
|
||||
# Final results
|
||||
print(f"\n\n{'='*60}")
|
||||
print(f"\n\n{'=' * 60}")
|
||||
print(f"{BOLD}FINAL RESULTS{RESET}")
|
||||
print(f"{'='*60}\n")
|
||||
print(f"{'=' * 60}\n")
|
||||
|
||||
total_correct = 0
|
||||
total_questions = 0
|
||||
@@ -408,7 +412,9 @@ Sections:
|
||||
total_correct += c
|
||||
total_questions += t
|
||||
|
||||
print(f"\n {'OVERALL':25} {total_correct}/{total_questions} ({total_correct/total_questions*100:.0f}%)")
|
||||
print(
|
||||
f"\n {'OVERALL':25} {total_correct}/{total_questions} ({total_correct / total_questions * 100:.0f}%)"
|
||||
)
|
||||
|
||||
# Recommendations
|
||||
print(f"\n{BOLD}Recommendations:{RESET}")
|
||||
@@ -427,7 +433,9 @@ Sections:
|
||||
if total_correct / total_questions >= 0.8:
|
||||
print(f"\n{GREEN}You're ready to review and critique the work!{RESET}")
|
||||
else:
|
||||
print(f"\n{YELLOW}Consider reviewing the learning guide before diving into code review.{RESET}")
|
||||
print(
|
||||
f"\n{YELLOW}Consider reviewing the learning guide before diving into code review.{RESET}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+12
-23
@@ -2,14 +2,6 @@ import argparse
|
||||
import llvm
|
||||
|
||||
|
||||
def first_non_phi(block: llvm.BasicBlock):
|
||||
instr = block.first_instruction
|
||||
while instr is not None and instr.opcode == llvm.Opcode.PHI:
|
||||
instr = instr.next_instruction
|
||||
assert instr is not None, "bad"
|
||||
return instr
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser("bc-profile")
|
||||
parser.add_argument("ir_in", help="Input LLVM IR to profile")
|
||||
@@ -40,39 +32,36 @@ def main():
|
||||
|
||||
print(f"start.type: {start_fn.type}, function_type: {start_fn.function_type}")
|
||||
|
||||
with first_non_phi(main_entry).create_builder() as builder:
|
||||
# TODO: if you pass a name here LLVM will assert (should be graceful)
|
||||
builder.call(start_fn.function_type, start_fn, [])
|
||||
first_inst = main_entry.first_non_phi
|
||||
assert first_inst is not None, "empty main entry block"
|
||||
with first_inst.create_builder() as builder:
|
||||
builder.call(start_fn, [])
|
||||
|
||||
for main_block in main_fn.basic_blocks:
|
||||
terminator = main_block.terminator
|
||||
if terminator.opcode == llvm.Opcode.Ret:
|
||||
print(f"return block: '{main_block.name}'") # TODO: how do we print the number if there is no name?
|
||||
print(main_block) # TODO: this should print the block bitcode?
|
||||
print(f"return block: '{main_block.name}'")
|
||||
print(main_block)
|
||||
with terminator.create_builder() as builder:
|
||||
builder.call(stop_fn.function_type, stop_fn, [])
|
||||
builder.call(stop_fn, [])
|
||||
|
||||
for function in mod.functions:
|
||||
if function.name == "main":
|
||||
print("skipping main")
|
||||
continue
|
||||
# TODO: is_ is not a good prefix for vscode discoverability
|
||||
if function.is_declaration:
|
||||
print(f"skipping declaration: {function.name}")
|
||||
continue
|
||||
print(f"instrumenting function: {function.name}")
|
||||
name_const = llvm.const_string(ctx, function.name)
|
||||
# TODO: I guess we need to promote this to a pointer?
|
||||
name_const = ctx.const_string(function.name)
|
||||
print(f"{name_const.value_kind=}")
|
||||
# TODO: should this be optional at all, maybe throw instead?
|
||||
entry_block = function.entry_block
|
||||
print("instrumenting entry block")
|
||||
assert entry_block is not None, "no entry block (bad)"
|
||||
with first_non_phi(entry_block).create_builder() as builder:
|
||||
# TODO: varargs instead?
|
||||
# TODO: this hard crashes
|
||||
builder.call(enter_fn.function_type, enter_fn, [name_const])
|
||||
|
||||
first_inst = entry_block.first_non_phi
|
||||
assert first_inst is not None, "empty entry block"
|
||||
with first_inst.create_builder() as builder:
|
||||
builder.call(enter_fn, [name_const])
|
||||
|
||||
with open(args.ir_out, "w", encoding="utf-8") as f:
|
||||
f.write(str(mod))
|
||||
|
||||
@@ -17,16 +17,12 @@ def main():
|
||||
builder.ret(zero)
|
||||
print(func)
|
||||
# The add instruction is used in the xor instruction twice, so we get 2 uses
|
||||
# TODO: this is a gap in the LLVM C API because we cannot get the operand index
|
||||
# and we would have to manually iterate the operands (while tracking the previous
|
||||
# operand index) to find out which operand a use corresponds to. Searching for the
|
||||
# use.user in the operands would not work if the same value is used multiple times.
|
||||
# Most likely the approach here is to implement this bookkeeping in llvm-nanobind itself.
|
||||
print(f"Uses of add:")
|
||||
# Each use knows its operand index within the user instruction
|
||||
print("Uses of add:")
|
||||
for i, use in enumerate(add.uses):
|
||||
print(f"[{i}] {use.user}")
|
||||
print(f"[{i}] operand {use.operand_index} of {use.user}")
|
||||
# But only one user, the xor instruction itself
|
||||
print(f"Users of add:")
|
||||
print("Users of add:")
|
||||
for i, user in enumerate(add.users):
|
||||
print(f"[{i}] User: {user}")
|
||||
|
||||
|
||||
+4
-4
@@ -742,8 +742,8 @@ class FunCloner:
|
||||
catch_pad = self.clone_value(src.get_operand(0))
|
||||
unwind_dest = src.unwind_dest
|
||||
unwind = self.declare_bb(unwind_dest) if unwind_dest else None
|
||||
# Note: cleanup_ret accepts None for unwind_bb (stub is incorrect)
|
||||
dst = builder.cleanup_ret(catch_pad, unwind) # type: ignore[arg-type]
|
||||
# Note: cleanup_ret accepts None for unwind_bb
|
||||
dst = builder.cleanup_ret(catch_pad, unwind)
|
||||
|
||||
elif op == llvm.Opcode.CatchRet:
|
||||
catch_pad = self.clone_value(src.get_operand(0))
|
||||
@@ -771,8 +771,8 @@ class FunCloner:
|
||||
unwind_dest = src.unwind_dest
|
||||
unwind_bb = self.declare_bb(unwind_dest) if unwind_dest else None
|
||||
num_handlers = src.num_handlers
|
||||
# Note: catch_switch accepts None for unwind_bb (stub is incorrect)
|
||||
dst = builder.catch_switch(parent_pad, unwind_bb, num_handlers, name) # type: ignore[arg-type]
|
||||
# Note: catch_switch accepts None for unwind_bb
|
||||
dst = builder.catch_switch(parent_pad, unwind_bb, num_handlers, name)
|
||||
if num_handlers > 0:
|
||||
handlers = src.handlers
|
||||
for h in handlers:
|
||||
|
||||
+778
-32
File diff suppressed because it is too large
Load Diff
@@ -78,7 +78,7 @@ def main():
|
||||
# Function 5: Function with fastcc calling convention
|
||||
fastcc_ty = ctx.types.function(i32, [i32], vararg=False)
|
||||
fastcc_fn = mod.add_function("fastcc_func", fastcc_ty)
|
||||
fastcc_fn.calling_conv = llvm.CallConv.Fast.value
|
||||
fastcc_fn.calling_conv = llvm.CallConv.Fast
|
||||
|
||||
# Function 6: Will be deleted
|
||||
delete_ty = ctx.types.function(void_ty, [], vararg=False)
|
||||
|
||||
@@ -83,7 +83,6 @@ def demote_phi_to_stack(func: llvm.Function) -> None:
|
||||
4. Replace uses of PHI with the load
|
||||
5. Delete the PHI
|
||||
"""
|
||||
ctx = func.context
|
||||
entry_bb = list(func.basic_blocks)[0]
|
||||
|
||||
# Collect all PHI nodes first
|
||||
@@ -126,23 +125,10 @@ def demote_phi_to_stack(func: llvm.Function) -> None:
|
||||
load = builder.load(phi_type, alloca, phi.name)
|
||||
|
||||
# Replace uses of PHI with the load
|
||||
replace_all_uses_with(phi, load)
|
||||
phi.replace_all_uses_with(load)
|
||||
|
||||
# Delete the PHI
|
||||
phi.remove_from_parent()
|
||||
phi.delete_instruction()
|
||||
|
||||
|
||||
def replace_all_uses_with(old_value: llvm.Value, new_value: llvm.Value) -> None:
|
||||
"""Replace all uses of old_value with new_value."""
|
||||
uses_to_replace = []
|
||||
for use in old_value.uses:
|
||||
uses_to_replace.append(use.user)
|
||||
|
||||
for user in uses_to_replace:
|
||||
for i in range(user.num_operands):
|
||||
if user.get_operand(i) == old_value:
|
||||
user.set_operand(i, new_value)
|
||||
phi.erase_from_parent()
|
||||
|
||||
|
||||
def flatten_function(func: llvm.Function, use_globals: bool, shuffle: bool) -> None:
|
||||
@@ -160,7 +146,6 @@ def flatten_function(func: llvm.Function, use_globals: bool, shuffle: bool) -> N
|
||||
|
||||
# Use i32 for state values (simpler and more compatible)
|
||||
int_ty = ctx.types.i32
|
||||
i32_ty = ctx.types.i32
|
||||
|
||||
entry_bb = blocks[0]
|
||||
original_blocks = blocks[1:] # Blocks to flatten (excluding entry)
|
||||
@@ -263,8 +248,7 @@ def flatten_function(func: llvm.Function, use_globals: bool, shuffle: bool) -> N
|
||||
int_ty.constant(block_state_map[target]), state_var
|
||||
)
|
||||
builder.br(dispatch_bb)
|
||||
terminator.remove_from_parent()
|
||||
terminator.delete_instruction()
|
||||
terminator.erase_from_parent()
|
||||
|
||||
elif len(successors) == 2:
|
||||
# Conditional branch
|
||||
@@ -295,8 +279,7 @@ def flatten_function(func: llvm.Function, use_globals: bool, shuffle: bool) -> N
|
||||
builder.position_before(terminator)
|
||||
builder.cond_br(condition, true_state_bb, false_state_bb)
|
||||
|
||||
terminator.remove_from_parent()
|
||||
terminator.delete_instruction()
|
||||
terminator.erase_from_parent()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -24,25 +24,9 @@ Options:
|
||||
import argparse
|
||||
import random
|
||||
import sys
|
||||
from typing import Callable
|
||||
|
||||
import llvm
|
||||
|
||||
|
||||
def replace_all_uses_with(old_value: llvm.Value, new_value: llvm.Value) -> None:
|
||||
"""Replace all uses of old_value with new_value."""
|
||||
# Collect uses first to avoid modifying while iterating
|
||||
uses_to_replace = []
|
||||
for use in old_value.uses:
|
||||
uses_to_replace.append((use.user, use))
|
||||
|
||||
# Replace each use
|
||||
for user, use in uses_to_replace:
|
||||
for i in range(user.num_operands):
|
||||
if user.get_operand(i) == old_value:
|
||||
user.set_operand(i, new_value)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# MBA Substitution Functions
|
||||
# =============================================================================
|
||||
@@ -300,9 +284,8 @@ def run_on_basic_block(bb: llvm.BasicBlock) -> None:
|
||||
replacement = obfuscator(builder, a, b, inst.name)
|
||||
|
||||
# Replace uses and remove original
|
||||
replace_all_uses_with(inst, replacement)
|
||||
inst.remove_from_parent()
|
||||
inst.delete_instruction()
|
||||
inst.replace_all_uses_with(replacement)
|
||||
inst.erase_from_parent()
|
||||
|
||||
|
||||
def run_on_function(func: llvm.Function, iterations: int) -> None:
|
||||
|
||||
@@ -95,14 +95,14 @@ def transform_branch(
|
||||
idx0 = builder.gep(
|
||||
array_ty, jump_table, [i32_ty.constant(0), i32_ty.constant(0)], "sibr.slot0"
|
||||
)
|
||||
false_addr = llvm.block_address(func, false_bb)
|
||||
false_addr = func.block_address(false_bb)
|
||||
builder.store(false_addr, idx0)
|
||||
|
||||
# GEP to table[1] and store true target
|
||||
idx1 = builder.gep(
|
||||
array_ty, jump_table, [i32_ty.constant(0), i32_ty.constant(1)], "sibr.slot1"
|
||||
)
|
||||
true_addr = llvm.block_address(func, true_bb)
|
||||
true_addr = func.block_address(true_bb)
|
||||
builder.store(true_addr, idx1)
|
||||
|
||||
# Get condition and invert it
|
||||
@@ -134,7 +134,7 @@ def transform_branch(
|
||||
idx0 = builder.gep(
|
||||
array_ty, jump_table, [i32_ty.constant(0), i32_ty.constant(0)], "sibr.slot0"
|
||||
)
|
||||
target_addr = llvm.block_address(func, target_bb)
|
||||
target_addr = func.block_address(target_bb)
|
||||
builder.store(target_addr, idx0)
|
||||
|
||||
# Load from table[0]
|
||||
@@ -145,8 +145,7 @@ def transform_branch(
|
||||
indir_br.add_destination(target_bb)
|
||||
|
||||
# Remove original branch
|
||||
branch.remove_from_parent()
|
||||
branch.delete_instruction()
|
||||
branch.erase_from_parent()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
Reference in New Issue
Block a user