Rust: Enable WebAssembly backend by default
hellorust.com
hellorust.com
I know Rust is not the most perfect language for some. For me, it’s really exciting that there is finally a high level language which can scale for every potential use case.
Rust is great for kernels and the embedded space; Excels in the systems space; Is gaining traction in the native application space; Has great tooling for web backends; and, now, with WASM, it’s capable of targeting the web (Yes, without DOM access this is limited, but my understanding is that is coming at some point in the future).
Are there any other runtime free, memory safe languages that can target all of those use cases?
I'm no expert, but from a layperson's perspective, the rust usage in these areas depends on a lot of "unsafe" blocks, fighting the borrow checker, etc.
Does that ratchet it down from "great" for this space to something like "ok" or "usable"?
Or is this just the best we can do in terms of a memory safe language for kernel and/or bare metal embedded?
This isn't unique to kernels. The standard heap-allocated vector type in Rust (on which the standard string type is based) is full of unsafe blocks: https://doc.rust-lang.org/src/alloc/vec.rs.html The problem of implementing a heap-allocated vector, with some elements uninitialized but available for use and with the ability to reallocate the entire vector and copy the members, is not a problem that lends itself well to the borrow checker's view of the world, so raw pointers are a fine way to solve it. Use of vectors themselves, their iterators, references to items in the vectors, etc. does match the borrow checker's view of the world, so the average Rust program running in userspace doesn't have to care that the standard library itself isn't implemented in safe Rust.
So copious usage of the "unsafe" keyword in a codebase can be a sign that the code as a whole is more safe: instead of having large portions of the code be unsafe (or instead of having the code pretend it's containing unsafety when it's not), only the parts that need raw memory access use it, and they maintain invariants used by the rest of the code.
It is certainly possible to do better - there are no machine-checked proofs that unsafe code does what it claims and upholds the invariants it claims to uphold. There could be, and I'm really interested to see what people do with languages that lend themselves better to proofs (usually dependently-typed, Turing-incomplete-but-very-powerful languages). But right now the state of the art is that such languages are a pain to use and writing proofs is even more of a pain. I think that's where the compromise is right now. The borrow checker is, arguably, a system for proving things about your code; it just only knows how to prove specific things. (Way more than C's type system proves, but way less than you'd prove in a perfect world.)
https://i.imgur.com/oqtEDoV.png
From miri, page 27 in their slide deck. The miri project has changed a lot since the slides were written — and it is now close to being merged into rustc.
Once you can say that, you can prove it. The proof techniques are well known. The first step is to be able to talk about it within the language. A predicate like
initialized(A,i,j)
meaning that array A is initialized from elements i to j inclusive is all that's needed.
Then, when you write assert(initialized(A,i,j));
A[j+1] = 0;
assert(initialized(A,i,j+1);
you've expressed how much of the array is now initialized. Then automatic theorem proving takes over and proves that if the first assert is true, so is the second.This eliminates the need for "unsafe" in many places. A fancier type system is not needed.
It only takes one wrong piece of unsafe code to enable a buffer overflow attack.
What exactly makes it "poorly chosen"?
But it disagrees with all past use of the name “vector” in mathematics and numerical computing, which is reasonably consistent and well defined and dates from the mid-19th century.
A Rust (C++, etc) Vec<T> of length n where T implements addition and multiplication in a way that satisfies field laws is the "reasonable" representation of a n length "mathematical" vector over the field T. It just generalizes that storage type to situations where T is not a field.
It doesn't support addition of two vectors and multiplication by a element of T using the normal mathematical syntax, but that's a fairly reasonable choice given how we regularly use vectors as a sequence of independent numbers and the different priorities for math syntax and programming language syntax.
Also more importantly in mathematics vectors aren’t really growable. When you change the number of elements you change the dimension (and thus the “type” for that vector). It is then ill-defined to add two vectors of different dimensions.
On the other hand, a mathematical vector is not a resizable thing: a vector in R² is of a fundamentally different type from a vector in R³.
A type named "vector" should be more like Vec<T, n> for some type T and some integer n. Failing that, the reasonable representation of an n-length mathematical vector, in C++/whatever, is an array.
The idea of appending an element to make a new vector is however extremely common, particularly in induction like proofs.
Vec<T, n> is in a sense already what we have, it's just that we are not capable of storing the n in the type information^0 so we store it at runtime.
^0 For a number of reasons. n can change with mutation while types can't. We don't necessarily know n ahead of time when building a vector (which happens in math too) but we have to know types at compile time. And we just don't have a great way of storing numbers in types in Rust anyways.
Buffer overflows in C are caused by accessing uninitialized memory, regardless of why the program does it (how big is it vs null pointer derefs vs various other ways).
Rust does solve that problem at the language level. There's no such thing as an uninitialized pointer, which is the only right solution I'd argue. If you do access out-of-bounds in a safe block, you panic, not cause a vulnerability. How is the problem not solved by this, combined with iterators?
What you're talking about, tracking uninitialized/initialized at runtime, can totally be done in rust too, e.g. with a [Option<T>] or such... but then you'll obviously pay the runtime price of doing such checking.
The more common case though is someone wishes to have a mutable vector which has a capacity and an accessible subset of data, which Vec implements.
What you're claiming can be proven easily is not so trivial to prove for the common case; it may be `j+n` where `n` may derive from user input.
It would be cool if blocks of rust now could be "theorem-proved-unsafe", but I think realistically it's a huge amount of work for a small win.
Writing and proving theorems about code is typically much more time-consuming than exhaustively testing or informally verifying a block of code via review, and when the unsafe code is small and simple enough, the results we get are already "good enough".
> It only takes one wrong piece of unsafe code to enable a buffer overflow attack.
Yup, though for many programs you can entirely avoid unsafe code (outside of the well-reviewed and well-tested stdlib), and you can pay special attention to unsafe code in review and testing.
That's a damn sight better than other low level non-gc'd languages like C++/C where the default is unsafe and all code is suspect.
You mentioned Vec right after that. Which is interesting, because Vec deals with uninitialized memory just fine. https://doc.rust-lang.org/std/vec/struct.Vec.html#method.res... allocates more space, but doesn't allow you to read it unless you explicitly add elements.
> It only takes one wrong piece of unsafe code to enable a buffer overflow attack.
Isn't that the case for every language? Here, you can limit the unsafe bit to the underlying datastructure if any unsafe actions are required. Maybe Vec uses some unsafe bits, maybe not - but the usage of it is all safe code.
https://docs.microsoft.com/en-us/dotnet/csharp/language-refe...
Among other things, you need to opt in to unsafe with the `/unsafe` compiler flag. I'm pretty much 100% certain that the `unsafe` keyword applied to scopes does not mean "treat the contained scope as if it were safe, but permit the use of unsafe operators" which is how it works in Rust.
Edit: to put some more words here, I think what you may be saying is "the unsafe fragment of C# behaves like Rust", which sounds sane to me: `unsafe` decorates regions and the compiler will complain at you if you use unsafe things outside such scopes. When I said "C#" I meant the larger language in which programs are either "safe" (verifiable) or not, and for which `unsafe { .. }` forces you into the unsafe fragment.
Rust doesn't have safe and unsafe language fragments; there is one language which follows rules similar to (as you say) unsafe C#, but which advertises itself as safe. In that language, `unsafe` moves you back in to "safe" code (or we could agree that it was never safe in the same sense that languages like C# are).
If it were otherwise, no C# program would compile without "unsafe", because large parts of the standard library core (stuff in System) is unsafe. The only reason why "unsafe" is useful is precisely because it lets you isolate unsafe operations in a way that lets safe code invoke into them (assuming that you uphold your safety guarantees).
The /unsafe switch is a different thing - it needs to be turned out for the C# compiler to allow "unsafe" anywhere in its input, and is basically a project-level declaration that "this has some unsafe code somewhere in it".
There are deployment scenarios, like IIS, where admins can disable the execution/loading of Assemblies with unsafe code.
Nor does "unsafe" breaks verification by itself - it's only the specific features that you might use inside (like pointer arithmetic) that would do so, and even then not all of them. For example, C# requires "unsafe" for any use of sizeof() that is not known at compile-time, and for which it needs to emit the sizeof IL opcode. So e.g. sizeof(int) is okay, but sizeof(IntPtr) is unsafe; also, sizeof all structs are considered unsafe. However, the IL opcode for sizeof is not unverifiable, and so such an assembly would pass verification, despite requiring "unsafe" in C#.
The "Weak" problem is mostly a back-pointer problem. If you had some way to talk about a back pointer locked in an invariant relationship with a forward pointer, that would deal with back pointers for trees and such. I sketched out a solution for this on YC earlier this year.[1] This doesn't deal with true circular lists, but those are relatively rare; people tend to use arrays for that today.
It's good to hear this criticized from someone who does program proving. I used to do that, a long time ago. What I usually get from the Rust crowd is macho assertion that they don't need checking of their unsafe code. History and the CERT archives indicate they do.
The advantage is that you should be able to write application code without unsafe blocks; they should be in a library. And the library has a contract: if unsafe behavior does leak outside the library, it is clearly the fault of the library, and can't be blamed on the (safe) caller.
It is better to know that only a specific subset of the codebase could be responsible for memory unsafety even if that behavior may leak. If nothing else, it helps focus reviewing and testing effort.
On some level, yes, it's the best we can do. Unless you want to add hardware semantics into your language. Unless you do, writing two bytes to 0xB8000 is going to be an arbitrary memory access. At least, that's my take.
However, those two calls to unsafe rely on an invariant maintained by the rest of the code that is not marked unsafe. Effectively, the entire module has to be treated as unsafe code by the programmer.
You don't have to go all that way to be better than C or C++. Rust can give you a submodule A whose correctness doesn't impact the safety of any uses of submodule B.
That's true, but the property I described can still help you track down the root cause and limit the number of places that interact with it.
"Rust is not perfect, so it's basically the same as C"
The fact that "american fuzzy lop" and "syzkaller" have gone through the kernel and found dozens of use-after-free's (see https://github.com/google/syzkaller/blob/4bd70f/docs/linux/f...), shows that even incredibly carefully programmed C messes up in totally rote ways.
The basic cleanup code which is able to be exploited here would not be in an unsafe block if this were rust. In fact, because rust abstracts memory management out better, most of the cleanup code wouldn't exist at all.
Rust also allows better abstractions. For example, Rc<T> in rust is much easier to use without screwing up than the manually handled reference counters littered throughout the kernel. It seems obvious to me that numerous deadlocks in the kernel could be avoided by not hand-rolling refcounting everywhere.
You're right that there's still the potential with rust for some code to compromise the safety of the entire kernel.
But with rust that's a subset of code, not practically every single line of the whole thing.
It seems to me like memory corruption/use-after-free/etc only being able to happen in a small fraction of code is better than it being able to happen anywhere.
Rc requires memory allocation. When you boot a kernel you don't have that. Until you set it up, you can't use anything that requires a heap, only stack values.
The best solution I found is to use C code to allocate raw memory and then go through the elaborate process of setting up kernel address space, after which you setup a frame allocator, after which you can setup interrupts and finally use pagefaults to normally use kernel heap.
But wait there is more! During a pagefault interrupt you can't use the kernel heap in a writeful manner that is not inplace. Since a page fault during a page fault is a double fault and thusly your only option is to crash the kernel. You can only read from the kernel heap, modify in place in the heap and write to pagetables.
Same goes for some other interrupts. Which btw, violate a lot of ways Rust code likes to work. An interrupt is like a coroutine but you don't have a nice runtime that abstracts it for you, instead the IRQ just smashes into the active execution and let's hope you haven't held any locks to important kernel stuff at that moment because you will have to crack that lock open.
Sure, the kernel has reusable things like its embedded linked list etc, but they're not type-safe, and they can't represent ownership.
With rust's type-system, a generic rc-like thing could be created which is suitable for the world of the kernel, even if it's not the stdlib Rc.
Without any memory management, things get hard fast, especially considering you still need to parse the memory layout to find out which parts of memory are even usable.
That's heap vs. heap. It comes after the very brief stage you're mentioning.
I tend to think that the driver and network level, which is where most problems in the real world tend to lie, is similar to userland in how often unsafe code gets used and the impact of unsafe code on the correctness of the kernel. In fact, the fact that you can write some drivers in userland on certain OS's--and they can crash without bringing down the whole OS--is strong evidence of this.
You can write some device drivers in userspace. But you can't handle page faults in userspace. Those need to be handled by the kernel and will be 100% unsafe because any mistake will lead to a certain and quick double or triple fault.
Kernel Level Programming is very very different to normal user-level programming. There is no malloc. There is no segmentation. Nothing prevents you from dereferencing a null pointer. In fact, in kernel-land a null pointer is valid memory. You don't have any of the forgiving properties of Ring3 assembly code in kernel-land.
Once you have VMM and Paging setup, preventing a null pointer deref is not hard (simply unpage 0x0)
The question is, how much code is in this state, vs being purely safe? My experience and that of others shows that it's generally a smaller amount than many people not experienced with Rust assume.
Basically, I find the "there's always unsafe code somewhere at the bottom and therefore it's the same as 100% unsafe" to be overly reductive.
I totally agree; it's not the same. However, I think there is disagreement about whether saying only "there's always unsafe code somewhere at the bottom" is better or worse than saying "it's 100% unsafe".
I tend to write some sketchy unsafe code, and the only thing I can say with a straight face is "please don't use this if you care about safety", i.e. treat it as if "it's 100% unsafe". I'm slightly weirded out that Rust has a keyword that amounts to "I understand UB really well; tell others to trust me on it transitively".
I'll be less weirded out when the UB story gets shaken out more, or if cargo gets an "audit-unsafe" option that shows you all unsafe blocks you transitively depend on (does that exist yet?). Still like Rust, but letting randoms write unsafe code is scary.
Are you saying that Rust doesn't have a better memory safety story than C++? In practice, that hasn't been our experience.
I don't use C++ anymore, but I am not clear on how Rust has better memory safety guarantees than C++. I do like its story more, for sure, and I could see how they might turn in to guarantees (but I could also believe that someone could identify a similarly safe fragment of modern C++, and don't want to start that).
Edit: will rust-lang.org take a PR changing "guaranteed memory safety" to "memory safety story"? ;)
I hope not. The value proposition of Rust is that if you stick to writing code without unsafe, then you have a guarantee that you will not fall victim to memory unsafety unless there is a bug in the language or in some unsafe block in a dependency you're using.
Personally, I find your quibbling in this thread pretty strange. Rust very clearly provides more memory safety guarantees than C++. For starters, you could not take the guarantee I just stated above and apply it to C++ because there is no such thing as "C++ without unsafe blocks."
I have used other memory safe languages, and I find their guarantees qualitatively different from Rust. I would say of them "you will not fall victim to memory unsafety unless there is a bug in the language or in the standard library" with no mention of other dependencies. The standard library is then fairly thoroughly audited.
By comparison, you have e.g. the `memmap` crate, which I like and use, written by serious people known to you, where `unsafe` is used in construction of the memory map. It is then on the user to ensure that there are no concurrent writes to the backing file for the lifetime of the map, at the risk of (as I understand it) UB. Would you describe such a program as "safe"? Or at least, can you understand why I might not? If a concurrent modification happens, is the bug to be found in the `unsafe { MmapMut::map_mut(&file)? }` block, or perhaps farther away where you safely open and modify the file before dropping the map?
I'm sorry if this comes off as quibbling; I do actually care about getting these things right, as I want to rely on them and do more interesting things (e.g. de-abomonate mapped memory).
What I should have written is: "you will not fall victim to memory unsafety unless there is a bug in the language or in the standard library, or you explicitly opt out of safety". Using `unsafe` in C# is opting out of its safety guarantees (and requires the `/unsafe` flag); I'm pretty sure none of the C# team would say "guaranteed memory safety" of unsafe C#. I expect the same is true of Java, and have no clue about Python.
To the extent that we are talking about unsafe C#, I think you are right; it's mostly the same as Rust. But, no one would claim that unsafe C# has "guaranteed memory safety" where the guarantee is "as long as your code and all the code you bring in don't have memory safety bugs". At least, I hope not.
My original point was just meant to be that there is the risk of harm when you try and quantify a language as "more safe" because there are fewer regions where unsafe operations are permitted. People might read "guaranteed memory safety" and misunderstand what it means, as evidenced by me not actually knowing what it means.
The two places of unsafe are barely just the minimum.
When interacting with UEFI you have to include a bunch more unsafe clauses since you'll be FFI'ing to PE-calling convention C code to wrap the UEFI methods. You'll have to allocate memory without being able to malloc which means passing a raw memory block, allocate in C, to rust and tell the frame allocator to use that.
Setting up a syscall is unsafe, you have to use a naked function with internal unsafeness and the entire construct is unsafe because a syscall can easily crash the system if it doesn't return properly.
You will have to dereference null pointers because on kernel level a null pointer is a valid address (my UEFI places a memory region at 0x00 for about 30 pages which is a great starting region) unless you waste a buffer region. But Rust and LLVM don't allow that safely at all.
The very nature of a kernel is unsafe. It has to do very unsafe things in an unsafe way to enable software running in Ring 3 to make safe assumptions about it's environment as long as it gets scheduled.
Of course, Rust is somewhat better than C since it's type system allows one to express things without the entire construct blowing up every 2 minutes unless you're developing a kernel for years.
Yes, this is only one subsystem. Obviously, as you say, there are more than that in a realer kernel. But even larger ones have still shown that the vast majority of their stuff works in safe code.
> Setting up a syscall is unsafe
Fun detail, this isn't unsafe anymore on x86! You don't need naked functions; you can
extern "x86-interrupt" fn some_handler()
and LLVM knows how to properly do the codegen! This is kinda emblematic of what I mean: you can do more with safe code than you'd think.On x86, not x86-64, which has the syscall instruction, replacing interrupts for syscalls (technically x86 had sysenter)
The syscall instruction requires writing the address of the syscall function into the LSTAR MSR. Additionally, the syscall entry is purely naked, only the EIP and Segments are preserved and in a way that requires the caller to handle it.
Sysret is also particularly nasty in being unsafe in various ways, including being buggy on all Intel CPUs.
Atleast it's faster than interrupts and you don't technically need the -mno-redzone for it if it's not part of your interrupt handling code.
A memory safe language for the kernel doesn't seem possible using "traditional" safety tools. I'd expect a "safer" language would look a lot like formal verification. If you can make a safer language that doesn't look a lot like formal verification, you can probably automate a lot of formal verification.
There are still some big advantages to rust. By explicitly marking unsafe code, you've made it a lot easier to figure out what needs to be focused on. I imagine you can even formally verify just the unsafe parts of your ecosystem.
Also, rusts tooling just seems more pleasant.
It was a systems programming language based on Algol and already had the notion of unsafe code for low level code.
Those modules had to be marked as UNSAFE and could only be executed if allowed to do so by the sysadmin.
Similar exemples could be provided about PL/S, PL/8, Mesa, Modula-2, Modula-3 and many others.
One big contribution that Rust brings into the table is to educate a whole generation that belives the myth that C was the very first systems programming language, that there are actually safer alternatives.
Safety vs. authorization are completely orthogonal. The former is an objective property of a program, to be established or refuted with formal proof. The latter depends on the subjective whims of one specific human being, in this case, the sysadmin. And, in my experience, the typical sysadmin is not a good enough semanticist to determine whether a program is safe or not.
Formal proofs can also contain logic errors.
Any kind of safety improvement is better than what C offers.
And my point is that this is useless in the absence of someone or something capable of reliably determining whether the code is safe to run.
> Formal proofs can also contain logic errors.
So you have competent people write them.
As for competent programmer, we all know how many corporations search who they hire.
https://rocket.rs/, https://rusoto.github.io/rusoto/rusoto_core/index.html, https://docs.rs/postgres/0.15.1/postgres/, https://docs.rs/kafka/0.7.0/kafka/, https://docs.rs/reqwest/0.8.1/reqwest/, as well as many other supporting libraries. I've not run into many things where there isn't already support.
Also, in terms of networking, if your doing blocking IO, the stdlib is fine. Otherwise, from my experience, I really like https://tokio.rs/ for non-blocking (I know people are torn on this one) and have used it in my DNS impl, https://github.com/bluejekyll/trust-dns
For more info on web and Rust, this is a great page: http://www.arewewebyet.org/
reason == ocaml
As far as I can tell, OCaml has a GC and WASM (at the moment) doesn't so this seems like the only viable solution to me.
Others will have different opinions for sure, and that's fine.
Honestly, I would disagree here, unless we're talking in terms of potential, rather than actual ability right now. There's certainly some level of support, but I really did not get the feeling that Rust was 'great' for embedded, rather that it could be.
My experience is very limited here, but I have used TockOS on one of their dev boards. That was very pleasant, no stdlib makes it a little harder than std Rust, but still felt more deterministic than writing in C.
It's probably even more important for professional embedded engineers, where the hardware decisions may not even be made by the software developers, for various reasons.
Advantage over what? This can be said about the majority of programming languages
For example, I’ve recently done an embedded ARM Linux project where I used C# and .NET Core for higher level parts, C++ for lower level, [DllImport] and C API between the two.
Ease of use is the same when I consuming the C API from e.g. C++. No disadvantage for C#.
C# doesn't support this out of the box, but it can be easily done by post-processing the generated assembly. There's a NuGet package for that.
https://www.nuget.org/packages/UnmanagedExports
I'm not sure if any of that works on other platforms, or with .NET Core. Probably not.
Didn’t work because that recompilation step broke debugger, and invalidated .PDB debug symbols.
I also have some experience with Java, JavaScript and PHP. They don’t.
Python: https://stackoverflow.com/a/33485103/126995
Basic: http://codingdomain.com/visualbasic/win32api/callback/
About the performance… Neither of these languages are particularly fast in the first place :-) But I don't see a reason why these callbacks might be too slow.
So, I wouldn't necessarily say that's a blocker. Obviously everyone should make the best choice given their business requirements, etc.
Now I've left embedded development behind me for a while now, things are mostly Linux on relatively modern ARM in the field where I used to do stuff, so Rust would probably be an option there, but I still have a hard time considering them to be "embedded".
For a certain type of lowish-level programming, yes. But there's a reason that many projects include something like Lua for the majority of the actual application-level work.
To expand a bit, Nim features its own JS backend with a high proportion of the stdlib supporting it. You can access the DOM via the `dom` module[1] and build some pretty cool stuff[2][3]. It doesn't target WebAssembly (yet) however.
On the systems side, Nim compiles to C/C++/ObjC and manages memory via a soft real-time GC (or via a choice of other GCs including boehm).
1 - https://nim-lang.org/docs/dom.html
2 - https://nim-lang.org/araq/karax.html
3 - https://picheta.me/snake/ (source available here: https://github.com/dom96/snake)
Are there any Rust-first UI frameworks rivaling something like Qt, yet?
Disclosure: I only write software for scientific computing and have no interest or experience in web development.
Lol, were you guys making 3D simulations or something? "GC hiccups" usually only become an issue in high-performance-demanding applications.
They use D, with their own GC implementation.
To give you an idea of file sizes, the above produces a 116 byte wasm file. If you leave off the no-std stuff it produces a 3059 byte wasm file.
EDIT: I'm actually being told that there was a bug in wasm-gc that's since been fixed, even with libstd it should be able to eliminate all of it and get the tiny file now.
[1]: https://github.com/killercup/wasm-experiments/blob/bf3b30eed...
[2]: https://github.com/killercup/wasm-experiments/blob/bf3b30eed...
Running the binaryen optimizer on that wasm shrinks it by 6%. Probably more can be done on the Rust side.
[profile.release]
opt-level = "s"
(i.e., optimizing for size), to get it down to 23kB gzipped.That said, this target uses https://github.com/alexcrichton/dlmalloc-rs
(I wanted to try to port semver parsing to the web today, but I hit an LLVM assertion, so it'll have to wait until we can fix that issue. This stuff is still very raw!)
To clarify, this is sarcasm. What I really want to say is when can we run firefox inside firefox? But it's not entirely written in Rust. The point of that question is to illuminate some of the absurdity of web apps. i.e. what function does the outer browser serve in this scenario and why do we need it?
Edit: All the answers point to the outer browser as providing a sandbox. I contend that is supposed to be an OS function. I also think that tabs in the browser came about because OSes didn't provide a decent way to handle multiple instances of applications. See what's happening here?
This is why javascript is so popular with its current monopoly on the web. No other language can do that, but with WASM other languages, such as Rust, can be part of the picture.
That anything that runs into the browser is isolated sandboxed and whatnot is irrelevant. It's still arbitrary code running on often unwilling clients.
Don't believe me? Think about ad-blockers.
I would genuinely like to be able to run Servo inside my un-jailbroken Chromebook. I already run an SSH client in the browser (using Google's Native Client instead of wasm, but same difference), and I don't see why Servo needs access to my SSH private keys. To me, the fact that the only way I can run Servo is to give it access to my entire user profile is the absurd thing.
A good rhetorical question in response is, why is the web so much more popular when using computers than installed applications?
The implication you've made is that there is no benefit to the web, but that is obviously not true, as web usage far outstrips installed applications (besides the browser) at this point. Given this, it becomes necessary to have common tooling to target the different browsers out there.
Getting back to the core of your question, if there was a framework for which you could target all users on the internet (with a single codebase), it ran natively on all platforms, was easily run by everyone merely by clicking on an icon, which then cleans itself up after running... then maybe native apps would be a good avenue for delivering all applications.
It also does not require a second runtime; integrating wasm support into existing JS VMs took very little code compared to an entire JVM or flash runtime.
This isn't precisely true. Before widespread availability of similar HTML 5 features, Flash was used for file upload (especially multiple files w/ progress bars), clipboard access, and pre-websockets network push. Client-side image processing, too, although you might consider that as part of the "big old <canvas>" usage. Your overall point is right on, of course.
Any attempt to use those languages or their existing ecosystems of code on the JVM, Flash, or Javascript will be incredibly fragile and convoluted.
Wasm solves that because it's aware of what it's doing. Java et al do not.
https://bellard.org/jslinux/vm.html?url=https://bellard.org/...
Different sandboxes for different needs. Processes, Jails, Docker, Hypervisors/VMs, Interpreters, they virtualise different resources with different levels of abstraction, and they all have pro's and cons.
I'm not particularly interested in writing entire webapps in Rust but I do see it being useful for smaller self-contained modules inside a larger JS app. Not having a robust binding API makes that difficult.
EDIT: But unrelated criticisms aside, congrats on the progress! I'm excited to see more first class support of WASM from languages other than C/C++.
Seems like you're either converting a lot of strings (which wastes memory) or calling lots of JavaScript string methods from web assembly, which is not likely to be fast in an inner loop.
I hand-wrote a perlin noise generation function in asmjs when that was the future. Then I called my noise function from a loop in javascript to paint an image (1 call per pixel - about 1M calls). Running the perlin noise code with the asmjs engine in Firefox turned out to be a bit slower than running all the code through FF's regular javascript engine. The asmjs code ran fast, but the FFI overhead at the boundary between JS and asmjs made the system slower overall.
Given how easy it looks, I might try the same experiment with Rust & WASM to see where we're at. That said, this is the sort of thing which can definitely be improved over time.
However for complex objects (like Javascript objects, strings or DOM objects) objects you need a serialization mechanism for those. That might be similar to what typical system call implementations do. However system calls mostly have a very simply set of parameters, so transferring Javascript objects might be far worse. If you would want to share Javascript objects on both sides you would also need some kind of reference-counted handles, which can identify the objects.
I haven't done anything with WASM yet, but I'm pretty sure the domains you mentioned (promises, asynchronous programming and also DOM handling) are the ones which will least likely benefit from WASM. Lower level math algorithms might however benefit a lot.
I'm curious whether the fragmentation that would bring on is a net add, or a net drain on the web as a whole.
Replicating the attack surface of the browser in JavaScript land in WebAssembly land goes against the whole design of it.
It's for the community to make nice libraries that expose the browser features to WebAssembly land in JavaScript. It's also the right place to deal with cross browser differences.
In a distant future I would see JavaScript actually compiled to WASM(maybe the browser would pipe it directly into a WASM compiler before to execute it) to reduce the maintenance costs and perhaps also the attack surface you are talking about.
The whole point of WASM was to cut off the middle man (i.e. JavaScript) so that we can reach native-like performance. WASM should be the ultimate dominator not JavaScript.
>> It's for the community to make nice libraries that expose the browser features to WebAssembly land in JavaScript. It's also the right place to deal with cross browser differences.
I believe the community could make nice libraries compiled to WASM that anyone can use regardless of the programming language. JavaScript would be just another language(albeit a popular one, at least due legacy reasons).
I understand and respect the aim -- the browser should be ambivalent about programming languages.
But this is not intention, because it is quite unfeasable. WebAssembly allows one to target the web with much more low-level languages, but that also means that the languages dictate the memory structure of things. In WebAssembly world there are no 'javascript arrays' or 'object hashmaps'.
Imagine mapping the Javascript DOM API surface (with all its dynamic datastructures and callbacks) to C++, Haskell and Smalltalk. Three languages that may compile to WebAssembly, but would have drastically different internal datastructures. And all of that data marshalling that you would need to do, would need to happen for every dom feature for every language that compiles to WebAssembly.
OR! We just implement an API that does nothing more than interop with JS land. This doesn't change often and it will be easy to support many different languages quickly.
Think of Javascript as the BASH of the web. In a shell script you would pipe the output of one written-in-c program to the input of a written-in-rust program. With HTML+JS you would hookup the heavy part of the application and plug the IO's exactly how you want them. Its closer to configuration, really, much like a shell script.
So there needs to be some glue language, and we need to support Javascript and its DOM operations in a way that is completely backwards compatible, and at least as fast (read: tighly coupled) as it is now. And we want to limit the attack surface. There is no other choice than that Javascript will be that glue language.
But its fitting, because Javascript like BASH is a language whose strongest competitive advantage is compatibility. Like BASH it will be the glue language and shell of a platform.
https://developer.apple.com/library/content/documentation/Co...
Aside from keeping emscripten's code smaller / more maintainable and allowing the team to focus more on its role as high-level tooling, this should improve the size and performance of emscripten output since IIRC it's currently missing out on a lot of optimization opportunity by producing wasm as transpiled asm.js.
That's actually not true: the asm.js => wasm path emits better code (smaller, faster) than the wasm backend path currently.
However, the wasm backend path is being improved, and should eventually get to parity.
In that case, it sounds like the LLVM backend will only yield clear user-facing benefits when new features like pthreads are introduced?
The wasm backend does have other benefits, which is why we'd like to move emscripten to use it by default:
* It uses LLVM's default legalization code, so it can handle LLVM IR from more sources (i.e. not just C and C++ from clang).
* We can stop maintaining the out-of-tree LLVM that asm2wasm depends on.
The LLVM wasm backend isn't ready yet (larger output code, slower compile times, a few missing features) but it's getting there.
The webpack team is working on stuff so you can just drop a rust file into your proejct, and it will do everything needed for super easy integration.
https://crates.io/crates/stdweb sort of gives you the bindings you want, by the way.
> better than other languages compiling to WebAssembly?
The best supported languages for this are C, C++, and Rust. So that's really the comparison here, IMO.
I'm looking forward DOM integration with wasm, and potentially a way to compile from scala native to wasm.
The bigger issue is that rustc uses features that aren't in wasm yet, like threads and file loading and such. It'll happen eventually, but not just yet...
I can imagine a world where all dependencies are compiled for WASM, similar to crates.io, and then all apps using those libs would benefit from sharing and load blazingly fast because they only load the site specific code.
Wouldn’t that be a good thing?
First, the stdlib contains a lot of compiler version-specific code so the savings would not be that great unless (until?) it becomes much more stable, along the lines of glibc.
Second, a large portion of stdlib is generics, which are be monomorphized per-app. Further, much of stdlib usage should be inlined and optimized into the app itself anyway.
The portions of the stdlib statically linked into an app will generally always be smaller than the whole stdlib. It may make sense to dynamically link some parts of the platform, like the memory allocator, but the whole stdlib is not a good candidate.
Actual benchmarks are pretty mixed. If you have a cached CDN copy of react it can be a little faster. But if you get a cache miss, the extra overhead of hitting another host to load dependancies is often 10x-100x slower for unlucky users than downloading 80k(?) of additional gzipped javascript. Its especially bad with react because unless you do isomorphic rendering, your page isn't visible at all until react is downloaded.
I suspect Rust's stdlib will compress to a similar size (maybe a little bigger), and the speed saving you're hoping for won't really be worth it in practice; even if you get around the monomorphising problem.
If not, then JIT'ed JS could still be faster than WASM.
If you're doing a large amount of CPU intensive work that can be isolated from the rest of the code that is an ideal scenario for WASM. You have the translation overhead of JS->WASM once, then do the computation, then WASM->JS once to get the result. A small function or code that needs to call back into JS (for DOM access, to query some state in your app, etc) is going to have a lot of those translations so won't see much or any benefit.
The "initial parsing time" advantage of WebAssembly shouldn't be underestimated though, this is important for bigger code bases, and WebAssembly binaries are also a bit smaller (after compression) than the same code as asm.js.
Why do you think that WASM is a good way to go? Why is it better than JS and do you think using it does not endanger the open source nature of the web?
So far I only read, it is as accessible as minifed JS, but actually I know how to read a lot of high level languages, but assemblers look to me much less accessible as high level languages (probably the reason why high level languages were invented in the first place).
So far I understand that WASM is faster during execution and that parsing times are shorter compared to asm.js (and probably JS too).
WASM is especially important to close the performance- and power-efficiency gap that exists on mobile between native applications and Javascript applications. Writing a JS app that doesn't use garbage collection, and doesn't get re-jitted while running isn't trivial, in WASM this is guaranteed. Only the initial parsing is burning a few more CPU cycles compared to a native application, from then on, the difference is small or non-existent.
In the end, WASM is also an important counter-force to the closed ecosystems on Android and iOS. It is the key to have a system that's both open and reasonably secure.
I am not concerned about the view source aspect, but this is actually an important topic to the WASM designers. Browsers have a "view source" on WebAssembly, which shows the ASCII representation, and this is surprisingly readable, since WASM is a higher level representation than traditional CPU assembly (the name WebAssembly is a really poor choice IMHO).
In the end, shipping the high level source code to client devices, and compiling the code there is a massive waste of resources, it's better if this only happens once on the developer machine. The "open source aspect" needs to come from the developer by hosting the original source code on e.g. github, but even if developers want to keep their code closed, the WebAssembly view-source provides enough info to reverse-engineer their code, and with time, better disassembly tools will be created which can recreate a "highlevel representation", stuff like this also exists for traditional executables.
And does the one who deployed the code has to use the non-lossy text format or can you simply convert any WASM binary to something human readable (with browser dev-tools)?
Right now, the text format is the only option. People are already talking about how to make it connect back to the original source, if you have access to it, but that's a work in progress.