Conversation
Two rules that update different fields of one Table Entry both lowered to `ac.table.propose ... mode "replace"` with the complete field set, so `ac-lower-rules` rejected them with "same-field overlap on owner @entries requires explicit priority on every writer endpoint". That forced one logical entry into several state banks -- the problem issue #129 describes. The reverted #134 narrowed the frontend but the firing path could not carry field writes until #141; with the backend in place, this lands the frontend half. `proven_field_write_fields` returns the canonical declaration-ordered field set only when the assigned value is provably the recorded read of that same target updated by `with_fields(...)`, and the write index AST-equals the recorded read index. Every other producer keeps the complete replacement, so an unproven value is never downgraded. The direct spelling `entries[i].field = value` reaches the same recognition. The field-assignment normalization's generated index binding is folded back into the recorded read for explicit `ac.table` owners, keeping the single-indexed-write grammar and the entry-locked result contract. Indexed persistent `list[Struct]` owners narrow at the `ac-lower-variable-state` boundary, which recognizes an `ac.var.with` chain rooted at the same-variable, same-index element read and emits the canonical field set (Decision 0261). `VerifyValueConstraints` stops handing the new shape an exemption meant for declarative writers: the allocation-versus-field exemption now requires both sides to be declarative field writers (`ac.table.write` in field mode and `ac.table.masked_write`), so a rule field proposal keeps competing for arbitration against a declarative allocation. Verified end to end where #134 could not reach: the disjoint-field two-rule module passes freeze, `acir-queue-plan`, `acir-queue-cxxgen` (two FieldMerge), and `acir-queue-pycgen`, and a native gfsim e2e shows both field writes committing against the tick-start image with unwritten fields preserved. Decision 0154 and 0236 record the contracts; gate evidence lives in docs/gates/logs/20260915-issue129-frontend-field-writes/. refs #129 Co-authored-by: Cursor <cursoragent@cursor.com>
25 tasks
This was referenced Sep 16, 2026
Contributor
Author
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Frontend half of #129, on top of the merged backend (#141).
依赖与后继关系
本 PR 已被 #145 取代,请勿单独合并本 PR。
#145 完整吸收了本 PR 的 proven frontend field-write 切片,并进一步支持 module-local Table、普通/
@ac.inlinehelper 的一致 provenance、同一 firing 对同一 Table 的不同字段 schema 多 proposal,以及 GFSim 逐条 footprint 原子批次。#145 当前堆叠在 #144 上,而 #144 又依赖 #142;合并顺序为 #142 → #144 → #145。依赖合并后,#145 将 rebase 到最新
main。本 PR 保留用于历史评审和证据追踪,不应与 #145 分别合入。Background
Two rules that update different fields of one Table Entry both lowered to
ac.table.propose ... mode "replace"with the complete field set, soac-lower-rulesrejected them with "same-field overlap on owner @entries requires explicit priority on every writer endpoint", forcing one logical entry into several state banks.#134 narrowed the frontend but only verified through
ac-lower-rules: the narrowed firing write wasmode "field", which the QueueGraph plan rejected, so it was reverted in #139. #141 then landed the backend: plan and CodeGen accept field-mode firing writes, and PYC commits them in gfsim's order (every FieldMerge against the committed image, then every Replace). This PR re-lands the frontend on top of it and verifies end to end.What lands
ac.table+ explicitwith_fields(revives #134).proven_field_write_fieldsnarrows a rule write tomode "field"with the canonical declaration-ordered field set only when the assigned value is provably the recorded read of that same target updated bywith_fields(...)and the write index AST-equals the recorded read index. Any other producer keeps the complete replacement; recognition is syntactic and fail-closed.Direct spelling
entries[i].field = value. The field-assignment normalization binds the index once and rewrites the target into thewith_fieldsform. For explicitac.tableowners the generated index binding is folded back into the recorded read, keeping the single-indexed-write grammar and the entry-locked result contract; a direct write without a recorded read feeding the rule result keeps the existing fail-closed behavior. For indexed persistentlist[Struct]owners,ac-lower-variable-statenow recognizes anac.var.withchain rooted at the same-variable, same-index element read and emitsmode "field"with the canonical field set (Decision 0261); input-rooted, other-index, and whole-value producers stay complete replacements.Verifier tightening (from #134). The allocation-versus-field exemption in
VerifyValueConstraintsnow requires both sides to be declarative field writers (ac.table.writein field mode,ac.table.masked_write), so a rule field proposal keeps arbitrating against a declarative allocation instead of inheriting the batch exemption (Decision 0156 order does not cover it).Verification (deeper than #134 could go)
#134 verified only through
ac-lower-rulesbecause its checkout had a standalone ACIR build. This PR was verified with the integrated toolchain:python_frontendunittest: 393 OK (1 skipped)check-acirlit: 244/244 (100%) — including the 7 PYC/CodeGen lanes a standalone build misses (pycc/pyc-opt from this checkout's integrated toolchain)ctest: 18/18check-contracts.pyOK;check_decision_status.pyok (rows=261, deferred=0); IR coverage + PYC inventory ledgers regeneratedtest_table_backend: 8/8, including the newtest_rule_field_writers_merge_same_entry_in_native_cpp: two@ac.rulefield writers commit disjoint fields of one Entry in native gfsim simulation, the unwritten field keeps its committed value, other entries untouchedvariable-element-field-write-lowering.mlirpins the list[Struct] narrowing boundary; the revivedrule-field-write-merge.mlirnow also pins freeze → plan → cxxgen → pycgenEvidence:
docs/gates/logs/20260915-issue129-frontend-field-writes/Not in this slice (still #129)
with ac.assert_disjoint(...)and cross-rule dynamic conflict obligations, with their gfsim/PYC/Verilog commit gating.ac.tablewithout a recorded read feeding the rule result (fails closed with ACPY-RULE-008 today, matching the no-return table rule behavior on main).refs #129