Guy Bedford
90d · built 2026-09-08
Performance
What Guy Bedford shipped in the selected window, measured in ETV, and how it compares with the 90 days before it.
Effective capacity
−0.6engineers
delivers like 0.4 (0.4x pre-AI)
Output (ETV)
2.8ETV
+9.2% vs 2.6 prior
Features share
30.6%
−9.0 pp vs prior window
Fixes share
29.9%
+28.4 pp vs prior window
Work mix
30.6% Features9.2% Maintenance25.7% Tests4.6% Docs29.9% Fixes
20 commits over 90 days, ending 2026-09-08.
Daily performance
Daily ETV, stacked by Features, Maintenance, Tests, Docs and Fixes.
Repository spread
Where this developer's commits land. Concentrated work (top1 > 80%) vs polymath spread (top1 < 30%).
| Repo | Commits | ETV |
|---|---|---|
| workerd | 13 | 2.7 |
| workers-rs | 5 | 0.2 |
Most impactful commits
Top 10 by ETV in the last 90 days.
- 0.3ETVAdd wasm_memory_discard compatibility flag (#6999) Gates the experimental WebAssembly memory.discard proposal, exposing WebAssembly.Memory.prototype.discard(byteOffset, byteLength) and the memory.discard opcode on a per-worker basis. The feature is wired through V8's per-context conditional-feature mechanism: a per-isolate enabled callback (SetWasmMemoryDiscardEnabledCallback) reports the flag state, which V8 consults both when installing the JS API (InstallConditionalFeatures) and when compiling wasm modules that use the memory.discard opcode. Lock::installWasmMemoryDiscard() enables the feature once per context, based on the flag, via NewContextOptions. Floats the memory.discard V8 patch (0039) into workerd's V8 patch set so the feature is present in workerd's own V8 build. The patch routes the JS API, the jitted memory.discard wrapper and the interpreter through BackingStore::DiscardWasmMemory rather than discarding raw addresses, so page release happens at a single point, and that point discards through the page allocator the reservation actually came from. Under V8_ENABLE_SANDBOX wasm memory is reserved from the isolate group's backing-store allocator, not the platform allocator, so reaching for GetPlatformPageAllocator() decommitted through an allocator that does not own the region -- benign on Linux, where DiscardSystemPages is madvise(MADV_DONTNEED) keyed on address, but wrong on the DecommitPages/SetPermissions path used elsewhere. Also removes the obsolete jspiEnabledCallback declaration left behind when JSPI was stabilized.github.com-cloudflare-workerd · bd568a93 · 2026-08-17
- 0.3ETVRoute all zlib consumers through an autogated router (#7132) The unprefixed zlib symbol names (deflate, inflate, crc32, ...) are now owned by a compiled routing layer, src/workerd/util/zlib-router.c++, which forwards each call to one of the two implementations linked into the binary: chromium zlib (restored under its chromeconf Cr_z_* names as @zlib//:zlib_impl) or zlib-rs (exported under zlib_rs_* prefixes by src/rust/zlib-rs, no longer exporting the unprefixed names). The branch is a process-global atomic set from the compression-rs autogate whenever the gate state changes. Since every consumer is compiled with CHROMIUM_ZLIB_NO_CHROMECONF via the public @zlib target, the gate now universally covers node:zlib, web CompressionStream, crypto crc32, kj-gzip/kj-http (fetch and WebSocket compression), and V8's compression utils. This makes the per-stream ZlibBackend dispatch table redundant, so it is removed along with the zlib-rs bridge TU: compression.{h,c++}, zlib-util.c++, and crypto.c++ are back to plain zlib calls. The bench shim now reaches zlib-rs through the zlib_rs_* symbols, so the Native arm measures real chromium zlib again (~20% deflate / ~15% inflate delta on the streaming workload). A new kj_test verifies the router selects the matching implementation on both sides of the gate.github.com-cloudflare-workerd · d7389205 · 2026-08-26
- 0.3ETVAccept WebAssembly.Module values in the dynamic worker loader (#6997) A module in the worker loader modules bag may now be provided as a WebAssembly.Module directly (e.g. obtained via a source phase import), either as the module value itself or as { wasm: module }. The compiled code is shared with the loaded worker via v8::CompiledWasmModule rather than recompiling the wire bytes, in both the legacy and new module registries. Inside the loaded worker the module can be imported with import source, matching the ESM phase imports proposal semantics. Includes jsg support for unwrapping v8::WasmModuleObject handles so that jsg::V8Ref<v8::WasmModuleObject> can be used as a parameter and struct field type, with rtti and TypeScript type updates.github.com-cloudflare-workerd · 9cdf3805 · 2026-08-21
- 0.3ETVFix two GC strong-root leaks: tail event cf cycles and abandoned pipe writes (#7017) * api: trace cf objects from tail Request wrappers for GC TraceItem::FetchEventInfo::Request held Detail::cf in an untraced strong root while getCf() returns that same mutable object. A tail handler that made cf reference the request (or anything reaching its wrapper) closed an uncollectable JS<->C++ cycle: cf root -> cf -> Request wrapper -> C++ Request -> Detail -> cf root, leaking one object group per mutated tail event until isolate death. Add visitForGc to Request tracing detail->cf. Detail is shared only between the redacted/unredacted Request pair, both of this class, so every holder re-traces the handle each GC cycle. The regression test self-tails, mutates event.request.cf to reference the request, and asserts WeakRefs to both objects are reclaimed; without the fix all 16 remain alive. * api: bound pipe write continuations to the IoContext The two Rc<Pipe::State> captures on the destination write promise were the only continuations in the pipe machinery not routed through ioContext.addFunctor. If the IoContext was torn down while a sink write was in flight, the awaitIo completion was destroyed without running, the Write queue entry never settled, and the bare reaction retained: write promise -> Rc<State> -> owner ref -> destination wrapper -> controller queue -> Write resolver -> write promise. Both stream wrappers plus any buffered chunks stayed on the isolate heap until isolate death, once per abandoned pipe (e.g. client disconnect mid-pipe). addFunctor's IoOwn drops the captures at IoContext teardown, matching every sibling continuation.github.com-cloudflare-workerd · e25810ac · 2026-08-17
- 0.3ETVAdd zlib-rs as an autogated backend for node:zlib (#7109) * Add zlib-rs as an autogated backend for zlib compression streams Backs the shared ZlibStream core with zlib-rs (libz-rs-sys), the memory-safe Rust implementation of the zlib C API, selected per stream by the new compression-rs autogate. This covers both node:zlib and the web CompressionStream/DecompressionStream API. The native chromium zlib remains the default. The z_stream ABI is identical between the two implementations, so the integration is a function dispatch table (ZlibDispatch) consulted at stream construction; node's dictionary/params escape hatches route through the same table via ZlibStream::dispatch(). The zlib-rs symbols are bridged from a translation unit that does not include the chromium zlib headers, since chromeconf.h renames all standard zlib identifiers (Cr_z_ mangling) - which is also why the unprefixed zlib-rs symbol names are collision-free in the binary. bench-zlib measures both backends on the streaming workload (1MB in 16KB chunks, level 6): zlib-rs measures ~34% faster deflate and ~17% faster inflate than the SIMD-enabled chromium zlib. * Use zlib-rs throughout workerd * Restore the zlib-rs rollout autogate * Fix downstream zlib header dependencies * Use zlib-rs for the zlib Bazel target Route all zlib consumers through libz-rs-sys while retaining the Chromium zlib headers. Enable carry-less multiplication on x64 to preserve CRC32 performance. * Avoid zlib module dependency on workerd Use the canonical root repository label so the zlib overlay can depend on zlib-rs without changing zlib's module metadata.github.com-cloudflare-workerd · ae4575ad · 2026-08-26
- 0.3ETVFix EventSource use-after-free EventSourceSink queued a gated callback capturing a raw EventSource pointer. If the EventSource was collected before callback execution, the callback dereferenced freed native memory. Carry a weak EventSource reference through the sink and promote it under the isolate lock before dispatch.github.com-cloudflare-workerd · 1883e0fc · 2026-08-22
- 0.3ETVReject substituted JSG wrapper identitiesgithub.com-cloudflare-workerd · 5b88c10b · 2026-08-21
- 0.2ETVCorrect the jsg-visit-for-gc check's type lists and document its model (#7015) * clang-tidy: add regression fixtures for jsg-visit-for-gc The check had no test coverage. Adds positive/negative fixture TUs and a sh_test (consume-test pattern) that lock current behavior exactly: - P1-P5: unvisited jsg::Ref (with and without visitForGc), jsg::Name, kj::Maybe<Ref>, and kj::OneOf alternatives are diagnosed, with an exact-count assertion so lost or extra diagnostics both fail. - N1-N4: fully-visited resources, nested-struct reach-through (visitor.visit(state.func)), standalone plain holders, and jsg-namespace internals are accepted. - The mention-only blind spot (a field named in any expression counts as visited) is locked and documented as current behavior rather than silently relied upon. No change to the check itself. * clang-tidy: remove jsg::Name from jsg-visit-for-gc visitable leaf types jsg::Name's visitForGc is private with only NameWrapper and MemoryTracker as friends, so GcVisitor::visit(name) does not compile: no holder can satisfy a demand to visit a Name field, and none does (NameWrapper only converts values, it does not trace). The demand was unsatisfiable and could only ever be answered with a NOLINT. Not visiting a Name is also correct: the symbol handle is held as a strong root for the holder's lifetime, and a v8::Symbol holds only its description, so it cannot participate in a JS<->C++ reference cycle. The single Name field in the tree (Channel::name, retention bounded by the module's channel map) drops its NOLINT, whose comment wrongly claimed the field was "visited through NameWrapper". Full-tree clang-tidy audit remains clean with the NOLINT removed, confirming the delisting (not the NOLINT) carries the suppression. * clang-tidy: match jsg::Promise<T>::Resolver in jsg-visit-for-gc Promise<T>::Resolver has a public visitForGc tracing the underlying V8Ref<v8::Promise::Resolver>, and the JSG docs have always required holders to visit it, but the check never matched it: Resolver is a non-template class nested in the Promise template, so its printed qualified name embeds specialization arguments and defeated the suffix match. Match the parent record's template name instead. An unvisited resolver pins the promise and its reaction closures, the same leak class as an unvisited jsg::Function. Audited every Resolver field in the tree (18 production sites) before enabling: all are already visited except the two intentionally-strong queue ReadRequest resolvers, which provably cannot be demanded against (plain structs, no visitForGc anywhere reaches their fields). Full-tree clang-tidy (all 509 ClangTidy actions re-executed against the rebuilt plugin) produces zero diagnostics. * clang-tidy: match jsg::Generator and jsg::Sequence in jsg-visit-for-gc The JSG docs list Sequence, Generator, and AsyncGenerator as GC-visitable, but the check matched none of them, and scrutiny shows the docs were only one-third right: - jsg::Generator<T> has a public visitForGc: add as a visitable leaf. - jsg::Sequence<T> is a kj::Array<T> subclass with no visitForGc of its own; it is visitable iff its element type is, via visitAll. Add as a first-arg container and correct the README row accordingly. - jsg::AsyncGenerator<T> has no visitForGc at all, so holders cannot visit one (the same impossible-demand class as jsg::Name). Do not match it; correct the README, which wrongly required visiting it. No fields of any of these types exist in the tree today (verified by inventory), and the full-tree clang-tidy run (all 509 ClangTidy actions re-executed) produces zero diagnostics; this is future-proofing plus doc truth. * clang-tidy: document the jsg-visit-for-gc model and its deliberate limits States the correctness model in the check header: an unvisited handle is a strong root (bounded retention, never use-after-free); visiting is what enables cycle collection and is only safe when the holder is re-traversed every GC cycle from a live wrapper. That is a traversal property the per-record structural check cannot decide, so the check never forbids a visit, and a diagnostic must never be answered by blindly adding one. Documents the three verified blind spots: mention-counts-as-visit (the cost of accepting the KJ_IF_SOME/KJ_SWITCH_ONEOF binding idiom, which an instrumented tree audit shows is the only reason all 10 mention-only sites in the tree pass — all correct), template bodies only being checked where instantiated (probe-verified: called bodies are checked, uncalled ones never run anyway), and plain holders without visitForGc only being demanded against when a visible body reaches into them.github.com-cloudflare-workerd · ab227a29 · 2026-08-17
- 0.2ETVRetain async context frames across scopes (#7096)github.com-cloudflare-workerd · 29d2b7b6 · 2026-08-24
- 0.1ETVRemove obsolete Cache PUT serialization (#6984) Unknown-length Cache API PUTs are serialized even though the runtime quota that required ordering was removed. Concurrent puts of cloned response bodies can deadlock when tee backpressure prevents the first body from completing before the second starts. Allow these requests to start concurrently and add a regression test.github.com-cloudflare-workerd · 9c08f8af · 2026-08-15