Conversation
sipma
left a comment
There was a problem hiding this comment.
It looks good to me. Thank you for fixing this.
Ricardo, could you please also have a look at it, and merge it if you think it is okay?
waskyo
left a comment
There was a problem hiding this comment.
First pass: I think I understand the reason for the change and (high-level) what you're doing to fix it, now I'm just trying to make sure the code details match up.
Apologies for the pain.
| self.basicblock.partition_control_flow() | ||
| return self.fragmented_assembly_ast(astree) | ||
|
|
||
| else: |
There was a problem hiding this comment.
consider removing the else since you do early return on the if. If you choose to keep it, then nuke the empty line.
| return self.hl_ast_cc_condition | ||
|
|
||
| def assembly_ast_cc_condition( | ||
| self, astree: "ASTInterface") -> Optional[AST.ASTExpr]: |
There was a problem hiding this comment.
style nit: looking at the code around, the astree param should be in its own line
There was a problem hiding this comment.
...and actually, this style isn't consistent (but I'll change my code to fit the "each param on new line" style. ast_fragment, trampoline_block_instructions, and some other methods have all the params on the same line. I prefer your suggestion though, but won't change them all right now.
| self.ast_cc_condition_prov(astree) | ||
| return self.hl_ast_cc_condition | ||
|
|
||
| def assembly_ast_cc_condition( |
There was a problem hiding this comment.
#til q: what does the cc stand for in the function name?
There was a problem hiding this comment.
cc is "condition code". The ARM instruction's conditional-execution suffix and the NZCV flags it tests.
|
|
||
| def fragmented_ast(self, astree: "ASTInterface") -> AST.ASTStmt: | ||
| def assembly_ast_fragment( | ||
| self, astree: "ASTInterface", frag: "BasicBlockFragment") -> AST.ASTStmt: |
There was a problem hiding this comment.
nit: follow coding style of the existing code. This comment applies to some of the other functions you added (don't want to spam on every instance)
There was a problem hiding this comment.
Fixed here and I reviewed my modifications to check the lines I changed.
| instritems: List[ASTInterfaceInstruction], | ||
| ll: bool = False) -> AST.ASTStmt: | ||
| instrs: List[AST.ASTInstruction] = [] | ||
| for i in instritems: |
There was a problem hiding this comment.
I think this is replacing the original code from def assembly_ast but that one looked at the instructions in sorted order:
for (a, i) in sorted(self.instructions.items(), key=lambda p: p[0]):
Does that difference matter?
There was a problem hiding this comment.
The ordering is actually moved out into the callers, so the ordering has not changed.
| def fragmented_ast(self, astree: "ASTInterface") -> AST.ASTStmt: | ||
| def assembly_ast_fragment( | ||
| self, astree: "ASTInterface", frag: "BasicBlockFragment") -> AST.ASTStmt: | ||
| if frag.is_predicated: |
There was a problem hiding this comment.
A comment here explaining the magic would be pretty useful for future us.
There was a problem hiding this comment.
All of these functions don't have docstrings - I propose that I add that as a separate PR.
| return self.linear_ast(astree, instrs) | ||
|
|
||
| def fragmented_ast(self, astree: "ASTInterface") -> AST.ASTStmt: | ||
| def assembly_ast_fragment( |
There was a problem hiding this comment.
This is not something from your changes, but trying to keep track of the distinction between ast_fragment and fragmented_ast is making my head spin 😵💫
If you're feeling magnanimous, some comments would be amaze 🎅
There was a problem hiding this comment.
Sure, but how about in a separate PR?
| cinstr = theninstrs[0] | ||
| brcond = cinstr.assembly_ast_cc_condition(astree) | ||
| if brcond is None: | ||
| chklogger.logger.warning( |
There was a problem hiding this comment.
rando: Use logger.error. Henny and I have been talking about using error always since we don't have a formal distinction between warning and error.
There was a problem hiding this comment.
Done in this and the other log.warning message.
| 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] |
There was a problem hiding this comment.
rando: what does cinstr stand for?
There was a problem hiding this comment.
cinstr stands for condition instructions.
| brcond, thenstmt, elsestmt, cinstr.iaddr, predicated=instrcount) | ||
| astree.add_stmt_span(ifstmt.locationid, spans) | ||
| return ifstmt | ||
| else: |
There was a problem hiding this comment.
nit: possibly nicer organization for this code since this path is simple:
if not frag.is_predicated:
instrs = [self.get_instruction(i.iaddr) for i in frag.linear]
return self.linear_assembly_ast(astree, instrs)
<predicated handling code from above, no need for else>
...
return ifstmt
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.
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.
5dac642 to
b254851
Compare
For ARM predicated instructions, the high level AST from the lifting is represented as a branch on the instruction predicate. The serialization from the high level to low level representation flattened every block into one instruction sequence so a conditional store was rendered as though it always executes. This resulted in the low-level form of the guard being detached from the rest of the graph (i.e the
expriddid not have a node.)The following is an example three-line function whose guarded store gcc if-converts:
Before, in the low-level AST, the store always happens and the provenance pointed at a guard that was not there:
Five of the six entries resolve to a node. The sixth is the guard where the high-level exprid 18 is node #349, (idx ge 0) (the condition of the high-level if) that maps to low-level exprid 15, which did not have a node in the low-level AST. A consumer that resolves the low-level form of a guard finds nothing, which caused the mram-patcher to raise a KeyError.
After. The predicated run is emitted as a branch, and node #421 carries exprid 15:
(N eq V) is the flag test the ge predicate reads and it is the same expression object the mapping already pointed at.
assembly_astnow partitions a block that has control flow and emits each fragment, mirroring the structure the high-level side already uses (ast -> fragmented_ast -> ast_fragment). A predicated fragment becomes a branch so the node keeps its exprid.