From 8696a78604151f459581a2b1144619fd06aefe51 Mon Sep 17 00:00:00 2001 From: Dan Phung Date: Sat, 12 Sep 2026 16:44:52 -0700 Subject: [PATCH 1/4] emit the low-level guard of a predicated run assembly_ast flattened every block into an instruction sequence, so an if-converted predicated run was rendered as unconditional assigns and the low-level form of its guard was attached to nothing. Since serialization only indexes nodes reachable from the ast start nodes, that expression never reached the exported AST, leaving provenance's expression-mapping pointing at an id no node carries. Partition a block that has control flow and emit each predicated fragment as a branch on the flag condition, mirroring what ast_fragment already does for the high-level AST. assembly_ast_cc_condition returns the same ll_ast_cc_condition object that ast_cc_condition_prov mapped the high-level condition onto, so the emitted node keeps its exprid and the mapping resolves. A fragment with no low-level predicate falls back to the flat sequence with a warning. Also makes the low-level AST stop claiming that a conditional instruction always executes. --- chb/astinterface/ASTInterfaceBasicBlock.py | 74 +++++++++++++++++++-- chb/astinterface/ASTInterfaceInstruction.py | 7 ++ 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/chb/astinterface/ASTInterfaceBasicBlock.py b/chb/astinterface/ASTInterfaceBasicBlock.py index f59181a7..6919fae1 100644 --- a/chb/astinterface/ASTInterfaceBasicBlock.py +++ b/chb/astinterface/ASTInterfaceBasicBlock.py @@ -192,10 +192,16 @@ def ast_switch_condition( ''' def assembly_ast(self, astree: "ASTInterface") -> AST.ASTStmt: - instrs: List[AST.ASTInstruction] = [] - for (a, i) in sorted(self.instructions.items(), key=lambda p: p[0]): - instrs.extend(i.assembly_ast(astree)) - return astree.mk_instr_sequence(instrs) + # A block with control flow of its own is emitted as fragments rather + # than as a single instruction sequence, so that the condition governing + # a fragment is kept in the low-level ast instead of being flattened + # away with it. + if self.basicblock.has_control_flow(): + self.basicblock.partition_control_flow() + return self.fragmented_assembly_ast(astree) + + return self.linear_assembly_ast( + astree, sorted(self.instructions.values(), key = lambda p:p.iaddr)) def ast_fragment( self, astree: "ASTInterface", frag: "BasicBlockFragment") -> AST.ASTStmt: @@ -224,6 +230,34 @@ def ast_fragment( instrs = [self.get_instruction(i.iaddr) for i in frag.linear] return self.linear_ast(astree, instrs) + def assembly_ast_fragment( + self, + astree: "ASTInterface", + frag: "BasicBlockFragment") -> AST.ASTStmt: + if frag.is_predicated: + theninstrs = [self.get_instruction(i.iaddr) for i in frag.thenbranch] + elseinstrs = [self.get_instruction(i.iaddr) for i in frag.elsebranch] + thenstmt = self.linear_assembly_block_ast(astree, theninstrs) + elsestmt = self.linear_assembly_block_ast(astree, elseinstrs) + spans = [(i.iaddr, i.bytestring) for i in theninstrs + elseinstrs] + cinstr = theninstrs[0] + brcond = cinstr.assembly_ast_cc_condition(astree) + if brcond is None: + chklogger.logger.warning( + "No low-level instruction predicate expression found at " + + "address %s", + cinstr.iaddr) + return self.linear_assembly_ast(astree, theninstrs + elseinstrs) + + instrcount = len(theninstrs) + len(elseinstrs) + ifstmt = astree.mk_branch( + brcond, thenstmt, elsestmt, cinstr.iaddr, predicated=instrcount) + astree.add_stmt_span(ifstmt.locationid, spans) + return ifstmt + else: + instrs = [self.get_instruction(i.iaddr) for i in frag.linear] + return self.linear_assembly_ast(astree, instrs) + def fragmented_ast(self, astree: "ASTInterface") -> AST.ASTStmt: if len(self.basicblock.partition) == 0: @@ -237,6 +271,19 @@ def fragmented_ast(self, astree: "ASTInterface") -> AST.ASTStmt: return astree.mk_block(stmts) + def fragmented_assembly_ast(self, astree: "ASTInterface") -> AST.ASTStmt: + + if len(self.basicblock.partition) == 0: + raise UF.CHBError("Error in fragmented assembly ast") + + stmts: List[AST.ASTStmt] = [] + + for (a, bf) in sorted(self.basicblock.partition.items()): + stmt = self.assembly_ast_fragment(astree, bf) + stmts.append(stmt) + + return astree.mk_block(stmts) + def ast(self, astree: "ASTInterface") -> AST.ASTStmt: if self.is_trampoline: return self.trampoline_ast(astree) @@ -269,6 +316,25 @@ def linear_ast( instrs.extend(i.ast(astree)) return astree.mk_instr_sequence(instrs) + def linear_assembly_block_ast( + self, + astree: "ASTInterface", + instritems: List[ASTInterfaceInstruction]) -> AST.ASTStmt: + instrs: List[AST.ASTInstruction] = [] + for i in instritems: + instrs.extend(i.assembly_ast(astree)) + instrseq = astree.mk_instr_sequence(instrs) + return astree.mk_block([instrseq]) + + def linear_assembly_ast( + self, + astree: "ASTInterface", + instritems: List[ASTInterfaceInstruction]) -> AST.ASTStmt: + instrs: List[AST.ASTInstruction] = [] + for i in instritems: + instrs.extend(i.assembly_ast(astree)) + return astree.mk_instr_sequence(instrs) + def trampoline_block_ast( self, role: str, diff --git a/chb/astinterface/ASTInterfaceInstruction.py b/chb/astinterface/ASTInterfaceInstruction.py index 552ff052..c503f374 100644 --- a/chb/astinterface/ASTInterfaceInstruction.py +++ b/chb/astinterface/ASTInterfaceInstruction.py @@ -151,6 +151,13 @@ def ast_cc_condition(self, astree: "ASTInterface") -> Optional[AST.ASTExpr]: self.ast_cc_condition_prov(astree) return self.hl_ast_cc_condition + def assembly_ast_cc_condition( + self, + astree: "ASTInterface") -> Optional[AST.ASTExpr]: + if self.ll_ast_cc_condition is None: + self.ast_cc_condition_prov(astree) + return self.ll_ast_cc_condition + def ast_switch_condition( self, astree: "ASTInterface") -> Optional[AST.ASTExpr]: From b254851ed66140c161203f7d7c2ee6331fd36fc1 Mon Sep 17 00:00:00 2001 From: Dan Phung Date: Tue, 15 Sep 2026 17:01:43 -0700 Subject: [PATCH 2/4] refactor ast emitters The low-level block emitters added with the predicated guard were copies of their high-level counterparts so refactor to give each high-level emitter an ll selector. --- chb/astinterface/ASTInterfaceBasicBlock.py | 48 ++++++++-------------- 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/chb/astinterface/ASTInterfaceBasicBlock.py b/chb/astinterface/ASTInterfaceBasicBlock.py index 6919fae1..8ba94524 100644 --- a/chb/astinterface/ASTInterfaceBasicBlock.py +++ b/chb/astinterface/ASTInterfaceBasicBlock.py @@ -258,31 +258,26 @@ def assembly_ast_fragment( instrs = [self.get_instruction(i.iaddr) for i in frag.linear] return self.linear_assembly_ast(astree, instrs) - def fragmented_ast(self, astree: "ASTInterface") -> AST.ASTStmt: - + def fragmented_ast( + self, + astree: "ASTInterface", + ll: bool = False) -> AST.ASTStmt: if len(self.basicblock.partition) == 0: raise UF.CHBError("Error in fragmented ast") stmts: List[AST.ASTStmt] = [] for (a, bf) in sorted(self.basicblock.partition.items()): - stmt = self.ast_fragment(astree, bf) + if ll: + stmt = self.assembly_ast_fragment(astree, bf) + else: + stmt = self.ast_fragment(astree, bf) stmts.append(stmt) return astree.mk_block(stmts) def fragmented_assembly_ast(self, astree: "ASTInterface") -> AST.ASTStmt: - - if len(self.basicblock.partition) == 0: - raise UF.CHBError("Error in fragmented assembly ast") - - stmts: List[AST.ASTStmt] = [] - - for (a, bf) in sorted(self.basicblock.partition.items()): - stmt = self.assembly_ast_fragment(astree, bf) - stmts.append(stmt) - - return astree.mk_block(stmts) + return self.fragmented_ast(astree, ll=True) def ast(self, astree: "ASTInterface") -> AST.ASTStmt: if self.is_trampoline: @@ -300,40 +295,31 @@ def ast(self, astree: "ASTInterface") -> AST.ASTStmt: def linear_block_ast( self, astree: "ASTInterface", - instritems: List[ASTInterfaceInstruction]) -> AST.ASTStmt: - instrs: List[AST.ASTInstruction] = [] - for i in instritems: - instrs.extend(i.ast(astree)) - instrseq = astree.mk_instr_sequence(instrs) - return astree.mk_block([instrseq]) + instritems: List[ASTInterfaceInstruction], + ll: bool = False) -> AST.ASTStmt: + return astree.mk_block([self.linear_ast(astree, instritems, ll=ll)]) def linear_ast( self, astree: "ASTInterface", - instritems: List[ASTInterfaceInstruction]) -> AST.ASTStmt: + instritems: List[ASTInterfaceInstruction], + ll: bool = False) -> AST.ASTStmt: instrs: List[AST.ASTInstruction] = [] for i in instritems: - instrs.extend(i.ast(astree)) + instrs.extend(i.assembly_ast(astree) if ll else i.ast(astree)) return astree.mk_instr_sequence(instrs) def linear_assembly_block_ast( self, astree: "ASTInterface", instritems: List[ASTInterfaceInstruction]) -> AST.ASTStmt: - instrs: List[AST.ASTInstruction] = [] - for i in instritems: - instrs.extend(i.assembly_ast(astree)) - instrseq = astree.mk_instr_sequence(instrs) - return astree.mk_block([instrseq]) + return self.linear_block_ast(astree, instritems, ll=True) def linear_assembly_ast( self, astree: "ASTInterface", instritems: List[ASTInterfaceInstruction]) -> AST.ASTStmt: - instrs: List[AST.ASTInstruction] = [] - for i in instritems: - instrs.extend(i.assembly_ast(astree)) - return astree.mk_instr_sequence(instrs) + return self.linear_ast(astree, instritems, ll=True) def trampoline_block_ast( self, From 1da87721f0540cd91258589098cefdbde42ef28a Mon Sep 17 00:00:00 2001 From: Dan Phung Date: Fri, 18 Sep 2026 16:57:41 -0700 Subject: [PATCH 3/4] change log.warning to log.error --- chb/astinterface/ASTInterfaceBasicBlock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chb/astinterface/ASTInterfaceBasicBlock.py b/chb/astinterface/ASTInterfaceBasicBlock.py index 8ba94524..eef68afd 100644 --- a/chb/astinterface/ASTInterfaceBasicBlock.py +++ b/chb/astinterface/ASTInterfaceBasicBlock.py @@ -214,7 +214,7 @@ def ast_fragment( cinstr = theninstrs[0] brcond = cinstr.ast_cc_condition(astree) if brcond is None: - chklogger.logger.warning( + chklogger.logger.error( "No instruction predicate expression found at address %s", cinstr.iaddr) brcond = astree.mk_temp_lval_expression() @@ -243,7 +243,7 @@ def assembly_ast_fragment( cinstr = theninstrs[0] brcond = cinstr.assembly_ast_cc_condition(astree) if brcond is None: - chklogger.logger.warning( + chklogger.logger.error( "No low-level instruction predicate expression found at " + "address %s", cinstr.iaddr) From aeb26349f00f84e83631577b024b79d660be9d07 Mon Sep 17 00:00:00 2001 From: Dan Phung Date: Fri, 18 Sep 2026 17:01:21 -0700 Subject: [PATCH 4/4] refactor frag.is_predicated block for early return and less nesting --- chb/astinterface/ASTInterfaceBasicBlock.py | 42 +++++++++++----------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/chb/astinterface/ASTInterfaceBasicBlock.py b/chb/astinterface/ASTInterfaceBasicBlock.py index eef68afd..374a692f 100644 --- a/chb/astinterface/ASTInterfaceBasicBlock.py +++ b/chb/astinterface/ASTInterfaceBasicBlock.py @@ -234,30 +234,30 @@ def assembly_ast_fragment( self, astree: "ASTInterface", frag: "BasicBlockFragment") -> AST.ASTStmt: - if frag.is_predicated: - theninstrs = [self.get_instruction(i.iaddr) for i in frag.thenbranch] - elseinstrs = [self.get_instruction(i.iaddr) for i in frag.elsebranch] - thenstmt = self.linear_assembly_block_ast(astree, theninstrs) - elsestmt = self.linear_assembly_block_ast(astree, elseinstrs) - spans = [(i.iaddr, i.bytestring) for i in theninstrs + elseinstrs] - cinstr = theninstrs[0] - brcond = cinstr.assembly_ast_cc_condition(astree) - if brcond is None: - chklogger.logger.error( - "No low-level instruction predicate expression found at " - + "address %s", - cinstr.iaddr) - return self.linear_assembly_ast(astree, theninstrs + elseinstrs) - - instrcount = len(theninstrs) + len(elseinstrs) - ifstmt = astree.mk_branch( - brcond, thenstmt, elsestmt, cinstr.iaddr, predicated=instrcount) - astree.add_stmt_span(ifstmt.locationid, spans) - return ifstmt - else: + if not frag.is_predicated: instrs = [self.get_instruction(i.iaddr) for i in frag.linear] return self.linear_assembly_ast(astree, instrs) + theninstrs = [self.get_instruction(i.iaddr) for i in frag.thenbranch] + elseinstrs = [self.get_instruction(i.iaddr) for i in frag.elsebranch] + thenstmt = self.linear_assembly_block_ast(astree, theninstrs) + elsestmt = self.linear_assembly_block_ast(astree, elseinstrs) + spans = [(i.iaddr, i.bytestring) for i in theninstrs + elseinstrs] + cinstr = theninstrs[0] + brcond = cinstr.assembly_ast_cc_condition(astree) + if brcond is None: + chklogger.logger.error( + "No low-level instruction predicate expression found at " + + "address %s", + cinstr.iaddr) + return self.linear_assembly_ast(astree, theninstrs + elseinstrs) + + instrcount = len(theninstrs) + len(elseinstrs) + ifstmt = astree.mk_branch( + brcond, thenstmt, elsestmt, cinstr.iaddr, predicated=instrcount) + astree.add_stmt_span(ifstmt.locationid, spans) + return ifstmt + def fragmented_ast( self, astree: "ASTInterface",