Skip to content

Emit the low-level guard of a predicated run - #295

Draft
leftbyte wants to merge 2 commits into
static-analysis-engineering:masterfrom
leftbyte:dphung/link-dangling-entry
Draft

leftbyte wants to merge 2 commits into
static-analysis-engineering:masterfrom
leftbyte:dphung/link-dangling-entry

Conversation

@leftbyte

@leftbyte leftbyte commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

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 exprid did not have a node.)

The following is an example three-line function whose guarded store gcc if-converts:

void set_channel(char *dst, int idx, char v, unsigned *nwrites) {
    if (idx >= 0)
        dst[idx] = v;          /* one predicated instruction */
    (*nwrites)++;
}

1074c:  cmp    r1, #0
10750:  strbge r2, [r0, r1]    <- executes only when the guard holds
10754:  ldr    r2, [r3]

Before, in the low-level AST, the store always happens and the provenance pointed at a guard that was not there:

#450  block
  #449  block
    #409  label
    #447  instrs
      #413  assign  ignored = (R1 minus 0)                 @ 0x1074c
      #424  assign  *((R0 plus (R1 lsl 0))) = R2           @ 0x10750   <- unconditional
      #432  assign  R2 = *((R3 plus 0))                    @ 0x10754
      #437  assign  R2 = (R2 plus 1)                       @ 0x10758
      #445  assign  *((R3 plus 0)) = R2                    @ 0x1075c
      #446  nop                                            @ 0x10760
    #448  return

expression-mapping: {'3': 2, '12': 4, '18': 15, '26': 21, '38': 28, '47': 39}
  hl exprid 3   -> ll exprid 2   |  #382 idx                 -> #412 (R1 minus 0)
  hl exprid 12  -> ll exprid 4   |  #358 v                   -> #423 R2
  hl exprid 18  -> ll exprid 15  |  #349 (idx ge 0)          -> NO NODE CARRIES EXPRID 15
  hl exprid 26  -> ll exprid 21  |  #395 *(nwrites)          -> #431 *((R3 plus 0))
  hl exprid 38  -> ll exprid 28  |  #404 (*(nwrites) plus 1) -> #436 (R2 plus 1)
  hl exprid 47  -> ll exprid 39  |  #373 (*(nwrites) plus 1) -> #444 R2

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:

#463  block
  #462  block
    #409  label
    #460  block
      #414  instrs
        #413  assign  ignored = (R1 minus 0)               @ 0x1074c
      #436  if (N eq V)  [cond node #421 exprid=15]  predicated=1   @ 0x10750
        #434  block
          #433  instrs
            #432  assign  *((R0 plus (R1 lsl 0))) = R2     @ 0x10750
        #435  block
      #459  instrs
        #444  assign  R2 = *((R3 plus 0))                  @ 0x10754
        ...
    #461  return

hl exprid 18  -> ll exprid 15  |  #349 (idx ge 0)          -> #421 (N eq V)

(N eq V) is the flag test the ge predicate reads and it is the same expression object the mapping already pointed at. assembly_ast now 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.

Dan Phung added 2 commits September 15, 2026 16:27
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant