Skip to content

Bring main up to stable-backports (carries #148) - #152

Merged
hansfbaier merged 4 commits into
openXC7:mainfrom
gHashTag:carry-148-to-main
Aug 18, 2026
Merged

Bring main up to stable-backports (carries #148)#152
hansfbaier merged 4 commits into
openXC7:mainfrom
gHashTag:carry-148-to-main

Conversation

@gHashTag

Copy link
Copy Markdown

main became the default branch but sits 4 commits behind stable-backports, so the fix from #148 is currently absent from the default branch — a fresh clone does not have it.

main is a strict ancestor of stable-backports (git merge-base --is-ancestor passes, compare API reports ahead_by=0 behind_by=4), so this is a fast-forward. No cherry-picks, no rewritten authorship — the same four objects:

4dd779f5  Hans Baier  Merge pull request #148 from jasonzeng124/fix/lut6-2-muxf-uaf
7d649e0b  Jason Zeng  xilinx: extract self-documenting bools for LUT6_2 split conditions
bfdeaf7c  Jason Zeng  xilinx: only reject BEL-constrained LUT6_2 when both outputs are used
31c8ea77  Jason Zeng  xilinx: fix use-after-free when a LUT6_2 feeds a MUXF7/8/9 tree

Credit is @jasonzeng124's and @hansfbaier's; this PR only moves the branch pointer.

How it arose, so it does not recur

main was cut at 00:37 on 2026-08-18; #148 merged into stable-backports at 02:14, because that PR still targeted the old branch. There are now no open PRs targeting stable-backports — the last two were mine (#151, #120) and I have retargeted them to main. So this is a one-off gap, not an ongoing leak.

Verified before proposing

Built at 4dd779f5 and ran litex-ddr-arty-s7 (deephier branch of demo-projects) on xc7s50csga324-1:

seed result
1 exit 0 — 193271-line FASM
3 exit 0 — 194024
8 exit 0 — 192243

Seeds 3 and 8 are the two that failed the carry site-exit class before #146, so this also confirms #146 and #148 coexist. FASM differs from the pre-#148 output, which is expected: #148 changes when a LUT6_2 may be packed, so the netlist legitimately changes.

What I did not verify: the repo's own regression/ suite — it lives on demo-projects' default branch and my working copy is on the deephier branch. I did not want to claim a gate I had not actually run.

@hansfbaier — you said you are focusing on review and merge, so this is meant to be a one-click item. If you would rather fast-forward main directly, or delete it and rename stable-backports instead, close this; the branch topology is your call and I did not want to guess.

jasonzeng124 and others added 4 commits August 14, 2026 13:37
pack_muxfs() runs before pack_luts() in both the xc7 and Ultrascale
flows. Its constrain_muxf_tree() walks driver.cell across nets with
no cell-type check, so a LUT6_2 feeding a MUXF7/8/9 input gets
constr_parent/constr_children set on it by the mux-tree constraint
walk. split_lut6_2() (previously called from inside pack_luts(),
which runs afterwards) then deletes that exact cell via flush_cells()
without repairing those pointers, leaving a dangling CellInfo* that
the HEAP placer later dereferences -- segfault.

Fix by running split_lut6_2() before pack_muxfs()/pack_carries()/
pack_srls() in both flows, so LUT6_2 no longer exists as a type by
the time any pass that net-walks into driver cells without a type
check gets to run. This closes the whole class rather than adding a
type check to constrain_muxf_tree() alone.

Also:
- flush_cells() now asserts a cell being deleted has no live
  constr_parent/constr_children links, so a future instance of this
  class of bug fails loudly at the deletion site instead of
  segfaulting later, deep in the placer.
- constrain_lut6_2_pairs() no longer overwrites constr_parent on a
  half that was already constrained by an earlier pass (e.g. its own
  mux-tree constraint) -- found via adversarial testing: the same
  LUT6_2 being both 5LUT/6LUT-pairable (constant I5) *and* driving a
  MUXF7 input hit the same class of dangling/desynced-constraint bug
  through a different code path (constrain_lut6_2_pairs unconditionally
  overwriting constr_parent), producing a different placer assertion
  failure. Such a pair is now left unpaired (costs an extra LUT bel,
  same tradeoff already accepted for the non-constant-I5 case) rather
  than corrupting constraint state.
- split_lut6_2() now propagates the original cell's region constraint
  to both halves (a region is just a bbox, so this is safe and cheap
  insurance against silently dropping it).
- split_lut6_2() fails loudly (log_error) instead of silently
  discarding state in two previously-unguarded cases: a LUT6_2 with a
  pre-existing BEL constraint (no defined rule for which physical half
  should inherit a single-bel pin), and a split-half name that
  collides with an existing cell (previously a bare NPNR_ASSERT deep
  in flush_cells()).

Verified against 4 hand-built repro netlists: the original UAF
(LUT6_2.O6 -> MUXF7), a paired constant-I5 LUT6_2 (still costs one
physical LUT), a paired LUT6_2 whose O6 also drives a MUXF7 input, and
the same with O5 driving the MUXF7 (the actual conflicting case for
constrain_lut6_2_pairs -- confirmed by reverting the guard and seeing
a different placer assertion fire). All four now place and route
cleanly.

Not fixed here (documented, left out of scope):
- Non-constant-I5 LUT6_2 still costs 2 LUT bels instead of 1; the
  placer's fracturable-LUT model refuses a 5LUT companion whenever the
  6LUT uses all 6 inputs (arch_place.cc), so closing this needs an
  arch-level change, not a packer-level one.
- A BEL constraint on a pre-split LUT6_2 has no defined per-half
  splitting rule; no live producer of this was found in this codebase,
  so it now fails loudly rather than being silently guessed at.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
split_lut6_2() previously log_error'd on any LUT6_2 carrying a BEL
attribute, regardless of whether O5 was actually used. That's a
regression versus pre-split behaviour: before split_lut6_2() existed,
a BEL-constrained LUT6_2 driving only O6 packed fine (it just aliased
straight onto the LUT6 xform rule, keeping its single port name and
BEL). Independent review caught this by diffing against the parent
commit.

A BEL names one physical resource; splitting is only genuinely
ambiguous when both O5 and O6 are driven, since then there's no rule
for which of the two resulting cells should inherit it. When only one
output is used, exactly one half is ever created, so it can just take
the BEL directly.

Verified: a BEL-constrained, O6-only LUT6_2 now packs/places/routes
cleanly (previously hard errored); a BEL-constrained LUT6_2 driving
both O5 and O6 still errors, as intended.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Addresses review feedback on openXC7#148: name the BEL-ambiguity check, the
split-half name-collision check, the BEL-inherit check, and the two
already-constrained checks instead of inlining them at the call site.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
xilinx: fix use-after-free when a LUT6_2 feeds a MUXF7/8/9 tree
@hansfbaier
hansfbaier merged commit 4dd779f into openXC7:main Aug 18, 2026
3 checks passed
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.

3 participants