Repository navigation
Conversation
The "Prototype Class" snippet allocated with allocateCell<JSFooPrototype> and declared a subspaceFor member. 78 classes in src/jsc/bindings allocate their prototype with Bun::allocatePlainObjectCell, which takes a size and never instantiates subspaceFor. The snippet now calls the helper, has no subspaceFor member, and has STATIC_ASSERT_ISO_SUBSPACE_SHARABLE after the class. It also calls Bun::createClassStructure, Bun::reifyStaticPropertyTable and Bun::putToStringTagWithoutTransition, as the classes on main do. Two paragraphs after the snippet say why the class has no subspaceFor member, why the assert is at file scope, and where the helpers are declared.
…ippet The text under the prototype snippet now says to use the out-of-line helpers. The "Constructor Class" snippet still called JSC::Structure::create. 37 of the 42 InternalFunction subclasses in src/ build their structure with Bun::createClassStructure, for example JSStatsConstructor in src/jsc/bindings/NodeFSStatBinding.cpp.
|
Status: ready for review. CI is red for reasons outside this change. This pull request has no repro. It changes one document, CI, build 137221 (finished):
How the snippets were checked:
|
|
No actionable comments were generated in the recent review. WalkthroughThe JSC class implementation skill updates its prototype and constructor examples to use Bun helpers. It also adds guidance on when a prototype can use the shared plain-object space. ChangesJSC Class Guidance
Suggested reviewers: Merge Risk:
|
There was a problem hiding this comment.
LGTM, a doc-only update that brings the skill's prototype snippet in line with the helpers Bun's bindings already use.
What was reviewed:
- Checked the four helper names and signatures against
src/jsc/bindings/BunClientData.hlines 143-148; the snippet's call shapes match. - Confirmed the "class has no
subspaceFor" claim againstBunClientData.cpp:allocatePlainObjectCellallocates through its ownPlainObjectCell::subspaceFor, so a member on the caller's class is never consulted. - File-scope
STATIC_ASSERT_ISO_SUBSPACE_SHARABLEalready appears in real code (e.g.src/jsc/bindings/NodeVMScript.h:42), so the snippet's placement is an existing in-tree pattern, not a new one. - The constructor snippet keeps
JSC::allocateCelland its livesubspaceFor; onlycreateStructurechanged, which is consistent with theInternalFunctionsubclasses on main.
Extended reasoning...
The PR edits a single Claude skill document, .claude/skills/implementing-jsc-classes-cpp/SKILL.md (+10/-8), replacing inline JSC calls in the example prototype and constructor classes with Bun's out-of-line helpers and adding two explanatory paragraphs; no source, build, or test files change. It touches no security-sensitive surface. Approval was decided by the change being small and documentation-only, by every helper and its semantics being verifiable in BunClientData.h/.cpp, and by the changed path having no CODEOWNER and no outstanding objections in the timeline. The remaining mismatch with the 78 existing classes (which still carry a dead subspaceFor) is a pre-existing state of the source tree that the PR description says is handled in separate PRs, not an error in this document.
Problem
.claude/skills/implementing-jsc-classes-cpp/SKILL.mdallocates withallocateCell<JSFooPrototype>(vm)and declaressubspaceFor. Trim ~2 MB from the release binary without touching hot paths #39770 addedBun::allocatePlainObjectCell, and 78 classes use it. No document names the helper.JSC::allocateCell<T>again. A class copied from one of the 78 keeps theirsubspaceFor, which nothing instantiates:ImportMetaEnv(bun test: Vitest compatibility #44833),JSModuleGraphPrototype(Bun.ModuleGraph: a context per graph for timers and I/O, per-graph CommonJS #42590), and six classes in the open Implement the async Clipboard API (navigator.clipboard) #33312, Dispatch unhandledrejection and rejectionhandled on the global scope with PromiseRejectionEvent #45596, Add the QuotaExceededError global #47040 and Implement import.meta.hot for bun --hot #32856.Fix
Bun::allocatePlainObjectCell, has nosubspaceFormember, and hasSTATIC_ASSERT_ISO_SUBSPACE_SHARABLEafter the class.Bun::createClassStructure,Bun::reifyStaticPropertyTableandBun::putToStringTagWithoutTransition, as the classes onmaindo. The second commit changes one line of the "Constructor Class" snippet for the same reason.Behaviour change: none
Background
subspaceFortellsJSC::allocateCell<T>which GC subspace holds aT. A prototype class adds no field, so all prototype objects sharevm.plainObjectSpace().Bun::allocatePlainObjectCell(vm, size)allocates there and takes no type.static_assertinside a template that nothing instantiates. 23 of the 78 classes have the assert only insidesubspaceFor.REVIEW.mdrules out a lint that checks that dead code stays dead.Notes
Each line of the new snippets, and a class on
main3bf1a06 that has itnew (NotNull, Bun::allocatePlainObjectCell(vm, sizeof(T))) T(vm, structure):JSStatsPrototype,src/jsc/bindings/NodeFSStatBinding.cpp:391.Bun::createClassStructure(...)withsetMayBePrototype(true):NodeFSStatBinding.cpp:407.STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(T, T::Base)after the class:src/jsc/bindings/webcrypto/JSSubtleCrypto.cpp:155, and 54 more of the 78 classes.Bun::reifyStaticPropertyTable(vm, JSFoo::info(), table, *this)andBun::putToStringTagWithoutTransition(vm, this, info()):src/jsc/bindings/JSBunRequest.cpp:204.createStructurewithBun::createClassStructure:JSStatsConstructor,NodeFSStatBinding.cpp:494.subspaceFormember: none onmain. All 78 still have the member.JSStatsPrototypeat the head of Remove dead code from the JSC prototype classes, bun_paths, bun_sys, the JSON5 parser, and two code generators #46388 (c5633b3) has the shape of the new snippet, and build 130907 of that head passed.Compile check
The scratch file has these parts, in this order:
#include "root.h"and#include "BunClientData.h", thennamespace Bunwithusing namespace JSC.JSFoowithDECLARE_INFO.s_infodefinitions, and empty bodies for the four host functions.The command is the
argumentsentry ofNodeFSStatBinding.cppinbuild/debug/compile_commands.json, with the scratch file as the source. That is clang 23.1.2 with-std=gnu++23 -Werrorand WebKit 4d45755fed4a.main-fsyntax-onlyand-c)int m_fieldinJSFooPrototypestatic assertion failed due to requirement 'sizeof(Bun::JSFooPrototype) == sizeof(JSC::JSNonFinalObject)',24 == 16, at the line after the classstatic void destroy(JSC::JSCell*)static assertion failed due to requirement 'Bun::JSFooPrototype::destroy == JSC::JSNonFinalObject::destroy'subspaceFor), plusint m_fieldsubspaceFormemberno member named 'subspaceFor' in 'Bun::JSFooPrototype'The object file of the old snippet defines four functions that the object file of the new snippet does not:
JSFooPrototype::subspaceFor<>JSC::subspaceFor<JSFooPrototype>JSC::allocateCell<JSFooPrototype>JSC::tryAllocateCellHelper<JSFooPrototype, ...>A five-line file checks the
static_assertrule alone. clang 23.1.2 and g++ 14.2 accept a falsestatic_assertinside a member template that nothing instantiates. Both reject the same line at file scope. The result is the same with-std=c++17,c++20andgnu++23.Counts on
main3bf1a06Bun::allocatePlainObjectCell: 78 calls in 72 files. Each of the 78 classes still declaressubspaceFor. 55 have the assert at file scope too.allocateCell<T>, for examplesrc/jsc/bindings/JSX509CertificatePrototype.h:21. TheirsubspaceForis live. The old snippet had their shape. No commit after Trim ~2 MB from the release binary without touching hot paths #39770 added a class of this kind.Bun::createClassStructure: 269 calls. 37 of the 42InternalFunctionsubclasses use it increateStructure.Bun::reifyStaticPropertyTable: 108 calls.Bun::putToStringTagWithoutTransition: 89 calls. OutsideBunClientData.cpp, no file insrc/callsreifyStaticPropertiesorJSC_TO_STRING_TAG_WITHOUT_TRANSITION().root.hdoes not declare the helpers.BunClientData.hdoes, andZigGlobalObject.hincludes it indirectly.Left alone
main. They are separate from the helpers of Trim ~2 MB from the release binary without touching hot paths #39770.jsDynamicCastandUNLIKELYas undeclared.WTFMoveis undeclared.initLatercall and onevisitcall per member.ZigGlobalObjectnow uses a member list and an offset table.subspaceFormember of the "Constructor Class" snippet is live: that snippet allocates withJSC::allocateCell<JSFooConstructor>.no test proof · iteration 0 · docs-only change; test-proof not applicable