4 ms·
I really have trouble understanding the push to provide higher-level features as intrinsic types and instructions. AIUI, the main alternative to stringref (lin
by haberman 3y ago
I really have trouble understanding the push to provide higher-level features as intrinsic types and instructions. AIUI, the main alternative to stringref (linked in the article: https://github.com/WebAssembly/js-string-builtins/blob/main/proposals/js-string-builtins/Overview.md https://github.com/WebAssembly/js-string-builtins/blob/main/...) is to provide string types via WebAssembly's normal "import" mechanism. This alternative makes more sense to me.
When you write extension modules for languages like Python, Ruby, PHP, or even JavaScript (via V8 or JavaScriptCore), you are always importing APIs like strings (eg. #include <ruby.h>). It seems natural to me that WebAssembly would be the same way.
Now ideally a WebAssembly module wouldn't be specific to one particular embedding environment. You would want to standardize this string API, so that a single API can be used efficiently with multiple host languages. So you'd want something more like #include <wasm/wtf16string.h>. But if it's an import, it also leaves open the possibility that such an API can evolve over time, or that a competing API could supplant it as the landscape of language engines changes. By putting it directly into the instruction set, you'd be baking in assumptions about how JS engines in 2023 function, even though those assumptions can (and hopefully will) change over time.
It's true that wasm/wtf16string.h probably won't be available in environments that don't have JS-style strings. But that seems for the best. I'd rather have a leaner WebAssembly than make every WebAssembly engine provide JS-style strings. In the case that the host environment doesn't already have a JS-style string that you are trying to interoperate with, why not just have Java implement its string type directly in WebAssembly? Or once shared libraries are available, wasm/wtf16string.h could be provided via some third-party library, shared across multiple modules, leaving open the possibility of passing strings around between modules.
I guess, to the article's point, why shouldn't memcpy() be an import also? What is gained by making it part of the instruction set? I assume that a WebAssembly implementation could recognize special imports, and optimize them the same as if it were a built-in instruction.
- csjh 3y agoI think string builtins seem to be the direction the wasm committee is moving towards anyways? Unless I'm mistaken
- davexunit 3y agoYeah, the JS String Builtins proposal is an alternative to stringref. Having experienced some of both, I think that stringref is much better.
- csjh 3y agoYeah, builtins seem like a bit of a specialized case. If I'm not mistaken, it doesn't even really give a Wasm-side method to create strings? Bit of a shame.
- davexunit 3y agoRight, inside the wasm module you're on your own and you use (array i8). Alternatively, you could decide that your strings are the host's strings and use (ref extern), but performance within the wasm module suffers because every string operation is a host call. The least worst option right now is (array i8) internally and copying whenever they cross the guest/host boundary.
- davexunit 3y ago> I really do not understand the push to provide higher-level features as intrinsic types and instructions As mentioned in the article, the purpose of WebAssembly is not to provide the lowest level instruction support possible but to be a good compilation target. Not having a string type makes targeting WASM GC more complicated and the runtime less efficient. > By putting it directly into the instruction set, you'd be baking in assumptions about how JS engines in 2023 function, even though those assumptions can (and hopefully will) change over time. The stringref proposal does a good job of balancing the world we exist in with the "right thing", IMO. I implemented a subset of it for an interpreter hosted in Guile Scheme, using Guile's string type which is very unlike Java/JavaScript (not UTF-16), and it works well. I'd like to see the proposal get more support.
- haberman 3y agoI don't understand how the counter-proposal makes WebAssembly a worse compilation target. Take the simple example of memory.copy. How is it worse to compile memcpy() to import+memcpy() rather than memory.copy? Why can't a string type be efficiently GC'd via an imported type?
- slaymaker1907 3y agoTo work with GC, you need some way to track if the GC'd object is accessible in WASM itself. You can't just have gc.release $addr because then you need to introduce a check everywhere you try and do something with $addr in WASM as WASM is supposed to be memory safe. The reason why you probably need a custom string type is so you can actually embed string literals without relying on interop with the environment. If a WASM module tries to simulate this by having an initialization function that constructs all your string constants in linear memory or something, I could see that getting pretty expensive and/or difficult to optimize.
- haberman 3y ago> To work with GC, you need some way to track if the GC'd object is accessible in WASM itself. I've never heard of a GC with that kind of API. Usually any native code that holds a GC reference would either mark that reference as a root explicitly (eg. https://github.com/WebAssembly/design/issues/1459 https://github.com/WebAssembly/design/issues/1459) or ensure that it can be traced from a parent object. Either way, this should prevent collection of the object for as long as the reference is reachable from a root. I agree that explicitly checking whether a GC'd object has been freed would not make any sense. > The reason why you probably need a custom string type is so you can actually embed string literals without relying on interop with the environment. WASM already has ways of embedding flat string data. This can be materialized into GC/heap objects at module startup. This must happen in some form anyway, as all GC-able objects must be registered with the GC upon creation, for them to be discoverable as candidates for collection. Overall I still don't understand the issue. There is so much prior art for these patterns in native extensions for Python, PHP, Ruby, etc.
- chubot 3y agoI believe it's mainly because of the requirements for (1) integration with a single GC (the one in the browser), and (2) zero-copy inter-op with JavaScript strings. If you take them as axioms, those pretty much force you to put strings as a native type in WASM. They can't really be done as libraries. Not sure if it helps, but I wrote about my experience writing a GC here, and the "reality sandwich" metaphor: https://www.oilshell.org/blog/2023/01/garbage-collector.html https://www.oilshell.org/blog/2023/01/garbage-collector.html https://lobste.rs/s/v5ferx/scheme_browser_hoot_tale#c_n87bzw https://lobste.rs/s/v5ferx/scheme_browser_hoot_tale#c_n87bzw https://lobste.rs/s/v5ferx/scheme_browser_hoot_tale#c_mw6tfx https://lobste.rs/s/v5ferx/scheme_browser_hoot_tale#c_mw6tfx The sandwich has: (1) the mutator's view on one side (2) the single bit representation of data types in the middle (3) the GC's view on the other side Basically a bunch of people thought that you could just have one half of the sandwich -- you could just have the GC, and use whatever data types you want. Implementing a GC will disabuse you of that notion (ESPECIALLY a GC that runs remote, untrusted code, but it's true for any GC.) The core data types and the GC are tightly coupled, and the GC already lives in the browser. So WASM necessarily gives you the WHOLE sandwich -- both data types and the GC. Fundamentally, it can't do anything else. But that means that the data types are a huge design compromise -- there are winners and losers. Now you have a MAPPING problem from every language to WASM types. --- As a concrete example, I mentioned that Go has slices, which are reference types, and they have pointers to anywhere in a string. This means the GC has to be able to find the head of a string from an interior pointer, which is hard in general. So, without being very close to WASM GC, I suspect Go will be a loser in this respect (it will perform less well), simply because JS GC's don't have to deal with interior pointers. Every design decision involves winners and losers -- "universal VM" is a bit of a fallacy. --- Strings are another area where there are winners and losers -- UTF-16 means JS/Java perform better, but UTF-8 means other languages perform better. Although this stringref proposal is interesting because it tries not to be biased -- it tries to develop an API that can be implemented with both UTF-8 or UTF-16 representations. Still, it's very hard problem and involves deep compromises.
- haberman 3y ago> They can't really be done as libraries. It's definitely the case that you can't implement a string type in WASM and have it interoperate with JavaScript strings. But there is no reason that JavaScript strings can't be a "special" kind of import that provides functionality implemented by the engine. In other words, #include <wasm/wtf16string.h> could provide an API for the built-in JavaScript string, much like #include <ruby.h> provides access to the native Ruby string in Ruby extensions. I believe this is how the counter-proposal works: https://github.com/WebAssembly/js-string-builtins/blob/main/proposals/js-string-builtins/Overview.md https://github.com/WebAssembly/js-string-builtins/blob/main/... For engines that do not have a built-in JavaScript-like string type, the wasm/wtf16string.h library could be implemented in WASM as a polyfill. In that case, there is no native string to interoperate with, so no disadvantage to implementing directly in WASM (except perhaps a bit of speed penalty).