{	"version": "https://jsonfeed.org/version/1.1",
	"title": "Web Languages Team",
	"language": "en",
	"home_page_url": "https://blogs.igalia.com/weblanguages/",
	"feed_url": "https://blogs.igalia.com/weblanguages/feed/feed.json",
	"description": "Blog of the Igalia Web Languages Team.",
	"author": {
		"name": "Web Languages Team",
		"url": "https://blogs.igalia.com/weblanguages"
	},
	"items": [
		{
			"id": "https://blogs.igalia.com/weblanguages/2026/04/08/counting-total-allocation-for-a-v8-isolate/",
			"url": "https://blogs.igalia.com/weblanguages/2026/04/08/counting-total-allocation-for-a-v8-isolate/",
			"title": "Counting Total Allocation for a V8 Isolate",
			"content_html": "<p>Towards the end of 2025, I had the opportunity to work on a project sponsored by Jane Street to extend V8’s memory profiler API with a new metric: the total number of bytes allocated by an Isolate since its creation.</p>\n<p>The requirement sounds simple: count mutator allocations (i.e., allocations caused by JavaScript execution) while ignoring allocations performed by internal machinery, like GC threads. The motivation was to allow building regression tests that monitor allocation behavior over time. If you want more background on the motivation and initial proposal, I recommend the patch’s <a href=\"https://docs.google.com/document/d/1O4JPsoaxTQsX_7T5Fz4rsGeHMiM16jUrvDuq9FrtbNM/edit?tab=t.0#heading=h.n1atlriavj6v\">design document</a>.</p>\n<p>What initially looked like a small change turned into a surprisingly deep dive into V8’s allocation fast-paths, LAB management, and safepointing infrastructure.</p>\n<h3 id=\"starting-with-allocationobservers\" tabindex=\"-1\">Starting with AllocationObservers <a class=\"header-anchor\" href=\"https://blogs.igalia.com/weblanguages/2026/04/08/counting-total-allocation-for-a-v8-isolate/\">#</a></h3>\n<p>My first instinct was: “V8 already has a lot of allocation profiling infrastructure; surely there might be something close to what I need, right?”</p>\n<p>That question led me to the <a href=\"https://source.chromium.org/chromium/chromium/src/+/main:v8/src/profiler/sampling-heap-profiler.h;l=46;drc=76372353c17d017ad220c51f7514e3b87a9888bb\"><code>SamplingHeapProfiler</code></a>, which internally uses <a href=\"https://source.chromium.org/chromium/chromium/src/+/main:v8/src/heap/allocation-observer.h;l=18;drc=97e7fa75720cf98a23b74404a0b14b9e7ac249d1\"><code>AllocationObserver</code></a>. This already tracks allocations and, importantly, it does not observe GC allocations, thanks to <a href=\"https://source.chromium.org/chromium/chromium/src/+/main:v8/src/heap/heap.cc;l=2380;drc=2b881d2dc67df0eedd3f970b898469a41ade4591\"><code>PauseAllocationObserverScope</code></a> inside <code>Heap::PerformGarbageCollection</code>.</p>\n<p>So I implemented a prototype: a new <code>AllocationObserver</code> with <code>step_size = 1</code> that increments a counter on every allocation, then exposed that counter through an added <code>total_allocated_bytes</code> field in <a href=\"https://source.chromium.org/chromium/chromium/src/+/main:v8/include/v8-statistics.h;l=140;drc=fe81545e6d14397cabb39ba3a5163eedf7624bb1\"><code>HeapStatistics</code></a>.</p>\n<p>This worked, produced correct numbers, and required minimal changes.  However, there was one major problem: The performance overhead was catastrophic. The major reason behind such high overhead is that using allocation observers to observe all allocations means that every allocation in the program will need to go through the C++ observer's infrastructure, and this disables every fast path related optimization. I soon realized why SamplingHeapProfiler is opt-in and must be explicitly started (see <a href=\"https://source.chromium.org/chromium/chromium/src/+/main:v8/src/profiler/heap-profiler.cc;l=231;drc=d6e2c35e6aa83edbc9cb48e5755271fadf748e6c\"><code>StartSamplingHeapProfiler</code></a>). Its observer is attached only at that point. We needed something much lighter than this approach.</p>\n<h3 id=\"linear-allocation-buffers-labs\" tabindex=\"-1\">Linear Allocation Buffers (LABs) <a class=\"header-anchor\" href=\"https://blogs.igalia.com/weblanguages/2026/04/08/counting-total-allocation-for-a-v8-isolate/\">#</a></h3>\n<p>One of the fast path related optimizations is LABs.  A LAB (Linear Allocation Buffer) is a contiguous region where the mutator can allocate memory using a simple bump-pointer. It's created when a mutator requests some allocation, and instead of only allocating what was requested, it allocates more memory to be used by subsequent mutator allocations. Allocations from this buffer are extremely fast, and a huge amount of JavaScript code that triggers allocation is affected by this fast path, such as function declarations, object literals, arrays, closures, strings, etc.</p>\n<p>If an <code>AllocationObserver</code> must run for every allocation, every allocation becomes a slow-path and performance tanks.</p>\n<p>This meant that profiling using observers was not acceptable for something that must always be enabled.\nStill, that prototype was valuable: it served as an oracle to validate later implementations.</p>\n<h3 id=\"the-path-to-negligible-overhead\" tabindex=\"-1\">The Path to Negligible Overhead <a class=\"header-anchor\" href=\"https://blogs.igalia.com/weblanguages/2026/04/08/counting-total-allocation-for-a-v8-isolate/\">#</a></h3>\n<p>While discussing alternatives with my colleague Andy Wingo, he suggested a promising idea: &quot;Instead of counting allocations as they happen, count the LAB size when the LAB is freed. For active LABs, we get their size when the API is called.&quot;</p>\n<p>The intuition for this idea is as follows:</p>\n<ul>\n<li>A LAB contains a contiguous region of allocation.</li>\n<li>Mutator keeps allocating objects using fast-paths. Those will be counted once we inspect current usage on LABs.</li>\n<li>Counting the LAB size when it is freed back to the collector gives us the amount of allocation that happened up to this point.</li>\n</ul>\n<p>To ignore GC allocations, we used <a href=\"https://source.chromium.org/chromium/chromium/src/+/main:v8/src/heap/main-allocator.h;l=331;drc=c6017cd52792bc96202ee86c66aa8fcea7b9f8c2\"><code>MainAllocator::in_gc</code></a>.</p>\n<p>LAB freeing happens in <code>FreeLinearAllocationAreaUnsynchronized</code> [<a href=\"https://source.chromium.org/chromium/chromium/src/+/main:v8/src/heap/main-allocator.cc;l=521\">1</a>] [<a href=\"https://source.chromium.org/chromium/chromium/src/+/main:v8/src/heap/main-allocator.cc;l=913\">2</a>].</p>\n<p>This approach solved the performance issue, but we needed to account for partially filled LABs when the API is queried. That led to adding a helper like:</p>\n<pre class=\"language-cpp\" tabindex=\"0\"><code class=\"language-cpp\"><span class=\"token keyword\">uint64_t</span> <span class=\"token class-name\">Space</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">GetTotalAllocatedBytesInLAA</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span><br>  <span class=\"token keyword\">uint64_t</span> total_bytes <span class=\"token operator\">=</span> <span class=\"token number\">0</span><span class=\"token punctuation\">;</span><br>  HeapAllocator<span class=\"token operator\">*</span> allocator <span class=\"token operator\">=</span> heap_<span class=\"token operator\">-></span><span class=\"token function\">allocator</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span><br>  <span class=\"token comment\">// Here we check spaces that might have allocations in their current</span><br>  <span class=\"token comment\">// LinearAllocationArea that hasn't been freed yet, which means that they</span><br>  <span class=\"token comment\">// weren't added to the total allocation counter.</span><br>  MainAllocator<span class=\"token operator\">*</span> space_allocator <span class=\"token operator\">=</span> <span class=\"token keyword\">nullptr</span><span class=\"token punctuation\">;</span><br>  <span class=\"token keyword\">switch</span> <span class=\"token punctuation\">(</span><span class=\"token function\">identity</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span><br>    <span class=\"token keyword\">case</span> NEW_SPACE<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span><br>      space_allocator <span class=\"token operator\">=</span> allocator<span class=\"token operator\">-></span><span class=\"token function\">new_space_allocator</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span><br>      <span class=\"token keyword\">break</span><span class=\"token punctuation\">;</span><br>    <span class=\"token punctuation\">}</span><br>    <span class=\"token keyword\">case</span> OLD_SPACE<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span><br>      space_allocator <span class=\"token operator\">=</span> allocator<span class=\"token operator\">-></span><span class=\"token function\">old_space_allocator</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span><br>      <span class=\"token keyword\">break</span><span class=\"token punctuation\">;</span><br>    <span class=\"token punctuation\">}</span><br>    <span class=\"token keyword\">case</span> TRUSTED_SPACE<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span><br>      space_allocator <span class=\"token operator\">=</span> allocator<span class=\"token operator\">-></span><span class=\"token function\">trusted_space_allocator</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span><br>      <span class=\"token keyword\">break</span><span class=\"token punctuation\">;</span><br>    <span class=\"token punctuation\">}</span><br>    <span class=\"token keyword\">case</span> CODE_SPACE<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span><br>      space_allocator <span class=\"token operator\">=</span> allocator<span class=\"token operator\">-></span><span class=\"token function\">code_space_allocator</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span><br>      <span class=\"token keyword\">break</span><span class=\"token punctuation\">;</span><br>    <span class=\"token punctuation\">}</span><br>    <span class=\"token keyword\">case</span> SHARED_SPACE<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span><br>      space_allocator <span class=\"token operator\">=</span> allocator<span class=\"token operator\">-></span><span class=\"token function\">shared_space_allocator</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span><br>      <span class=\"token keyword\">break</span><span class=\"token punctuation\">;</span><br>    <span class=\"token punctuation\">}</span><br>    <span class=\"token keyword\">case</span> SHARED_TRUSTED_SPACE<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span><br>      space_allocator <span class=\"token operator\">=</span> allocator<span class=\"token operator\">-></span><span class=\"token function\">shared_trusted_space_allocator</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span><br>      <span class=\"token keyword\">break</span><span class=\"token punctuation\">;</span><br>    <span class=\"token punctuation\">}</span><br>    <span class=\"token keyword\">default</span><span class=\"token operator\">:</span><br>      <span class=\"token keyword\">break</span><span class=\"token punctuation\">;</span><br>  <span class=\"token punctuation\">}</span><br>  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>space_allocator <span class=\"token operator\">&amp;&amp;</span> space_allocator<span class=\"token operator\">-></span><span class=\"token function\">top</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">></span> space_allocator<span class=\"token operator\">-></span><span class=\"token function\">start</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span><br>    total_bytes <span class=\"token operator\">+=</span> space_allocator<span class=\"token operator\">-></span><span class=\"token function\">top</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-</span> space_allocator<span class=\"token operator\">-></span><span class=\"token function\">start</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span><br>  <span class=\"token punctuation\">}</span><br>  <span class=\"token keyword\">return</span> total_bytes<span class=\"token punctuation\">;</span><br><span class=\"token punctuation\">}</span></code></pre>\n<p>…which scanned each allocator’s active LAB and counted the remaining used bytes. This version passed correctness checks against the observer-based oracle.</p>\n<h3 id=\"reaching-the-final-version\" tabindex=\"-1\">Reaching the Final Version <a class=\"header-anchor\" href=\"https://blogs.igalia.com/weblanguages/2026/04/08/counting-total-allocation-for-a-v8-isolate/\">#</a></h3>\n<p>Before approval, reviewers suggested a couple of changes to simplify the logic:</p>\n<h4 id=\"1-count-allocations-when-a-lab-is-created-not-freed\" tabindex=\"-1\">1. Count allocations when a LAB is <strong>created</strong>, not freed. <a class=\"header-anchor\" href=\"https://blogs.igalia.com/weblanguages/2026/04/08/counting-total-allocation-for-a-v8-isolate/\">#</a></h4>\n<p>This lets us place the logic in a single point:  <a href=\"https://source.chromium.org/chromium/chromium/src/+/main:v8/src/heap/main-allocator.cc;l=298\"><code>MainAllocator::ResetLab</code></a>.</p>\n<p>Counting on LAB creation slightly overestimates total allocation because LABs are not always fully used before being freed. In my experiments, the overestimation was around +1%, which we considered acceptable for this API.</p>\n<h4 id=\"2-removing-space-gettotalallocatedbytesinlaa\" tabindex=\"-1\">2. Removing <code>Space::GetTotalAllocatedBytesInLAA</code> <a class=\"header-anchor\" href=\"https://blogs.igalia.com/weblanguages/2026/04/08/counting-total-allocation-for-a-v8-isolate/\">#</a></h4>\n<p>Another suggestion was to avoid scanning active LABs during <code>GetHeapStatistics</code>. Instead, they suggested freeing LABs from all threads before reading the counter, similar to what is done for <a href=\"https://source.chromium.org/chromium/chromium/src/+/main:v8/src/api/api.cc;l=10238;drc=2b881d2dc67df0eedd3f970b898469a41ade4591\">LABs in main thread</a>.</p>\n<p>This required thread synchronization, which led me to investigate...</p>\n<h3 id=\"safepoints\" tabindex=\"-1\">Safepoints <a class=\"header-anchor\" href=\"https://blogs.igalia.com/weblanguages/2026/04/08/counting-total-allocation-for-a-v8-isolate/\">#</a></h3>\n<p>V8 uses safepoints to coordinate all threads so the runtime can safely examine the heap while avoiding race conditions.</p>\n<ul>\n<li>A <code>SafepointScope</code> waits until all threads reach a safepoint.</li>\n<li>Threads periodically call <code>LocalHeap::Safepoint</code> to check if they must park.</li>\n<li>Inside the safepoint scope, we can safely free their LABs and collect per-thread counters.</li>\n</ul>\n<p>I experimented with a version using a safepoint inside <code>GetHeapStatistics</code> and implementing the following method:</p>\n<pre class=\"language-cpp\" tabindex=\"0\"><code class=\"language-cpp\"><span class=\"token keyword\">uint64_t</span> <span class=\"token class-name\">Heap</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">GetTotalAllocatedBytes</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span><br>  <span class=\"token keyword\">uint64_t</span> total_allocated_bytes <span class=\"token operator\">=</span> <span class=\"token number\">0</span><span class=\"token punctuation\">;</span><br>  <span class=\"token function\">safepoint</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">-></span><span class=\"token function\">IterateLocalHeaps</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token operator\">&amp;</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">(</span>LocalHeap<span class=\"token operator\">*</span> local_heap<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span><br>    total_allocated_bytes <span class=\"token operator\">+=</span> local_heap<span class=\"token operator\">-></span><span class=\"token function\">allocator</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">-></span><span class=\"token function\">GetTotalAllocatedBytes</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span><br>  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span><br>  <span class=\"token keyword\">return</span> total_allocated_bytes<span class=\"token punctuation\">;</span><br><span class=\"token punctuation\">}</span></code></pre>\n<p>This removed the need for atomic counters, since each thread had its own local counter.</p>\n<p>The experiment worked and was interesting, but...</p>\n<h3 id=\"returning-to-atomic-counters\" tabindex=\"-1\">Returning to Atomic Counters <a class=\"header-anchor\" href=\"https://blogs.igalia.com/weblanguages/2026/04/08/counting-total-allocation-for-a-v8-isolate/\">#</a></h3>\n<p>Despite the safepoint-based approach being correct, reviewers ultimately preferred the atomic counter solution. I'm not entirely  clear on why they made this decision, but I'd guess that the simplicity of an atomic counter was the major reason.</p>\n<p>Performance evaluation using <a href=\"https://browserbench.org/Speedometer3.1/\">Speedometer</a>, <a href=\"https://browserbench.org/JetStream/\">JetStream</a>, and internal V8 benchmarks (SunSpider, Kraken, Octane) showed no detectable regressions.</p>\n<p>This is the version that ultimately landed.</p>\n<h3 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"https://blogs.igalia.com/weblanguages/2026/04/08/counting-total-allocation-for-a-v8-isolate/\">#</a></h3>\n<p>Even though the initial requirement looked straightforward, implementing this feature required deep knowledge of several parts of V8’s memory subsystem—LABs, fast-path allocation, GC interactions, and safepoints. It was a delightful opportunity to gain a much deeper understanding of how V8 handles allocation at scale.</p>\n<p>After the patch landed in V8, I worked on backporting it to Node.js’s <a href=\"https://github.com/nodejs/node/pull/60573\"><code>HeapStatistics</code></a>. The feature should already be in <a href=\"https://github.com/nodejs/node/pull/60800\">Node.js v25</a>.</p>\n<h3 id=\"acknowledgements\" tabindex=\"-1\">Acknowledgements <a class=\"header-anchor\" href=\"https://blogs.igalia.com/weblanguages/2026/04/08/counting-total-allocation-for-a-v8-isolate/\">#</a></h3>\n<p>Thanks to Jane Street for sponsoring this work; to my Igalia colleagues Andy Wingo, Joyee Cheung, and Romulo Cintra for internal discussions and reviews; and to the V8 and Node.js reviewers who devoted their time to improving the patch.</p>\n",
			"date_published": "2026-04-08T00:00:00Z"
		}
		,
		{
			"id": "https://blogs.igalia.com/weblanguages/2026/01/01/introducing-the-web-languages-team/",
			"url": "https://blogs.igalia.com/weblanguages/2026/01/01/introducing-the-web-languages-team/",
			"title": "Introducing the ‘Web Languages’ Team",
			"content_html": "<p>For a long time at Igalia, we've had our fantastic Compilers team, which consisted of people who worked on a wide array of projects — all the way from Low Level Compilers (LLVM) to JS Compilers (V8, SpiderMonkey, etc), JS Transpilers (Babel), NodeJS as well as foundational JS libraries (PDF.js).</p>\n<p>This big-tent approach served us well for a long time. However, with expansion of our work in the Web Languages space, we realized it was finally time to form the new 'Web Languages' team which has a sharper focus on shaping JavaScript standards via TC39, WHATWG, and WinterTC.</p>\n<p>You can visit our <a href=\"https://blogs.igalia.com/weblanguages/about/\">about page</a> for more details, and read more about our work from last year in this <a href=\"https://blogs.igalia.com/compilers/2026/02/06/igalia-s-compilers-team-a-2025-retrospective/\">blog post</a>.</p>\n<p>This is a new chapter for us, we’re very excited to grow this dedicated Web Languages focus to keep expanding our scope and impact on the standards and tooling across the ecosystem.</p>\n",
			"date_published": "2026-01-01T00:00:00Z"
		}
		
	]
}
