Skip to content
This repository was archived by the owner on Apr 25, 2025. It is now read-only.

Un-restricting globals initialization #189

Closed
jakobkummerow opened this issue Jan 29, 2021 · 9 comments
Closed

Un-restricting globals initialization #189

jakobkummerow opened this issue Jan 29, 2021 · 9 comments

Comments

@jakobkummerow
Copy link
Contributor

This proposal currently marks i31.new, rtt.canon and rtt.sub as constant instructions, which means they can be used to initialize globals. Notably, struct/array allocating instructions are not categorized as constant (whereas ref.func from the function-references proposal is). I got the following feedback from the J2CL team:

We are storing our vtables (which are structs) in globals and it would be nice if those could be initialized in the global definition and declared immutable rather than having them mutable and initialize them in the start function.

Is there a particular reason to be so restrictive about which instructions are allowed in global initializers? Can we loosen the restrictions somewhat?

@fgmccabe
Copy link

The pattern of 'build & freeze' is actually very common and not restricted to global variables.
If we are going to relax restrictions on global initializers significantly then I suggest we take the pattern more seriously.

@tlively
Copy link
Member

tlively commented Jan 29, 2021

@fgmccabe what would that entail?

@fgmccabe
Copy link

Off the top of my head, some way to 'seal' a variable. One of the attractions, for me, of the maligned let instruction, was that it let me express this pattern pretty well. But that does not cover the 'build an object' use case.

@RossTate
Copy link
Contributor

I've been wondering about this pattern. Related question for @jakobkummerow in order to start answering @tlively's question: are rtts implemented with a cyclic data structure? In particular, if we consider an rtt "foo", does the cast table stored inside "foo" itself contain a pointer to "foo" (so that "foo" values can be rtt.cast to "foo")?

@jakobkummerow
Copy link
Contributor Author

@RossTate : Currently no: in each RTT, we store its list of super RTTs, excluding itself. For casting, we special-case that case, so (rtt.cast obj rtt) first checks whether obj's RTT exactly matches rtt, and only otherwise loads the list of supers.

That said, I consider this an implementation detail that I don't want to make any long-term promises about. We implemented what seemed convenient, we may change it in the future (and have, in fact, already changed it in the past), other engines might implement it differently. (I also don't see why it would affect variable/object sealing.)


On the larger topic: aside from immutability, another strategic benefit of allowing more different instructions in global initializers (which in turn allows more work to be done there, as opposed to the start function) is that the start function runs unconditionally and "eagerly", whereas for global initializers, engines have the option to execute them lazily, i.e. on first use of the respective global. Considering that the specific use case here are vtables, if you assume that large applications might well have thousands of vtables, this might be a very significant argument for application startup performance.

@RossTate
Copy link
Contributor

RossTate commented Feb 1, 2021

Thanks for the info!

The reason I asked is that it's illustrative of the kinds of things language runtimes will have to implement within WebAssembly, which in turn guides how one would design support the "build and freeze" pattern. Many runtimes use immutable cyclic datastructures behind the scenes, and I've been wondering how we would be able to make WebAssembly support that pattern.

The "build and freeze" pattern is important for separate compilation as well as for ensuring the memory safety of some efficient data structures (such as flattened interface tables) in multi-threaded settings.

But I don't know the plans for supporting these features or use cases, so it'd be useful to hear what @rossberg has planned, rather than conjecture.

@rossberg
Copy link
Member

rossberg commented Feb 3, 2021

Closely related is @sbc100's recent extended const proposal. Further extending the class of const expressions with some of the allocators in this proposal makes total sense to me.

As for RTTs, I don't see any issue for now -- as is, rtt.canon can handle (mutually) recursive types just fine without exposing the need for value recursion on the Wasm level. Whether and how it uses recursive data structures internally remains an implementation detail.

That said, recursive initialisation is a well-known pain point. In the MVP, you'll have to use mutable vars/fields for that. Post-MVP we likely want the ability to turn fields readonly, e.g. via subsumption. That probably won't cover all use cases either, but there is no general solution really.

@RossTate
Copy link
Contributor

RossTate commented Feb 3, 2021

That probably won't cover all use cases either.

To be clear, it won't cover any of the use cases I mentioned above.

but there is no general solution really

Published works can already address these use cases (except for cyclic structures, which requires an extension), as they're used by major languages that have been compiled to typed assembly languages.

@tlively
Copy link
Member

tlively commented Apr 4, 2022

We now have many more instructions marked constant, so I will close this issue. We can open new issues for specific new potential constant instructions based on the current state of the proposal.

@tlively tlively closed this as completed Apr 4, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants