CachedTypes: borrow CachedBytecode bytes for InstructionStream and ExpressionInfo

DY
7mo ago
+225-0

Summary

CachedInstructionStream::decode and CachedExpressionInfo::decode heap-allocate and memcpy their payloads out of the CachedBytecode buffer:

  • CachedInstructionStream::decode allocates a Vector<uint8_t, …, InstructionStreamBufferMalloc> and copies the instruction bytes into it.
  • CachedExpressionInfo::decode FastMallocs totalSizeInBytes() (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_borrowed and a view() accessor that prefers the owning Vector when non-empty (so InstructionStreamWriter mid-generation reads its current bytes) and falls back to m_borrowed. All const accessors (at, begin/end, size, sizeInBytes, rawPointer, contains) go through view().
  • InstructionStream::Ref holds an InstructionSpan by value rather than an InstructionBuffer&. Public surface is unchanged. MutableRef (writer-only) keeps InstructionBuffer&.
  • CachedVector::borrowedSpan() (static-asserted to identity-encoded element types) returns a span over the in-place encoded bytes.
  • CachedInstructionStream::decode returns JSInstructionStream::createBorrowed(m_instructions.borrowedSpan()). encode reads stream.view() so a borrowed stream round-trips.

ExpressionInfo

  • Gains const unsigned* m_borrowedPayload. payload() returns it when set, otherwise the upstream bit_cast(this + 1). chapters() reads through payload() so encodedInfo()/endEncodedInfo() follow.
  • createBorrowed() FastMallocs only sizeof(ExpressionInfo) — header plus the mutable LineColumnMap cache — and points m_borrowedPayload at the caller-owned bytes.
  • CachedArray::borrowedBuffer(size) (static-asserted to identity-encoded element types) is the CachedArray analogue of CachedVector::borrowedSpan.
  • CachedExpressionInfo::decode returns ExpressionInfo::createBorrowed(...) pointing at m_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 --compile standalone runs and produces identical stdout to a no-bytecode build (fib, async, class, Function.prototype.toString, Error.stack line/column)
  • BUN_JSC_verboseDiskCache=1 shows Cache hit for sourceCode
  • Error.stack line:column values from a borrowed ExpressionInfo match the non-borrowed path
  • Bun test/bundler/bundler_compile.test.ts
  • Bun CI with bumped WebKit pin