Conversation
|
lgtm. To expand: the consensus was that we don't yet have the experience writing polyfills to know whether |
|
Prior discussions (#416 for example, there are others) have raised reasons to not remove |
|
The plan was to get the experience @lukewagner is mentioning, before moving forward with a decision about the feature, for MVP. We collected a list of scenarios back in October. We agreed that, once we have that polyfil experience, but before MVP, to take a critical look at that scenario list and make a better informed decision about a design to address them. So we thought it was important to have an answer, for MVP, to the problem has_feature was trying to solve, but we were not sure what the final design would be. Is this still the plan? |
|
From my perspective, yes. |
|
Yes, that is also my understanding, and that's how we had converged on On Thu, Jan 21, 2016 at 10:55 PM, Luke Wagner notifications@github.com
|
|
If we agree that we need it before finalizing the MVP, and we already have a list of known use cases, why are we removing what we have when it's an extremely trivial opcode? There's a ton of existing software out there using a similar feature-detection model, some of it actively shipping in browsers right now. This is not a high-risk or controversial mechanism. I'm all for us replacing How will we figure out what the right form of feature testing is if we don't implement any form of it to try out? We've already examined the 'just do a crude eval' alternative a couple times in the past, and it's trivial to come up with real-world scenarios where 'eval a test module' is not just awkward but actively insufficient. |
|
On Sun, Jan 24, 2016 at 7:28 AM, Katelyn Gadd notifications@github.com
|
It's been in AstSemantics.md forever. The design seems more than sufficient to accompany the spec, which has contained this feature for months and months: Any lack of implementation re: Furthermore, while I can agree with the premise stated above:
acting based on this premise requires at least a small amount of rigor here. If our new consensus is to pull out what we have (despite it being designed and specced for months) in order to learn about the problem, let's get serious:
At present the plan amounts to 'we'll figure it out later', which is totally fine in most cases but is less than totally fine when it's being used as the justification to remove a tiny opcode that is already designed and already specced. |
|
@kg Personally I don't think it was well designed. It might not meet the security needs of the web to be giving the wasm code access to feature tests at runtime as it would give the wasm code a good fingerprint of the runtime. We might want to explore only giving access to the features information to the layer-1 code, so that it can rewrite the layer-0 code, etc. After some experience, the layer-1 might be standardized so that even the JS can not access these features. I only briefly touched on such issues with one member - it needs a lot more thought. Also I have a use case needing the memory size, and some negotiation, that can not be handled by such a simple feature test alone, but perhaps I could explore it at layer-1. I recall an issue about skipping unknown code, and this is on my mind, but I want to deal with it by adding a data layer that even future code is written in, so that adding more operators can be done by just adding them to the opcode table which will declare the number of arguments, and immediate arguments and optional arguments, and perhaps even their type for checking. With this the runtime has enough information to be able to at least skip unknown operators. This is also needed for view-source, which I presume we don't want to depend on knowledge or particular features. This strategy has limits, but might deal with many feature additions, requiring much less frequent version changes. |
This is a valid point of view, but not if it isn't justified.
Runtime traits will largely be a result of which version of which browser you have installed. You can already detect browser name & version through various methods.
Any feature test based rewriting would allow the target code to identify which features are available. You'd just be making it more awkward.
Features aren't just method calls. Most things can be addressed by
There's been some debate over skipping unknown code and related topics.
Right. And to restate, we have not made any plans to version the format. Whenever it came up so far multiple CG members expressed a desire to never version it. In a versionless model feature detection and an opcode table are much more important. |
Yes, that was basically the feedback I received. But I wanted to find time to understand the issue.
This was also the feedback I received, but it is not immediately clear to me. Just asking for time, and supporting revisiting this later in this regard. Lets assume we have an encoding that allows new operators and new types to be declared, so no new versions are needed. Then it become a matter of a runtime supporting these, not of the runtime supporting the binary format? So the runtime can either fail to validate code that uses any unknown operator or type, or it can signal an error at runtime if such code is executed? For
I am really just supporting re-visiting this later, so we can focus on getting something working. It does not bother me if it's removed now or not and it does not seem to be blocking anything. |
Supporting new operators and types is something you do both at the AST level and the encoding level. The encoding needs to know how to represent new types and operators (by shape, essentially) while the AST level needs to know the semantics so the code using new types/operators can actually be executed. However, operators and types aren't the only things you can add, and they're not the only things you could need to feature-detect.
I don't understand what you mean here. Can you clarify? Runtime behavior of the application has nothing to do with the binary encoding, regardless of layer.
Revisiting later is fine, but we need to have an actual plan for what we're going to revisit and how. |
If new operators and types need extensions to the encoding (matters that can not be accommodated in the operator encoding table) then surely an encoding revision would be needed - something you have already suggested is not practical. For example, if someone wants to add a 128 bit SIMD immediate type then we need to either plan for this in the MVP encoding or they will need to use the existing 64/32/8 bit immediate encodings and repeat them, otherwise an encoding revision will be needed because the binary will not be readable by older runtimes. This could be caught at layer-1 with a feature test and emitting an encoding compatible with the runtime - not by a runtime
Yes, immediate values. There are some operators that currently need special handling, but there are only a few and rest can be declared in a table. What else can you think of? We can explore this now working on abstracting the encoding.
The opcode table will not address the 'opcode available' matter, it just declares their layout and encoding. Only the runtime knows if they are supported. If a module does not validate when unknown operators or types are used then
For example, the layer-1 code negotiates an initial memory size and if memory growth is supported, and then chooses to use index masking on each memory access versus omit the masking, and this would change every memory access - very fine grain code changes. What can Perhaps yielding a static result that would allow the compiler to bake in the choice. But again that does not help if the decision can change from one instance to the next, and this has implications for caching and perhaps security. If supporting baking in choices then I would like to be able to bake in constants too - wasm does not even have the The whole matter of the higher architecture needs to be re-visited. I support revisiting it later and focussing on getting the lower layers working. |
You don't need to extend the encoding, the encoding just needs to be able to accommodate new opcodes and value types (by design, from the beginning). For example, a simd128 immediate is basically just a Abstract decoding like this is also why it would be possible to put small polyfill functions into the opcode table. Many new opcodes can be implemented (with a performance penalty) by raw wasm that receives the opcode's immediates/operands and processes them. It would be up to a compiler or toolchain to generate the polyfills and put them in the opcode table. There are specific types of features that probably can't be added this way, but I can't think of any off-hand.
Layer-1 isn't user code and it's got nothing to do with feature detection or polyfilling. It's just structural compression. You're talking about adapting a loaded module at startup before it runs based on the nature of the target hardware/runtime, which is a good idea but is totally distinct from structural compression. I honestly don't understand what your case has to do with
You could implement the same primitive as a runtime call to an imported function. You could call the import
Again, layer-1 is just compression. But what you describe - yielding a static result - is how this opcode was designed up until this point. It doesn't make much sense if some of the features you feature-detect can go away at runtime - something like that would demand a different mechanism or a dedicated API.
Anyone arguing that |
I think we agree on trying to make the encoding extensible.
Interesting idea for operations that are equivalent to functions. But again it exposes the features to the use code. A privileged upper layer could just rewrite the operations and perhaps without being visible to the user code.
It might be possible to design this to be declarative not procedural, and
Why not just detect before compile time and do the rewriting then.
Again, a privileged upper layer could make these choices.
If wasm code is generating wasm then it could target the same generic pipe that all code goes through, the same pipe that allows the runtime to make these decisions. It could pipe it through the same privileged upper layer which would do any necessary rewriting.
The suggestion seems to be to experiment with layer-1 in user code, and this experimentation could very well explore adapting the code to features etc. It might be just 'structural compression' to you, but if you add some declarative macro support that has inputs from runtime options then it would subsume the
Because the solution would subsume
It would not support fine grain code modification.
That's news to me. I for one would like to explore other upper layer options.
|
Privileged upper layers can solve a great many problems, but if we're going to introduce something like that we have to spec it, and define a way to give it proper security isolation and make it extensible and rev it. That's a lot of complexity.
I'd be in favor of a declarative solution if someone proposed one that was easy to implement. We have neither a proposal nor implementation for a declarative solution right now. "Privileged upper layer code" sounds like a nontrivial thing to spec, even if its only responsibility is this.
No, the information isn't available at compile time. When you run llvm and generate your wasm AST and encode it to binary, you don't know anything about the user's machine. At runtime, the user's machine decodes the AST and maps it to native machine code (or interprets it, or compiles it to javascript). At that point the values for
Because you can't. :-) You could of course do a compile-time target for a given set of feature flags, and do that for every configuration you care to support. Then you can pick the right configuration to load for each module... by detecting the available features.
Funneling JIT code through extra layers is a great way to make JIT so slow that it's unusable. Keep in mind that JITs often run on a method-at-a-time basis.
The current definition of layer 1 doesn't match what you're discussing. What you're suggesting sounds like it could be worthwhile, so do you mind filing a separate issue to propose your redefinition of layer 1? There have been some past discussions about whether we should spec it at all, so that issue is probably useful to have.
This needs to be defined. Those four words don't mean anything to me without context.
Memory size isn't a feature. It's a parameter. You'd query the parameter using some sort of parameter querying function or opcode. GL extensions work this way: You detect the existence of the extension, and the extension can come with new operations, queries, etc.
No optimization is required on the part of the runtime for
Please do not make claims like this unless they are supported. |
Yes, it does seem out of reach to get something like that spec'ed. But we could at least explore the separation and possibility in user code.
Let me clarify 'compile time' - let it be the runtime compiling the wasm into machine code, not the upstream tools. So if the runtime knows that the result of
Rewriting on the client side, but before the wasm runtime compiles to machine code.
Ok, this seems to be a core communication failure.
A runtime parameter query does not support compile time (the runtime compiling to machine code) optimization and thus not the fine grain code change use cases.
Only for the coarse grain use case.
It's a concern, not some shocking allegation, and I believe it is true to the extent that giving the wasm user code access to a features list supports fingerprinting the runtime and it might even help target exploits. Perhaps 'security problem' is a poor choice of words, sorry, but what do you call giving every piece of wasm code the capability to access a fingerprint of the runtime? What's not clear is if we could do any better, and I am only suggesting I would like to revisit this area to think it through. |
|
Oh boy, you two ... I feel like I know perfectly well what both of you are saying, but somehow you're having trouble listening to each other. Or maybe I'm missing something. In earlier discussions, I felt like there was consensus that "layer 1" shouldn't be fully spec'd in MVP, and that therefore layer 1 would be implemented by user code. It wasn't clear to me whether said user code would itself be written in wasm, but I hope and expect so, as there is an immense amount of power in allowing wasm code to introspect and generate other wasm code (us .NET developers love our runtime code generation, and I imagine various programming languages want or need such a feature, especially all those research languages struggling to gain traction...). Ideally, wasm functions could generate other functions that use the same address space, although having two separate address spaces (one for the layer 1 "decompressor" and another used later by the expanded module) would suffice for implementing a "normal" layer 1. But of course, if layer 1 is implemented by user code, there is nothing forcing it to be a mere decompressor; thus it can implement all the wizardry described by JSStats, including implementing I am sympathetic to kg's point that
We could just modify the design docs to say that the feature is likely to be removed before MVP because "blah blah blah layer 1 subsumes it".
I didn't figure out if that's what JSStats meant to suggest (my mental model of wasm remains quite impoverished from merely reading design docs and following discussions..) but assuming your interpretation is correct, my response would be that yes, a feature used inappropriately will have undesirable results. But a feature like this could also enable/augment some really cool stuff - such as metaprogramming, AOP, debuggers implemented in wasm, profiling tools, and garbage collectors. If we can spec it to avoid unnecessary performance hits, I'm not only in favor, I'm salivating. I'm not following the "potential security problem" of |
|
I'll probably follow up on the rest of your replies later, but:
Yeah, I want to make this explicitly clear: When we talk about layer 0 / layer 1 / layer 2 that is specifically in terms of how we compress wasm. One of the key details here is that it needs to be possible for a runtime (like a browser) to implement all three layers in native code without extra buffers or multiple passes. This gives us a path towards extremely good load performance, even on mobile devices. Note that you aren't obligated to do that, so it's certainly possible to start out by implementing those things in user space - doing your layer 2 decompress and passing layer 1 bytes to the decoder, for example - but it's not intended. That's why I keep saying that layer 1 isn't anything more than compression. You can do all sorts of cool stuff by filtering wasm modules before they're compiled, but mixing that with compression means that now all of it has to run in user space, forever, unless we standardize all the filtering and other cool tricks at the same time. It sounds like what you want is to introduce some sort of filter layer defined in userspace that runs at some point before the code is compiled. I can imagine something like that being possible, but I have no idea how I'd spec it and I don't know how happy v8 or spidermonkey or jsc or chakra people would be about implementing it :-) So again, please file an issue about that! |
|
Fwiw #378 |
|
[sent before reply was complete] |
|
Another option is to simply kick "has_feature" up to the embedding level. if (WASM.SIMD != undefined) ... Or even individual opcodes: if (WASM.int64_rotl) ... Not a great solution, but a workaround. I think we need to have some fairly-well baked examples to go into this On Mon, Jan 25, 2016 at 6:46 PM, Seth Thompson notifications@github.com
|
|
@kg Is there a design doc or recorded consensus saying "layer 1 is only used for compression"? I think the layer 1 that will be spec'd out later is currently expected to do compression only. But whatever pipeline is defined by MVP to allow a userspace layer 1+2 would not be disabled, since the feature is useful and we don't want to break backward compatibility. So whatever technique enables a "userspace My question remains: if there's no |
@titzer's point here is a good one - we can just punt this up to the embedder. I think this is an adequate compromise if we're dead-set on removing the opcode. We need to leave some form of this primitive in, though, and we will end up having to standardize how that embedder call works. It leaves out the ability to make runtime behavior decisions, but it's possible that you can funnel all the feature flags in to the module from outside so you can make those decisions.
The idea of putting metaprogramming features into the layered compression pipeline has not come up before now, with the exception of macros - and in particular we seemed agreed on the idea that nullary macros (deduplication) are uncontroversial and good, but macros-with-arguments is hard and would need experimentation to figure out. So: A metaprogramming layer is an interesting idea, but it really doesn't belong in the layered compression pipeline, because it would undermine the viability of the compression pipeline as a whole. It sounds like a great candidate for something to spec in parallel with the core standards process so that it could potentially be introduced post-MVP. It's definitely something you could prototype in user-space, and a runtime could potentially offer the metaprogramming code easy access to decompressed layer0 bytecode so it's easy to do the filtering/rewriting. |
|
Hit 'Comment' too early. One last thing: Even if we strip
We know that new local/argument types will show up eventually, and that new opcodes will potentially have new types of immediates. We also know that new opcodes will have arguments. This all demands that we do a certain amount of work so that existing encoders can decode modules containing these new types and opcodes, even if the end result is that they get rejected as incompatible and punted to a polyfill. The 'contains new opcodes = doesn't decode at all' approach is only suitable for a subset of runtime scenarios, and isn't suitable at all for debuggers or development tools. Though to clarify again: being able to decode new opcodes/immediates is not about being able to execute code depending on either of those things, it's about being able to decode a module and make sense of it. This basically means modules need to have a small schema attached to them (though it doesn't actually need to be INSIDE the file - there are some alternatives like Brotli's static dictionary). |
|
(continuing a conversation @s3ththompson and I had today) Can the embedder optimize Or am I missing something? Again, it's fine to punt, but we have to be OK with the cost. |
|
Another possibility is to make the approach of "probing via eval()'ing E.g. for the "SIMD" feature I can see that this would unlock:
E.g. for the "THREADS" feature this would unlock
E.g. for "MDX" managed data extensions this would unlock:
On Tue, Jan 26, 2016 at 7:40 AM, JF Bastien notifications@github.com
|
|
@titzer I think the opcode table would essentially be that: any unknown operator name string would raise a validation error. Assuming we make opcode tables their own sections and the other sections are optional, you have a super-small module by creating a module containing only an opcode-table section with only the opcodes of interest in the table. What's nice about this is it avoids the need to create an ad hoc collection of feature strings with ad hoc granularity (which is what also started to seem awkward, especially without concrete use cases, with |
|
@lukewagner It's more than just opcodes. We will have new types and new values. An opcode table is not enough, since we need to have binary representations of new types and possibly new values if we allow constants of the new types to be in the binary. Do we really need to have feature detection at the granularity of individual opcodes? It seems to only increase the problem of combinatorics. In some sense this comes down to a question of what we are spec'ing as "standard" and "in-development". Are we specing a binary format? How binary is binary? So far in the binary format, strings do not have semantic meaning, since we have an already agreed upon base set of types, values and operators. To extend this, we can go one of two ways; either add more standard binary encodings, or add a generic mechanism by which strings map to binary encodings (i.e. opcode table). But that then implies the semantic meaning is really in the strings of individual opcodes, individual types, etc. IMHO that's no longer a binary format but an adhoc compression mechanism. That's not just an aesthetics concern; it means work in implementations. Implementations have to understand strings for everything. Today they do not. The other thing is that that inherently separates "base WASM" from all the extensions will we will ever have, unless we have a mechanism to add extensions to "base WASM" and give them blessed, standardized encodings. |
Not coincidentally, I think in addition we also need a "type-code table" :) We can probably delay adding this until after the MVP by saying, just like with opcode tables, if you don't specify a table, there is a default one (which in the MVP, for types, contains the 4 value types). I don't know what you mean by "new values" though?
I don't think we need to but if it's already there it seems better than attempting to define a new thing which forces us to make some ad hoc granularity choices. Maybe ping @s3ththompson; he was saying he talked to some other Webby Googlers who had experience in this space before and they've also said that attempts to define coarse granularity feature tests had been troublesome. Detecting at the individual op level is like wasm's version of the common JS technique of testing the existence of representative constructors/functions.
In the proposed extension where sections have string names then those few blessed section names strings would have semantics; we're just replacing a number with a string (and not worrying about size b/c of low frequency). Similarly, that's what I think we should do with operators, replacing op_code_s with op_name_s. Of course that would bloat us 5x so yes, opcode tables are a compression technique, but, just like variable-length integers et al, it's worth it in layer 0. Now you could ask: do we reallllly need the op names? I think so. During the MVP we have a grace period where noone ships anything until everyone agrees on everything. However, I think it's wishful thinking to imagine that's how it will always be; it's certainly not the case in JS or Web APIs: browsers implement things in (different) priority order and then you have holdouts on various contentious features and that will definitely, imho, happen at some time in the future with wasm and then lead to conflicts/ambiguities over this precious dense opcode index space. And in addition to those "social" constraints, you have the added benefit that if, in the early days, we allocate all the prime 1-byte real estate to the MVP operators, including all the super-cold ones like
This is a good point: with the MVP we'd define a "default" opcode table (what you'd have if you don't declare any table) which would be "base WASM" as you described. However, every quanta of time we'd get everyone to agree on extending this default opcode table (conservatively: so appending, not rearranging) with new opcodes. You can think of opcode tables as a way to avoid having this process be really awkward/political: if a feature doesn't have consensus, it can simply "miss the train". |
Current consensus is to remove has_feature as an operator. This PR removes it from AstSemantics.md.