Skip to content

fix(codegen): stamp the canonical x86_64 heap magic through one shared helper (#482)#491

Open
mirchaemanuel wants to merge 1 commit into
illegalstudio:mainfrom
mirchaemanuel:fix/482-canonical-heap-magic
Open

fix(codegen): stamp the canonical x86_64 heap magic through one shared helper (#482)#491
mirchaemanuel wants to merge 1 commit into
illegalstudio:mainfrom
mirchaemanuel:fix/482-canonical-heap-magic

Conversation

@mirchaemanuel

Copy link
Copy Markdown
Contributor

Summary

Fixes #482. Twelve emitters hand-typed a transposed x86_64 heap magic (0x4548504c instead of the canonical 0x454C5048, ASCII ELPH) in their kind-6 throwable stamps: the runtime-raised ValueError/TypeError/LogicException/Error paths, JSON throw errors, SPL fixed-array / doubly-linked-list exceptions, the hash builtin error path, and static-property/math error objects. The x86_64 refcount and free helpers gate on the canonical magic, so every refcount operation on those objects was a silent no-op on linux-x86_64.

Fix

  • One shared, documented helper next to the sentinel constants: codegen_support::sentinels::x86_64_heap_kind_word(kind) — all 12 stamps now go through it (the emitted assembly changes only in the magic value).
  • The constant's layout is unit-tested, and a repository lint test scans src/ so a hand-typed transposed literal can never come back — the class of typo is closed, not just the instances (full disclosure: two of the twelve were mine, faithfully copied from the pre-existing ValueError emitter, which is exactly why a shared constant was the right fix).

Behavioral note

Neutral today: caught-exception releases land with the #448 fix in PR #477, and until then both increfs and decrefs on these objects were no-ops alike. Once #477 merges, throwables from these paths become genuinely freeable on x86_64, matching ARM64 (whose release path has no magic/kind gate). The two changes are independent and safe to merge in either order.

Verification

Suites green: 1614 codegen (enums/exceptions/spl/json/oop/types/static — every touched emitter's surface) + 993 error tests; sentinels unit + lint tests included; asm comment alignment verified on all rewritten lines.

…d helper

Fixes illegalstudio#482. Twelve emitters hand-typed a transposed heap magic
(0x4548504c vs the canonical 0x454C5048 'ELPH') in their kind-6 throwable
stamps: the runtime-raised ValueError/TypeError/LogicException/Error paths,
JSON throw errors, SPL fixed-array/doubly-linked-list exceptions, the hash
builtin error path, and static-property/math error objects. The x86_64
refcount and free helpers gate on the canonical magic, so every refcount
operation on those objects was a silent no-op on linux-x86_64.

All kind-word stamps now go through crate::codegen_support::sentinels::
x86_64_heap_kind_word(kind), documented next to the sentinel constants; the
constant's layout is unit-tested and a repository lint test scans src/ so the
hand-typed transposed literal cannot come back.

Behavior today is neutral (caught-exception releases land with the illegalstudio#448 fix
in PR illegalstudio#477; until then increfs and decrefs on these objects were both no-ops).
Once illegalstudio#477 merges, throwables from these paths become genuinely freeable on
x86_64 like on ARM64.

Suites green: 1614 codegen (enums/exceptions/spl/json/oop/types/static) +
993 error tests; sentinels unit + lint tests included.
nahime0 pushed a commit that referenced this pull request Jul 10, 2026
…type check)

An interface-typed constructor-promoted property (or param) with a concrete
default — `public ComponentResolverInterface $r = new NullComponentResolver()` —
was rejected: "Method parameter $r expects Object(I), got Object(N)". The default
is validated in the purely-syntactic schema pass, where the class/interface table
isn't populated yet, so type_accepts's class_implements_interface spuriously
returns false. Accept an object default for an object-typed param there (mirroring
the existing enum-case leniency); a genuine mismatch still surfaces at the call
site. Byte-parity regression test. AIC epic #483 / EC-8 (#491), the object-param
sub-part (~14 gaps: ward-forms AlteredForm/Field/... interface-typed promoted props).

(cherry picked from commit db17c17)
nahime0 pushed a commit that referenced this pull request Jul 10, 2026
…throw-guard

`if ($value === false) { throw; } return $value;` (ward-http StreamGuards::requireInt
and the string/stream guards) declared `: int` but was inferred as returning
Union([Int, Bool]) — type_guard_narrowing recognized is_*()/instanceof but not the
`$x === false` strict comparison, so the divergent-guard complement (which the if/else
narrowing already keeps after a diverging body) never stripped the false-ish member.
Recognize `$var === false` / `false === $var` (BinOp::StrictEq) → narrow to Bool in the
then-branch and strip it in the else-branch (int|false → int). Byte-parity regression test.
AIC epic #483 / EC-8 (#491), the return-over-widening sub-part.

(cherry picked from commit 7c8ac75)
nahime0 pushed a commit that referenced this pull request Jul 10, 2026
`$this->raw instanceof Message ? $this->raw : new Message($this->raw)`
with `private Message|string $rawMessage` failed to compile — ternary
inference applied zero narrowing, and guard narrowing keyed only
variables (ExprKind::Variable), never property receivers.

Add branch_guard_narrowing(): recognizes variable guards (delegating to
type_guard_narrowing) plus `$var->prop instanceof Class` /
`$this->prop instanceof Class`, keying the narrowed type under a
synthetic \x01-sigil env key that infer_property_access_type consults
before falling back to the declared field type. Both ternary inference
paths (pure + assignment-effects) narrow their branch envs with it. A
ternary is a single expression — no intervening writes — so the property
narrowing is write-invalidation-safe here by construction.

Byte-parity vs PHP 8.5 (hi:k) + codegen regression test. AIC EC-8 (#491):
clears the FieldViolation Message|string pattern across the Forms Field
family.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit fcff01d)
nahime0 pushed a commit that referenced this pull request Jul 10, 2026
…tement-level

Three narrowing gaps behind ~7 AIC Domain classes:
- `if (is_null($p)) { throw; }` did not narrow ?int → int (only
  `=== null` did): add is_null to the predicate map.
- `if (!$this->prop instanceof C) { throw; } return $this->prop;`
  (ward-forms StoreResult::ref) kept the property at ?C: statement-level
  guards now key simple property receivers via the synthetic
  \x01-sigil env key, riding the existing if-chain save/restore and
  divergent-complement machinery.
- The ternary property narrowing from the previous commit now shares the
  same single entry point.

Mechanics: fold type_guard_narrowing + branch_guard_narrowing into
guard_narrowing(); guard_receiver_and_type() extracts (receiver expr,
target) for all guard forms (is_* / is_null / instanceof / === false /
=== null) on variables AND simple property accesses.

Soundness: property narrowings are purged after any call (the callee may
mutate the object), after assignments writing through a property, and at
loop-body entry (a later iteration may observe an earlier write) —
purge_property_narrowings() in the effects walk. A call's own argument
checking still sees the narrowing (purge happens after the call's
inference).

Byte-parity vs PHP 8.5 (5 / x) + 2 codegen regression tests. Full suite
6177 passed / 1 env-only failure (IPv6-DNS network test). AIC EC-8 (#491).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 3c58b4e)
chadmandoo added a commit to chadmandoo/elephc that referenced this pull request Jul 10, 2026
…26.1

Re-ports four v0.26.0 checker-completeness fixes onto the clean v0.26.1
base, driven by the AIC survey (ward-forms Field method chain was the
deepest shared gap across 17 field classes). Survey 73% → 79% (224 →
241/307).

- COMPARISON OPERANDS (elephc #598 / R8): relational (`< > <= >=`) and
  spaceship (`<=>`) accept scalar operands — strings compare
  lexicographically, unions coerce at runtime — not just numerics. PHP
  semantics; v0.26.1 rejected `$a < $b` on Str/Union.
- ABSTRACT new static() (EC-3 / illegalstudio#486): a late-bound `new static()` only
  ever instantiates a CONCRETE subclass at runtime, so the abstract base
  declaring it is not a constructor target to validate
  (Field::with()). class_refs.rs filters abstract candidates.
- RETURN COLLECTOR narrowing (part of the same cluster): collect_return_infos
  re-applies if-guard narrowing so `return $value;` inside
  `if ($value instanceof Message)` reports Message, not the un-narrowed
  type (Field::messageOption()).
- MIXED/UNION BOUNDARY (EC-8 / illegalstudio#491): types_compatible accepts a `mixed`
  value into any narrower declared type (runtime-enforced PHP) and a
  union value with >=1 compatible member (int|false on its success
  path). Clears the listOption/renderOption/requireZero family.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added area:builtins Touches PHP builtin declarations or emitters. area:codegen Touches target-aware assembly or backend lowering. area:runtime Touches runtime helpers, GC, ownership, or bridge runtimes. size:m Medium-sized pull request. target:linux-x86_64 Contains behavior specific to the Linux x86_64 target. type:fix Corrects broken or incompatible behavior. labels Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:builtins Touches PHP builtin declarations or emitters. area:codegen Touches target-aware assembly or backend lowering. area:runtime Touches runtime helpers, GC, ownership, or bridge runtimes. size:m Medium-sized pull request. target:linux-x86_64 Contains behavior specific to the Linux x86_64 target. type:fix Corrects broken or incompatible behavior.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

x86_64 emitters stamp a transposed heap magic (0x4548504c vs 0x454C5048): refcount ops silently no-op

1 participant