Skip to content

feat(codegen): hold buffer<T> and ptr handles in static properties - #2039

Merged
nahime0 merged 8 commits into
mainfrom
feat/static-buffer-properties
Oct 25, 2026
Merged

nahime0 merged 8 commits into
mainfrom
feat/static-buffer-properties

Conversation

@nahime0

@nahime0 nahime0 commented Oct 24, 2026

Copy link
Copy Markdown
Member

Problem

A static property could not hold a buffer<T> handle. final class P { public static buffer<float> $shared; } P::$shared = buffer_new<float>(3); passed the checker and then failed with EIR backend error: unsupported EIR backend feature: store_static_property for static property PHP type Buffer(Float); buffer<PackedClass> statics failed the same way. Whole-element writes through a static buffer (P::$shared[$i] = 1.0) were also rejected by the checker with Array index assignment requires an array static property, got buffer<float>. Plain ptr / ptr<T> static properties shared the same unsupported backend path (store_static_property for static property PHP type Pointer(None)).

Ivory wants engine-wide scratch buffers and lookup tables as static buffer<T> properties (Code Journal task 231f2338).

Design

The static slot holds the handle or address word and owns nothing, exactly as an instance property slot does:

  • Codegen (src/codegen/lower_inst/static_properties.rs): Buffer and Pointer slots join the supported static-property storage shapes. Loads copy the low word out (after the usual uninitialized-marker guard for typed slots), stores overwrite it and clear the marker. Buffers are not refcounted, so nothing is released or freed on overwrite or at shutdown, matching instance properties.
  • Checker (src/types/checker/stmt_check/assignments/static_properties.rs): Class::$buf[$i] = $v on a buffer<T> static uses the shared validate_buffer_element_write rules of local and instance-property buffers (integer or mixed index, element type, packed records only through field access).
  • EIR lowering (src/ir_lower/stmt/static_property_writes.rs): a buffer-typed static element write loads the slot once the key and value are evaluated and emits buffer_set through the handle, with the same index and value coercions as the instance-property path. The slot is not written back (a buffer has no copy-on-write storage). Compound assignments, ++ and -- reuse this through the existing element-update desugaring. Reads, packed field reads and writes, buffer_len(), buffer_ptr(), and passing to functions already lowered correctly once the backend accepted the slot type.
  • Null pointer defaults (src/codegen/block_emit.rs): public static ptr $p = null; used to drop the null default (a pointer slot needs no literal) and then mark the declared slot uninitialized, so the first read threw. A declared pointer slot whose null default is skipped is now initialized explicitly to the zero word (value 0, marker 0), also on every web request.

Buffer hoisting pass

src/ir_passes/buffer_access.rs is untouched. Static buffer accesses do not break its assumptions: an access whose handle comes from an in-loop load_static_property is never treated as loop-invariant (only out-of-loop SSA values and unaliased load_local slots are), and any loop containing a call or a store_static_property that could replace or free the buffer is already disqualified. Codegen tests pin that a call inside the loop that replaces the static buffer is observed, and that freeing it through a copy inside the loop hits the use-after-free fatal at the next access.

Out of scope (filed as Code Journal tasks)

  • ?buffer<T> is boxed into mixed everywhere (locals, parameters, instance and static properties), so buffer operations on it fail in the backend; this is a representation decision for all nullable buffers, not specific to statics (task 33a02241).
  • public int $x = null; / public static int $x = null; pass the checker although PHP rejects them (task 660bbcc0). The new zero-word initialization is restricted to pointer slots so it does not change that case.

Tests

  • tests/codegen/static_buffer_properties.rs (new): store/load and element writes with literal and runtime indices, compound / ++ / -- updates, self:: and static:: access inside methods, packed field reads and writes (including += and ++ on a field), untyped static refined to a buffer, passing and returning the buffer, aliasing and reassignment (old handle stays valid), uninitialized typed static (isset() false, catchable Error), late static binding selecting a redeclared subclass slot, a loop that observes a buffer replaced by a call, use-after-free and bounds fatals, a heap-debug clean loop, ptr / ptr<T> / ?ptr<T> statics including the = null default and an uninitialized ptr<T>, and the new example's exact output.
  • tests/error_tests/extensions.rs: element type mismatch, string index, whole packed element, and push diagnostics through a static buffer property.
  • src/ir_lower/tests/static_properties.rs: on all five targets, the element write lowers to buffer_set on the loaded handle with no slot write-back, assembly is generated, the ptr ... = null slot is initialized to the zero word, typed slots without defaults are marked uninitialized, and no buffer or pointer slot gets shutdown cleanup.

Example and docs

  • New examples/static-buffer-tables/ (lookup tables, scratch buffer and a ptr<T> cursor in static properties), compiled and checked by test_static_buffer_tables_example.
  • docs/beyond-php/buffers.md: new "Static buffer properties" section replacing "A static property cannot hold a buffer yet".
  • docs/beyond-php/pointers.md: typed ptr / ptr<T> instance and static properties, including the = null default.
  • docs/internals/memory-model.md: static buffer/pointer slot layout and ownership.

@github-actions github-actions Bot added area:codegen Touches target-aware assembly or backend lowering. area:eir Touches EIR definitions, lowering, validation, or passes. area:types Touches type checking, inference, or compatibility. size:m Medium-sized pull request. type:feature Introduces new user-visible behavior or capabilities. labels Oct 24, 2026
@nahime0
nahime0 marked this pull request as ready for review October 24, 2026 07:35
@greptile-apps

greptile-apps Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The changes since the previous review appear safe to merge.

Summary

The PR lets static properties hold buffer handles and pointers without owning their memory. It adds static buffer element writes, null pointer defaults, documentation, and tests.

  • Static properties now keep buffer handles and pointer addresses.
  • Static buffer element writes now update the buffer directly.

Reviews (3) · Last reviewed commit: "test(codegen): keep the null-or-pointer ..." · Reviewed by Greptile

@nahime0
nahime0 added this pull request to the merge queue Oct 24, 2026
Merged via the queue into main with commit 3276e8c Oct 25, 2026
147 checks passed
@nahime0
nahime0 deleted the feat/static-buffer-properties branch October 25, 2026 03:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:codegen Touches target-aware assembly or backend lowering. area:eir Touches EIR definitions, lowering, validation, or passes. area:types Touches type checking, inference, or compatibility. size:m Medium-sized pull request. type:feature Introduces new user-visible behavior or capabilities.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant