Skip to content

Zero-cons SLOT-VALUE / SLOT-BOUNDP via the class location table - #1810

Open
dg1sbg wants to merge 1 commit into
clasp-developers:mainfrom
dg1sbg:pr/slot-value-zero-cons
Open

Zero-cons SLOT-VALUE / SLOT-BOUNDP via the class location table#1810
dg1sbg wants to merge 1 commit into
clasp-developers:mainfrom
dg1sbg:pr/slot-value-zero-cons

Conversation

@dg1sbg

@dg1sbg dg1sbg commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Removes the allocation from the standard SLOT-VALUE / (SETF SLOT-VALUE) / SLOT-BOUNDP path.

The problem

The generic path reaches the slot through %FIND-SLOT and the standard SLOT-VALUE-USING-CLASS method, both of which call the metaobject readers SLOT-DEFINITION-NAME and SLOT-DEFINITION-LOCATION. Those are un-optimized STANDARD-READER-METHODs on bootstrap MOP classes, and each one allocates. For code that touches slots in a loop that is pure garbage.

The change

When the receiver is a current standard instance, the name→location lookup goes straight through the class's LOCATION-TABLE instead of the metaobject readers, and the value is fetched with STANDARD-LOCATION-ACCESS. The old bodies are factored out unchanged into %SLOT-VALUE-GENERIC, %SETF-SLOT-VALUE-GENERIC and %SLOT-BOUNDP-GENERIC, which the fast path falls back to.

The fast path is taken only when %INSTANCE-FAST-TABLE returns a table, which requires both:

  • the class rack slot actually holds a hash-table (so built-in and unfinalized classes fall back), and
  • CORE:INSTANCE-STAMP equals the class's STAMP-FOR-INSTANCES.

The second condition is the important one. An obsolete instance must not use the fast path — the generic SLOT-VALUE-USING-CLASS dispatch is what runs the stamp check and UPDATE-INSTANCE, and skipping it would read a stale rack, silently returning wrong values for a redefined class.

SLOT-MAKUNBOUND is deliberately left on the generic path.

Verification

Beyond the suite, the stamp guard was probed directly, since it is the one thing that would fail silently:

OBSOLETE instance:  instance-stamp 268866 vs class 268874  ->  %INSTANCE-FAST-TABLE => NIL
FRESH instance:     instance-stamp 268874 vs class 268874  ->  %INSTANCE-FAST-TABLE => T

The obsolete instance is correctly refused and falls through to the generic path.

Also checked by hand: reads, writes, SLOT-BOUNDP on bound and unbound slots, SLOT-MAKUNBOUND, SLOT-MISSING, SLOT-UNBOUND, non-instance fallback, and that binding the escape hatch to NIL still yields correct results.

macOS arm64, native build, LLVM 22.1.8:

GC variant successes unexpected failures
boehm 1971 none
boehmprecise (clean build from scratch) 1973 none

The four expected failures (SBCL-CROSS-COMPILE-4, INCLUDE-LEVEL-2B, INCLUDE-LEVEL-3, TYPES-CLASSES-10) are pre-existing and unrelated; the count differs between variants only because boehmprecise also runs the variant-specific snapshot tests.

The boehmprecise result matters for this change specifically: the fast path reads raw rack slots via CORE:RACK-REF and hands back slot locations, so if anything there were invisible to the precise scanner it would show up under precise stack scanning and nowhere else. The stamp-guard probe above was re-run under boehmprecise with identical results.

For disclosure, both runs were made from a tree that also carried #1809 on top of main; slot-value.lisp there is byte-identical to the commit in this PR, and #1809 touches no CLOS code.

Points I'd like a maintainer's view on

The rack indices are duplicated, not derived. +CLASS-LOCATION-TABLE-RACK-INDEX+ (17) and +STAMP-FOR-INSTANCES-RACK-INDEX+ (18) restate the :location values declared in hierarchy.lisp. They are correct today and those :locations are explicit rather than positional, so this is a two-place edit rather than an implicit ordering dependency — but nothing enforces the correspondence, and if it ever drifts every standard instance in the image reads the wrong rack cell silently. A compile-time assertion tying the constants to the slot definitions would cost nothing; happy to add one if you want it.

*OPTIMIZE-SLOT-VALUE* is not exported. It is described as the escape hatch for programs that define a custom SLOT-VALUE-USING-CLASS and want it honoured, but reaching it requires CLOS::. Should it be exported, or is internal-only intended?

No benchmark is included. The motivation is allocation, and the mechanism is clear from the diff, but this PR does not ship a measurement. Say the word and I'll add one.

@dg1sbg
dg1sbg force-pushed the pr/slot-value-zero-cons branch from 704789b to 1a44d75 Compare July 31, 2026 17:02
slot-value/(setf slot-value)/slot-boundp went through the metaobject slot-definition
readers (%find-slot's slot-definition-name + slot-value-using-class's
slot-definition-location) -- un-optimized STANDARD-READER-METHODs on bootstrap MOP
classes that cons ~96 B each (192 B/call). Route the standard case through the class's
existing LOCATION-TABLE (name->location, rack slot 17 on STD-CLASS) for 0-cons access.

Fast path is gated on %INSTANCE-FAST-TABLE: taken only for a CURRENT standard instance
(core:instancep + instance stamp = class STAMP-FOR-INSTANCES, rack slot 18). An OBSOLETE
instance (redefined class), a built-in/unfinalized class, or *OPTIMIZE-SLOT-VALUE* nil
(custom slot-value-using-class) falls back to the full MOP dispatch, whose
SLOT-VALUE-USING-CLASS discrimination runs the stamp check + UPDATE-INSTANCE. Reading the
rack slots directly avoids the CLASS-LOCATION-TABLE reader (itself a consing metaobject reader).
@dg1sbg
dg1sbg force-pushed the pr/slot-value-zero-cons branch from 1a44d75 to 2174fc1 Compare August 13, 2026 16:35
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