Lang design: building no-prototype arrays

46 views
Skip to first unread message

Conrad Buck

unread,
Apr 19, 2026, 12:09:54 PMApr 19
to v8-dev
Greetings from TC39-land! I'm currently working on a proposal for immutable data structures, and there's a puzzle I'm trying to solve that I was hoping someone with knowledge of engine internals might be able to help me out with. In the spirit of the car talk puzzler, here we go:

I'm hoping to be able to use deeply-frozen no-proto objects and arrays as the language basis for records. The advantage here is that there is no risk of prototype pollution, which is appropriate for a data-only structure such a struct/record is.

While I find it extremely promising that the language already contains these objects and has well-defined semantics for them, the trouble I have is that I can't figure out how to construct them in a reasonable amount of time.

The naive way is like this: `Object.setPrototypeOf([], null)` and it's very, very slow.

Is there any faster way to create these objects? I was hoping that `structuredClone` might let me make one the slow way and make more the fast way, but no it copies a no-proto array into an array-proto array.

`new Array({ __proto__: null })` was another idea I had, but proved similarly fruitless as it sets up the array prototype.

If there was a workaround -- a reasonably performant way to get instances -- a Records feature could be introduced to the language with a performant polyfill, making them usable right away. If not, records would be more like a hard-breaking change. I'd really like to understand which situation we're in!

All the best,
Conrad

Ben Noordhuis

unread,
Apr 20, 2026, 5:34:58 AMApr 20
to v8-...@googlegroups.com
Object.setPrototypeOf(arr,null) or arr.__proto__=null is about as fast
as it gets.

I don't know about "very, very slow" - I get 5 million ops/sec on my
crappy 11 years old laptop. Seems plenty fast?

Conrad Buck

unread,
Jun 4, 2026, 10:36:05 AM (8 days ago) Jun 4
to v8-...@googlegroups.com
Well you sent me down quite a rabbit hole, but yes I was able to confirm what you said -- that actually constructing the no-proto objects was not the main cause of the bottleneck.

What *was* a problem was that I was using a WeakMap to cache the is-deep-immutable property, and what I learned after I did my investigation is that it was the sheer number of objects tracked in that one weakmap (millions or more) was the source of almost all the slowdown, which the large chunks of unresponsiveness being caused by the rebuilding of the hash table.

I couldn't see this at all until I changed perf tools. I had been using browser devtools to profile and debug node code, but running the problematic code *natively* in the browser magically causes the "line by line accumulated perf cost" gutter show up so that you can see *where* in a particular function the majority of its costs are incurred. This turned out to be tremendously important because when the expensive call is into a native function like WeakSet.add, the native function will never show up in the flamegraph. I think that's kind of a shame, really, because the absence of that information just makes it look like the parent function containing that call is bizarrely expensive for no reason at all, at least until you get the line-by-line shades-of-yellow view that shows you what's *really* going on


--
--
v8-dev mailing list
v8-...@googlegroups.com
http://groups.google.com/group/v8-dev
---
You received this message because you are subscribed to a topic in the Google Groups "v8-dev" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/v8-dev/GRYbPP8WA_Y/unsubscribe.
To unsubscribe from this group and all its topics, send an email to v8-dev+un...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/v8-dev/CAHQurc954UZoDasL6pPz_574rk_hphBBQj34K8yagH7RNMzpQA%40mail.gmail.com.
Reply all
Reply to author
Forward
0 new messages