CachedTypes: borrow CachedBytecode bytes for InstructionStream and ExpressionInfo
Summary
CachedInstructionStream::decode and CachedExpressionInfo::decode heap-allocate and memcpy their payloads out of the CachedBytecode buffer:
CachedInstructionStream::decodeallocates aVector<uint8_t, …, InstructionStreamBufferMalloc>and copies the instruction bytes into it.CachedExpressionInfo::decodeFastMallocstotalSizeInBytes()(header +chapters[nC]+encodedInfo[nE+nExt]) and copies the trailing payload.
In both cases the bytes are stored verbatim (CachedVector<uint8_t> / CachedArray<unsigned> are identity encode/decode), the decoded object is read-only after construction (ExpressionInfo's only mutable state is the per-instance LineColumnMap cache in its header), and the CachedBytecode buffer they came from outlives every cache-derived UnlinkedCodeBlock. The copies are therefore unnecessary and, because m_isGeneratedFromCache disables UnlinkedCodeBlock jettisoning, they persist for ~process lifetime.
This change, gated on BUN_JSC_ADDITIONS:
InstructionStream
- Gains
InstructionSpan m_borrowedand aview()accessor that prefers the owningVectorwhen non-empty (soInstructionStreamWritermid-generation reads its current bytes) and falls back tom_borrowed. All const accessors (at,begin/end,size,sizeInBytes,rawPointer,contains) go throughview(). InstructionStream::Refholds anInstructionSpanby value rather than anInstructionBuffer&. Public surface is unchanged.MutableRef(writer-only) keepsInstructionBuffer&.CachedVector::borrowedSpan()(static-asserted to identity-encoded element types) returns a span over the in-place encoded bytes.CachedInstructionStream::decodereturnsJSInstructionStream::createBorrowed(m_instructions.borrowedSpan()).encodereadsstream.view()so a borrowed stream round-trips.
ExpressionInfo
- Gains
const unsigned* m_borrowedPayload.payload()returns it when set, otherwise the upstreambit_cast(this + 1).chapters()reads throughpayload()soencodedInfo()/endEncodedInfo()follow. createBorrowed()FastMallocs onlysizeof(ExpressionInfo)— header plus the mutableLineColumnMapcache — and pointsm_borrowedPayloadat the caller-owned bytes.CachedArray::borrowedBuffer(size)(static-asserted to identity-encoded element types) is theCachedArrayanalogue ofCachedVector::borrowedSpan.CachedExpressionInfo::decodereturnsExpressionInfo::createBorrowed(...)pointing atm_storage.borrowedBuffer().
No on-disk format change.
Why the lifetime is safe
SourceProvider (runtime) UnlinkedFunctionExecutable (m_isCached)
└─ m_cachedBytecode ──┐ └─ m_decoder ─┐
▼ ▼
CachedBytecode ◄──────────── Decoder::m_cachedBytecode
│
span into mmapped buffer
│
JSInstructionStream::m_borrowed / ExpressionInfo::m_borrowedPayload
(held by UnlinkedCodeBlock::m_instructions / m_expressionInfo)
UnlinkedFunctionExecutable::codeBlockEdgeMayBeWeak() returns false when m_isGeneratedFromCache, so cache-derived UnlinkedCodeBlocks are never aged out — the borrowed bytes outlive every reader.
Test plan
-
bun build --bytecode --compilestandalone runs and produces identical stdout to a no-bytecode build (fib, async, class,Function.prototype.toString,Error.stackline/column) -
BUN_JSC_verboseDiskCache=1showsCache hit for sourceCode -
Error.stackline:column values from a borrowedExpressionInfomatch the non-borrowed path - Bun
test/bundler/bundler_compile.test.ts - Bun CI with bumped WebKit pin