Skip to content

typescript rules: the three shapes the profile named — 192s → 99s serial solve on a 1.1M-LOC subject - #1860

Merged
swapnilpaliwal-sd merged 2 commits into
apps/integration-0.1.9from
perf/ts-datalog-rules
Oct 8, 2026
Merged

swapnilpaliwal-sd merged 2 commits into
apps/integration-0.1.9from
perf/ts-datalog-rules

Conversation

@swapnilpaliwal-sd

Copy link
Copy Markdown
Contributor

The TypeScript counterpart of the Java rule work (243a4b2), found the same way: an instrumented souffle solve on a 4,461-file subject, per-rule times, then the three worst shapes restructured with tuple-identical semantics.

shape rule(s) cost before fix
substring cross-join qualified_name_binds + the qname_walk seed 29s + 3.4s for ~800 rows the import qualifier is a dot-prefix of the demanded name, so join it by equality through qname_dot_prefix (the fix one rule above already recorded; two clauses never adopted it)
cross-product anchor name_binds, name_binds_any global clauses 30s anchor on global_decl_visible, which already holds the gated (module, name) pairs
repeated syntactic prefix inside the typing fixpoint 22 rules decomposing a property access inline ~27s property_access_recv materialized once below the fixpoint; the rules start from their recursive delta and probe it by receiver

Gate: every output relation BYTE-identical old vs new, same facts, on both a 1,266-file and a 4,461-file subject. Serial solve 13.3s → 9.4s (−29%) small, 192s → 99s user (−48%) large. tests/run.py --lang typescript: 237/262, the same 25 const-object checks that fail on the base.

Also: run-souffle.sh learns AXIOM_DL_PROFILE=<file> (interpreter solve with per-rule profiling) — how these were found, kept for the next language.

Round 2 is profiling now (next tier: holder_holds_function 6s, elem_read_fn 3s, the zero-tuple qname_walk seed); will push further commits to this branch.

…rial on a 1.1M-LOC subject

The souffle profile on a 4,461-file TypeScript subject put ~85s of a 192s serial solve in three
shapes, each the TypeScript twin of one the Java engine already fixed:

- qualified_name_binds matched the import qualifier by SUBSTRING against every demanded dotted
  name: no index applies, |demanded| x |bindings| pairs with two substr calls each — 29s for 805
  rows, the most expensive rule of the solve. An import's local name holds no dot, so the
  qualifier is exactly one of the name's dot-prefixes, and qname_dot_prefix (written for the
  same lesson one rule above) already enumerates those: joined by equality, probed through the
  index. The qname_walk seed with the same body is joined the same way — two dot-prefixes of one
  demanded name nest, so the substring tests were implied.

- name_binds and name_binds_any enumerated |client modules| x |demanded names| in front of
  global_decl_visible, which already holds exactly the gated (module, name) pairs — 30s between
  the twins. Anchored on the table; the module_decl check stays as an indexed probe.

- a property access was decomposed inline — the PROPERTY_ACCESS scan, its name, its RECEIVER
  child — in 22 rules, most recursive through expr_type(receiver), and semi-naive evaluation
  re-anchored that scan every iteration (expr_type's clauses: all of their time recursive, 17K
  tuples/s). property_access_recv materializes the three atoms once, below the typing fixpoint;
  the 22 rules start from their delta and probe it by receiver.

No semantic change, and the gate proves it: every output relation of the old and new engine is
BYTE-identical on the 1,266-file subject and on the 4,461-file one, same facts, same machine.
Serial solve: 13.3s -> 9.4s (-29%) small, 192s -> 99s user (-48%) large. tests/run.py --lang
typescript: 237 of 262, the same 25 const-object checks that fail on the base.

run-souffle.sh learns AXIOM_DL_PROFILE=<file>: the solve runs once in the souffle interpreter
with per-rule profiling to <file> — how these rules were found, kept for the next language.

Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
@swapnilpaliwal-sd

Copy link
Copy Markdown
Contributor Author

Round 2 attempted two further hoists (the holder_value ⋈ value_branch pair in 12 rules; the two-atom property-access prefix in 14 more) and the byte-identity gate REJECTED them: ~1,000 call-runs edges lost on the 1,266-file subject, reproduced on a 2-file fixture — a chained access typed through a parameter's inline object shape (attachTo.parent.getHash()) degrades known_edge → ambiguous_unknown. The rewrite is conjunction-preserving on paper, both engines are deterministic run-to-run, expr_shape is identical in both, and the loss sits around the ts_field_access_target ↔ ts_objlit_owner recursion (which carries !ts_field_access_is_typed). Either the equivalence argument misses a souffle semantic or souffle 2.5's evaluation is order-sensitive there — worth its own issue; notably the 262-check suite does NOT see the loss, only the byte gate does.

Round 2 is reverted; this PR stays at round 1 (−48% serial solve, byte-identical on both subjects). The remaining profile tail is flat (≤3.6s/rule), so further rule work should start from that fixture, not from more hoists.

…object shape

The round-2 rule hoists lost exactly this edge — attachTo.parent.getHash(), the receiver
typed by a shape's field — degrading it to ambiguous_unknown while all 262 existing checks
held; only the byte-identity A/B gate saw it. The 2-file shape is now a case, so the next
rule reordering in the field-access neighborhood fails a named check instead of a diff.

Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
@swapnilpaliwal-sd
swapnilpaliwal-sd merged commit eb0d952 into apps/integration-0.1.9 Oct 8, 2026
12 checks passed
@swapnilpaliwal-sd
swapnilpaliwal-sd deleted the perf/ts-datalog-rules branch October 8, 2026 02:24
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