Skills Plugins MCP Prompt Model 博客 我的中心

javascriptcore-garbage-collector

JSC GC reference for Bun. Use for use-after-free, JS object leaks, "collected too early", or when touching WriteBarrier, visitChildren, visitAdditionalChildren, JSRef, JSC::Strong/Weak, hasPendingActivity, ensureStillAlive, addOpaqueRoot, reportExtraMemoryAllocated, IsoSubspace, HeapAnalyzer, finalize.

DeepseekModel 官方收录技能 质量 优秀 · 90 v1.0.0

获取

https://deepseekmodel.com/api/download.php?id=oven-sh-bun-claude-skills-javascriptcore-garbage-collector-skill-md&format=skill
下载 .skill 标准格式,含 system_prompt 与 model_config,导入任意 Agent 框架即可使用
.skill 文件中 system_prompt 字段的实际内容。
name javascriptcore-garbage-collector description JSC GC reference for Bun. Use for use-after-free, JS object leaks, "collected too early", or when touching WriteBarrier, visitChildren, visitAdditionalChildren, JSRef, JSC::Strong/Weak, hasPendingActivity, ensureStillAlive, addOpaqueRoot, reportExtraMemoryAllocated, IsoSubspace, HeapAnalyzer, finalize. JavaScriptCore's Garbage Collector (Riptide) Riptide is non-moving, generational, parallel, mostly-concurrent, conservative-on-the-stack . Understanding those five words prevents most GC bugs in Bun. The mental model The heap is a graph. GC does a breadth-first search from roots → marks everything it reaches → everything unmarked is freed (lazily, on next allocation from that block). It does NOT compact or move objects — pointers stay stable for an object's lifetime. Two collection modes: Eden GC : only scans newly-allocated objects + remembered set. Fast, frequent. Full GC : scans everything. Slower, rarer. It runs concurrently. Marking happens on background threads while JS is executing ; the mutator only stops at brief safepoints. visitChildren runs off the main thread, racing with your code . How the VM gathers roots Roots are not a hardcoded list — they are marking constraints registered with Heap::addMarkingConstraint() and run to fixpoint. The built-in set lives in Heap::addCoreConstraints() ( vendor/WebKit/Source/JavaScriptCore/heap/Heap.cpp:2970 ): Tag Name What it marks Cs Conservative Scan Native stack + registers of every JS thread, scanned word-by-word ( gatherStackRoots → ConservativeRoots ). Also JIT stub routines. World is stopped for this. Msr Misc Small Roots vm.smallStrings , m_protectedValues ( JSValueProtect / gcProtect ), MarkedArgumentBuffer lists, vm.exception() / lastException() / m_terminationException Sh Strong Handles m_handleSet.visitStrongHandles() — every JSC::Strong<T> . Also vm().visitAggregate() (atom string tables etc.) D Debugger Sampling profiler, type profiler, ShadowChicken Ws Weak Sets Iterates every WeakBlock ; calls WeakHandleOwner::isReachableFromOpaqueRoots() to decide whether a weak ref should become strong this cycle O Output Calls visitOutputConstraints() on already-marked cells in output-constraint subspaces (executables, WeakMaps). This is the "re-run after marking discovers more" hook Jw JIT Worklist CodeBlocks queued for compilation Cb CodeBlocks Executing/compiling CodeBlocks Bun registers an additional constraint, DOMGCOutputConstraint ( src/jsc/bindings/BunGCOutputConstraint.cpp ), which calls visitOutputConstraints on every marked cell in Bun's output-constraint subspaces (event targets, generated classes with visitAdditionalChildren , etc.). Constraint volatility controls when they re-run during the fixpoint: GreyedByExecution — may produce new grey cells whenever the mutator runs (re-run after every resume) GreyedByMarking — may produce new grey cells when other marking happens (re-run after each drain) SeldomGreyed — usually doesn't add anything; run last Object layout: the 8-byte JSCell header Every GC-managed object inherits JSCell ( runtime/JSCell.h ): | StructureID (4) | indexingTypeAndMisc (1) | JSType (1) | flags (1) | cellState (1) | StructureID — compressed hidden-class pointer indexingTypeAndMisc — 2 bits are an embedded WTF::Lock (the cell lock ); always CAS this byte cellState — inlined GC color, used by the write barrier Out-of-line, in the MarkedBlock footer (or PreciseAllocation header for objects >~8KB): isMarked bit — survived last GC isNewlyAllocated bit — allocated since last GC Liveness = isMarked || isNewlyAllocated (with logical-versioning so blocks aren't swept eagerly). CellState and the write barrier vendor/WebKit/Source/JavaScriptCore/heap/CellState.h : PossiblyBlack = 0 // visited (or old-space-pending-rescan during full GC) DefinitelyWhite = 1 // new / unmarked PossiblyGrey = 2 // on the mark stack Generational + concurrent GC share one retreating-wavefront barrier: // After: obj->field = newValue if (obj->cellState <= blackThreshold) // 0 normally, bumped while GC is marking writeBarrierSlowPath (obj); // → put obj on remembered set / revisit You almost never write this by hand. Use WriteBarrier<T> as the field type and call .set(vm, owner, value) — it stores then barriers. A raw JSCell* / JSValue member without a WriteBarrier wrapper is a bug: eden GC will free the target out from under you. LazyProperty<Owner, T> , LazyClassStructure , and WriteBarrierStructureID are barrier-aware variants for lazily-initialized fields and structures. Allocation: where objects live bmalloc/libpas provides pages; JSC carves them up: MarkedBlock — 16KB block, fixed cell size (segregated free list). Footer holds bitvectors. 16-byte minimum cell alignment. addr & ~(16KB-1) → block, so liveness checks are O(1). PreciseAllocation — large objects (>~8KB), individually malloc 'd, 96-byte GC header. Always returns addresses with addr % 16 == 8 so ptr & 8 distinguishes them from MarkedBlock cells. CompleteSubspace — size-segregated set of BlockDirectory s for general JS objects. IsoSubspace — one subspace per C++ type (security: a freed cell can only be reused for the same type, defeating type-confusion UAF). Every Bun class with native fields needs its own IsoSubspace — subspaceFor<T> in the header, slot in BunClientData / DOMIsoSubspaces . Allocation may trigger GC. A safepoint exists at every allocation. Never assume "I just allocated X, so Y from before is still alive" unless Y is rooted. Conservative stack scanning — what it does and doesn't guarantee vendor/WebKit/Source/JavaScriptCore/heap/ConservativeRoots.cpp walks the native stack/registers word-by-word (after MachineThreads::tryCopyOtherThreadStacks snapshots them). Any aligned word inside a live MarkedBlock cell or PreciseAllocation is a root. This means: a JSCell* / JSValue in a C++/Rust local variable usually keeps the object alive — no Handle / Local ceremony like V8. This does NOT mean you're always safe. The compiler may dead-store-eliminate the local after its last visible use, or never spill it. If you extract an interior pointer ( string->characters8() , butterfly storage, typed-array vector() ) and then call something that can allocate, the original cell may no longer be on the stack: JSC::EnsureStillAliveScope keepAlive (cell) ; // RAII: forces cell onto stack until scope end // ... use interior pointer, call things that allocate ... or ensureStillAliveHere(cell) . In Rust: value.ensure_still_alive() . visitChildren — the per-cell tracing hook // In header: DECLARE_VISIT_CHILDREN; WriteBarrier<JSObject> m_callback; WriteBarrier<Unknown> m_cachedValue; // In .cpp: template < typename Visitor> void JSFoo::visitChildrenImpl (JSCell* cell, Visitor& visitor) { auto * thisObject = jsCast <JSFoo*>(cell); ASSERT_GC_OBJECT_INHERITS (thisObject, info ()); Base:: visitChildren (thisObject, visitor); // ALWAYS call base first visitor. append (thisObject->m_callback); visitor. append (thisObject->m_cachedValue); } DEFINE_VISIT_CHILDREN (JSFoo); Rules — runs concurrently on a GC thread: No allocation. No toJS , no jsString , nothing that touches vm.heap . No ref() / deref() of RefCounted (not thread-safe). No locks the main thread might also take while allocating (deadlock). If a field can be torn by a racing mutator, take Locker locker { thisObject->cellLock() } in both visitChildren and the mutating site. Forgetting to append() a WriteBarrier field → use-after-free, often eden-GC-only, often only under load. visitAdditionalChildren and output constraints visitChildren only sees the cell's own fields. When a JS wrapper's liveness should propagate to other JS objects reachable through native state (event listeners, observers, the JS values held inside a wrapped C++ object), Bun uses the WebCore pattern: // Custom hook called from BOTH places: template < typename Visitor> void JSFoo::visitAdditionalChildren (Visitor& visitor) { wrapped (). listeners (). visitJSEventListeners (visitor); visitor. addOpaqueRoot (& wrapped ()); } // 1) From visitChildren (normal marking): DEFINE_VISIT_CHILDREN_WITH_MODIFIER (..., JSFoo) { ... thisObject-> visitAdditionalChildren (visitor); } // 2) From visitOutputConstraints (constraint fixpoint re-scan): template < typename Visitor> void JSFoo::visitOutputConstraints (JSCell* cell, Visitor& visitor) { auto * thisObject = jsCast <JSFoo*>(cell); Base:: visitOutputConstraints (thisObject, visitor); thisObject-> visitAdditionalChildren (visitor); } Why two entry points? visitChildren runs once when the cell turns grey. But marking may later discover that some other native object (an opaque root) is live, which retroactively makes more of this cell's references live. visitOutputConstraints is re-invoked by DOMGCOutputConstraint during the constraint fixpoint to catch that. To make a class participate, its IsoSubspace must be registered as an output-constraint subspace ( clientSubspaceFor* with outputConstraint in BunClientData / generated ZigGeneratedClasses.cpp ). The codegen does this automatically when .classes.ts has hasPendingActivity , own properties, or event-target semantics. Opaque roots — liveness through non-JSCell pointers When native objects form a graph that should keep wrappers alive: // In some wrapper's visitAdditionalChildren: visitor. addOpaqueRoot (nativePtr); // "nativePtr is reachable" // Elsewhere, deciding whether ANOTHER wrapper survives: bool JSBarOwner::isReachableFromOpaqueRoots (Handle<Unknown> h, void * ctx, AbstractSlotVisitor& v, ASCIILiteral* reason) { auto * bar = static_cast <Bar*>(ctx); if ( UNLIKELY (reason)) *reason = "Bar is in document tree" _s; return v. containsOpaqueRoot (bar-> ownerNode ()); } The opaque-root set is just a HashSet<void*> rebuilt each cycle. It's how DOM trees stay alive as a unit. JSC::Weak<T> , WeakImpl , WeakBlock , WeakHandleOwner JSC::Weak<T> ( vendor/WebKit/Source/JavaScriptCore/heap/Weak.h ) is the GC-aware weak pointer. It does not keep its target alive; .get() returns nullptr after the target is collected. Under the hood: Each Weak<T> owns a WeakImpl* ( vendor/WebKit/Source/JavaScriptCore/heap/WeakImpl.h ): { JSValue, WeakHandleOwner* (low bits = state), void* context } . State is Live → Dead → Finalized → Deallocated . WeakImpl s are slab-allocated in 1KB WeakBlock s ( vendor/WebKit/Source/JavaScriptCore/heap/WeakBlock.h , blockSize = 1024 ). Every MarkedBlock and PreciseAllocation has a WeakSet — a linked list of WeakBlock s for cells in that container. During the Ws constraint, each WeakBlock::visit() walks its WeakImpl s; for each one whose target is not yet marked , it calls WeakHandleOwner::isReachableFromOpaqueRoots(handle, context, visitor, &reason) . Return true → the target is marked (the weak ref is "upgraded" this cycle). This is how hasPendingActivity() and opaque-root reachability keep wrappers alive even when nothing strongly references them. After marking, WeakBlock::reap() flips unmarked Live impls to Dead . WeakBlock::sweep() later runs WeakHandleOwner::finalize(handle, context) on each Dead impl, then frees the slot. finalize runs on the mutator thread but the cell is already dead — do not touch its JS fields. Typical use: drop the wrapper from a native→JS wrapper cache. struct MyOwner final : public JSC::WeakHandleOwner { bool isReachableFromOpaqueRoots (Handle<Unknown>, void * ctx, AbstractSlotVisitor& v, ASCIILiteral*) override { return static_cast <NativeThing*>(ctx)-> hasPendingActivity (); } void finalize (Handle<Unknown>, void * ctx) override { static_cast <NativeThing*>(ctx)->m_wrapper = nullptr ; } }; JSC::Weak<JSFoo> m_wrapper { jsFoo, &myOwnerSingleton, nativeThing }; Weak<T> is move-only (allocates a WeakImpl ). Don't put it in a hot path; cache it. JSRef — the native↔wrapper reference pattern When a native object needs to hold a reference back to its own JS wrapper, use JSRef ( src/jsc/JSRef.rs ), not gcProtect , not a raw JSValue field, and usually not Strong directly. JSRef is a tagged union with three states: Weak — a bare JSValue . Does not keep the wrapper alive. Valid only because the wrapper's finalize() will flip this to Finalized before the cell is freed, so try_get() returns None instead of a dangling pointer. (This is not a JSC::Weak ; it's cheaper — no WeakImpl allocation.) Strong — wraps bun_jsc::Strong (a JSC::Strong<Unknown> root). Keeps the wrapper alive. Finalized — terminal; try_get() returns None . Pattern: strong while busy, weak while idle. this_value: JSRef, // initialized with JSRef::empty() // On construction / when work starts: self .this_value. set_strong (js_wrapper, global); // or .upgrade(global) // When the last in-flight operation completes: self .this_value. downgrade (); // Strong -> Weak, GC may now collect // In any callback that needs the wrapper: let Some (js_this) = self .this_value. try_get () else { return };
Agent 识别该技能的关键词,点击任意一个即可复制。

该技能未提供触发词。

下载的 .skill 包内含以下字段。
字段 说明
format格式标识(skill/v1)
skill_id技能唯一 ID
name技能名称
version版本号
description技能描述
category所属分类(数组)
trigger_words触发词列表
tags标签列表
source来源标识
source_url来源链接(本页地址)
exported_at导出时间(每次下载生成)
system_prompt系统提示词正文
model_config模型参数:provider / model / temperature / max_tokens / top_p
examples示例
install_guide各平台导入说明(Coze / Dify / Claude / 自定义框架)
同一份技能可按不同平台格式导出。
.skill 标准格式,含 system_prompt 与 model_config,导入任意 Agent 框架即可使用 下载
.skillpro 增强格式,额外含脚本 / 工具 / 依赖 / 钩子占位 下载
.json 纯 JSON 导出,只含 system_prompt 与模型参数 下载
Coze 带 frontmatter 的 Markdown,Coze 平台导入用 下载
Dify Dify DSL,创建应用后直接导入 下载

每日精选 Skill 推荐,免费送到你邮箱

输入邮箱,每天接收一个精选 AI Agent 技能推荐。完全免费,持续更新。

提交后我们会发送一封确认邮件,点击邮件里的链接才会开始收信。

完全免费,取消任意时间。我们不会发送垃圾邮件。