If you are running untrusted WASM files with wasmedge be aware that it doesn't sandbox by default. WASM can contain native code that will be executed without sandboxing. From the docs [0][1]:
> The wasmedge CLI tool will execute the WebAssembly in ahead-of-time(AOT) mode if available in the input WASM file.
> The wasmedgec can compile WebAssembly into native machine code (i.e., the AOT compiler). For the pure WebAssembly, the wasmedge tool will execute the WASM in interpreter mode. After compiling with the wasmedgec AOT compiler, the wasmedge tool can execute the WASM in AOT mode which is much faster.
They added an option (--force-interpreter) to disable running untrusted native code [2]:
> Thanks for reporting this. I think we can have a flag to disable the automatically loaded AOT sections from an unknown given universal wasm format. One possible situation is that users want to execute a wasm file with interpreter mode, however, they use a universal wasm format received from a malicious developer, and then the bad thing happens.
Hmm, we allow the CLI to execute AOT code sections embedded in WASM files as a convenience feature -- you can do AOT compilation and then execute it on your CLI.
For server side deployment, you should always do the AOT compilation on the server. But I agree this could be more clear. We are adding a new CLI option to disable AOT code segment in the WASM file for people who do not wish to perform the separate AOT compilation step.
I got a bit bewildered by the quick start documentation. It starts by showing how to install and uninstall, followed by running it in different execution environments, it seems to be expecting that you already know what wasmedge is. That's unexpected, as usually a quickstart shows how to write and run a simple application.
One of the challenges is that Wasm supports multiple languages. So, we will have to decide to start from a Rust app or a JS app or something else. Would love your suggestions.
Interesting to see that it has JavaScript support built in. It seems to be based on QuickJS which will work for some things but as they acknowledge quite slow.
> In the end, running v8 in a cloud-native environment often requires a full stack of software tools consisting of "Linux container + guest OS + node or deno + v8", which makes it much heavier and slower than a simple WasmEdge + QuickJS container runtime.
Whether it's faster or not really depends on the complexity of what's being run. After reading about wasmtime 1.0's super fast 5 microsecond snapshot restore time (https://bytecodealliance.org/articles/wasmtime-1-0-fast-safe...) I did some experiments with QuickJS and Javy which runs QuickJS in wasmtime. Rendering a moderately complex web page with React renderToString production 30kB of html on my MacBook Air M1 took:
- Deno first iteration: 12-20ms
- Deno subsequent iterations: 2-4ms
- qjs native: 18-24ms
- javy (qjs wasmtime): 40-60ms
I've not tried WasmEdge but it's JS performance would presumably fall somewhere between that of qjs native and under wasmtime.
Firecracker claims a ~3ms snapshot restore time on ARM so might be the best way to go currently for such workloads but there is certainly a large subset of problems for which the current performance is adequate.
There has been talk of AOT compilation for SpiderMonkey.wasm (https://bytecodealliance.org/articles/making-javascript-run-...) which I hope this will some day happen since it should close the gap substantially. I recently asked on the ByteCode Alliance Zulip but there hasn't yet been any progress and it requires a substantial amount of work.
I remain mystified about the value of running JS inside a wasm VM. I see why people want to go there, because they want one tool to rule them all, but I don't get the actual value add. WASM hasn't been built for this. Not yet. You'll be running the garbage collector, compiler, bytecode interpreter fully in an emulated environment. There's bound to be overhead. For no reason. (And remember overhead isn't just slow processing of customer jobs -- it's energy consumption as well.)
There are already multiple runtimes that can run both WebAssembly and Javascript/TypeScript. The JS runtimes themselves. V8 does a great job of running both wasm and JS. And a custom V8 (or JavaScriptCore or whatever) embedding with appropriate sandboxing can be put together without a huge amount of work.
Seems like the better peg for the hole in question if people want support for JS and WASM in a hosted environment.
One of the benefits of running JS in Wasm (specifically the QuickJS approach) is that you can create JS APIs in Rust. That allows you to move a lot of compute intensive operations into Rust while still giving your developers a clean and nice JS API. WasmEdge does this with its JS Tensorflow API:
After working on a couple codebases that used wasmtime and wasmer heavily, I think if I were to start from scratch I'd just use V8 as my WASM runtime.
(And the model I would follow would be to containerize the V8 runtime piece inside its own cgroup, with resource budgets and permission constraints. cgroups accounting beats the limited and inefficient accounting one gets from the various WASM runtime opcode based accounting systems.)
Personally, I believe v8 is and always will be primarily focused on the browser use case. It’s support for wasi, container tooling will always take a back seat compared with priorities in the browser.
Today, there are a large set of Rust apps that can run in WasmEdge, wasmtime, Fermyon spin, wasmCloud etc, but would not run on v8’s embedded Wasm engine …
My interest comes from wanting to see more performant options for per-request isolation in JavaScript applications. There have been bugs on popular web services returning information for the wrong user caused by inadvertently using global or module level variables.
Isolates are one option but I believe startup time for isolates with a snapshot is actually slower than Firecracker's snapshot restore.
Developing with Firecracker is somewhat annoying since it requires support for nested virtualization (doesn't work on Mac M1 or AWS, though does on GCP and GitHub Codespaces. The M2 has hardware support so development on Mac should work eventually.)
The various WebAssembly runtimes have really put a lot of work into getting fast snapshot restore with Wasmtime 1.0 doing so in microseconds. I'd love to be able to run server rendering in such an environment.
You could take a look at GraalJS. It supports its own form of isolates and they can be created very fast, because an isolate setup is basically mmapping the pre-calculated heap into another area of memory and starting up another thread. I never measured it but it shouldn't be particularly slow. It also means you can mix languages.
Another thing that would be interesting to measure here is memory usage. QuickJS might be slower (as a minimal interpreter) but at least it should be much smaller than a full optimizing JS engine (running normally or compiled to wasm), both in terms of code size and runtime memory usage. That might matter in some cases.
"at least it should be much smaller than a full optimizing JS engine, both in terms of code size and runtime memory usage"
Why should QuickJS use less runtime memory?
Genuine question, I just recently learned of QuickJS and what I understand, it is way smaller in itself, meaning you do not need so much memory for the engine itself, but that does not say anything how efficient it is with executing js code. Or are there benchmarks that say, it is also more memory efficient with executing js?
Either way, yes, I also think meassuring memory is important.
Optimizing VMs often use lots of memory for various reasons. Specific costs in modern optimizing VMs include the memory for the JITed code, and with multiple tiers you end up JITing each function more than once. You also need memory to save information about which functions to tier up (a call count or something more refined). Even things like polymorphic inline caches have a cost, the space for each of those PICs, and the code around them.
A simple interpreter can avoid all those. It can build a very simple and compact bytecode and just execute that.
(But, of course, the speedups in optimizing JITs are usually worth it! 10x or more.)
I'm impressed — I don't think I've ever seen you miss an opportunity to promote your company. I assume you have some kind of keyword alert set up? Kudos on the mad hustle!
Syrus is a technocrat's technocrat and a big open source contributor. I watch from the sidelines in awe as the community builds wasmer and the wasm ecosystem.
I guess you are referring to wizer [1]? (it's the closest to snapshot restore in the WebAssembly world).
In case you are referring to that, the wizer strategy is valid for any Wasm runtime (and it will be fast once the Wasm module is snapshotted by wizer).
If you refer towards Wasm module loading times (once a module is already been precompiled), Wasmer is already one of the fastest ones.
Not OP but I was considering using it as well. I could see it going either way. If Fabrice stays interested in the project and keeps improving it, it could be a great win. If he moves on to other things, perhaps not.
I've been looking at quickjs issues and forks and see a lot of random fixes from various people and most of them don't seem to be accepted or responded to:
The mailing list doesn't seem to have a lot of responses either. So, it's actually hard to say. The first instinct is it's great to base a product on such a talented programmer's work, but it seems you should be proficient enough to debug and fix issues yourself to do that.
No blame or slight to Fabrice, of course, I am always impressed by his work, and he certainly doesn't owe anyone any of his time.
While I appreciate your comparison pages for highlighting the strengths of wasmer, they really do avoid talking about any of the metrics where wasmer isnt top performer. IMHO this doesnt really give me the information I need to make a decision.
For example, the wasmedge website claims it is the fastest runtime but I notice that your comparison page doesnt mention runtime performance at all. It would be nice to know how much slower is wasmer in that respect.
I feel that if wasmer covered things like this in the comparisons, and was highlighting that its only X% slower (even though thats a negative) I could balance the positives accordingly when making a decision about which runtime to choose.
There doesnt seem to be anywhere that provides an unbiased comparison. Maybe wasmer could lead the charge in that respect?
> For example, the wasmedge website claims it is the fastest runtime but I notice that your comparison page doesnt mention runtime performance at all. It would be nice to know how much slower is wasmer in that respect.
The places where there are no differences between runtimes are not showcased in the shared page intentionally.
Runtime performance it's not hidden because Wasmer is slower, it's hidden because there should be little difference on runtime performance between both runtimes.
But just to be clear, Wasmer (when used with LLVM) should be as fast (if not faster) than WasmEdge on runtime performance.
As a random fact: Wasmer 3.0 uses zero-copy deserialization mechanisms that are not yet implemented by any other runtime. So I bet my horses that Wasmer running a pre-compiled Wasm module is not just at-level with WasmEdge but probably faster :)
> There doesnt seem to be anywhere that provides an unbiased comparison. Maybe wasmer could lead the charge in that respect?
Thanks Syrus - I think this is exactly why you should expand on the comparisons. A lot of users without your experience of the internals will be looking to these comparisons for information - being able to explain things like this would go a great way to clearing up misconceptions.
Performance benchmarking is complicated. We have published peer reviewed studies that show WasmEdge is the fastest in some cases. Would love to see how it performs on your workloads.
Where can I find these studies? The only one I can see mentioned on the website is using runtimes over 2 years old and doesnt include either wasmtime or wasmer (which are, AFAIK, the other major runtimes at present).
* Lightweight (no GC, under 2MB)
* Designed to be a secure sandbox (not just a language runtime)
* Modular (add host functions as needed — eg support for Tensorflow)
* Multiple languages (esp compiled languages like Rust)
* Works with container tools (Docker, k8s etc)
Lightweight (no GC, under 2MB) => Which is why all GC based languages are forced to bring their own GC when targeting WASM.
Designed to be a secure sandbox (not just a language runtime) => JVM and CLR also were designed as such. It didn't work out as well as planned, when black hat hackers started researching how to game the sandbox. Still open for WASM.
Modular (add host functions as needed — eg support for Tensorflow) => JVM and CLR have dynamic code loading capabilities. WASM still hasn't standardized dynamic linking
=> Multiple languages (esp compiled languages like Rust) => Just like CLR, hence why it is Common Language Runtime, C and C++ were part of version 1.0 release.
Works with container tools (Docker, k8s etc) => Just like JVM and CLR do.
It's ideal for running untrusted code, as it will only be able to access the external functions it's given by the host. This in contrast with other VMs, where you're effectively giving full system access to the VM.
Besides that, WASM allows for an open universal standard for VMs. This means that we can easily develop with many different languages for WASM.
WASI development is very slow. Bytecodealliance is kinda all the big names but it seems not a lot of investment to hire more people working on the spec (I know at some point more people won't work, but right now there are only a few)
I work on Wasi at Fastly, as part of the Bytecode Alliance.
WASI development has indeed been quite slow. Most of our efforts for the past 2 years have been developing and implementing the Component Model proposal. Right now we are working on porting WASI to the component model, which has been pushing all of the new wit tooling more than it has WASI itself.
I can’t really say that adding more people would have sped up the last few years. The vision of the component model is very ambitious - it’s the first credible open (as in, not owned by Oracle or MS) standard for a real cross language VM, and the security posture supports mutually distrustful components by default. If we had just iterated on the witx version of wasi without coming up with the component model first, I believe both the component model and wasi would be worse off for it.
A lot of the foundational spec work and tooling is now (hopefully!) pretty stable. We used to rewrite everything related to the component model (fka interface types or module linking) every 6-9 months because the spec changed really radically, now I genuinely believe you’ll be able to take todays wit-bindgen to production in 6-9 months of polishing. We are approaching the point where we can get those tools in the hands of way more contributors and the ecosystem can grow.
So yes, the contributors to it today are small, but they are growing (I’ve spent a big chunk of the last few weeks bringing on more contributors from other bca companies) and soon will be able to grow even more.
"the first credible open (as in, not owned by Oracle or MS) standard for a real cross language VM"
It's great that you're developing WASI but I don't see how it's any more open than the JVM or .NET CLR. Obviously from a licensing perspective it's not any different, anyone can go implement the JVM or CLR specs. In the past the JVM and Java even had a rather complicated community process designed to disempower Sun, some aspects of which remain today.
It sounds like WASM/WASI is something similar but with Fastly being the dominant company instead of Oracle/MS. I went to the bytecodealliance github repo and picked a random repository with "wasi" in the name and looked at the last commit. It was by someone who works at Fastly. Technical specs always have some firm or another pushing more than others at any given point, that's not what determines if they're open or not.
Also, both the JVM and CLR have had support for mutually distrusting components from the start, so that's not really new either. That support didn't get widespread use because sandboxing even at the process level for individual apps is still rather rare, and sandboxing at the sub-component level proved too hard to get right with the CLR/JVM approach. It's probably better to argue that what you're doing with WASI is better in some concrete technical sense rather than arguing it's new.
there’s a good write up on this somewhere that you’ll have to forgive me not being able to find tonight on mobile. In general, we want the wat syntax to mirror the binary format very closely, but that’s often at odds with a surface syntax that promotes good software engineering or good developer ux. Two cases for that off the top of my head:
* the component model wat can represent interfaces, but it can’t represent the composition of interfaces, e.g. if we were writing a new interface which opened something file-like and wanted to reuse the oflags type from wasi-filesystem, there would be no way to express that indirectly because we insist wasm binaries are self contained (don’t require you to load other resources), so the oflags type definition would need to be copied in literally.
* when we add resource types back to wit syntax (you can rewind history in wit-bindgen 2 months to find examples, we got rid of them because they were a strawman and now something quite similar is getting speced) it’s nice to have wit syntax appear just like constructors and methods, even if we all know a method desugars to a functions that take a resource as it’s first parameter in the canonical abi. It’s nice to have a distinction in wit that can be used by code generators for creating idiomatic C++, Rust, JS etc methods. Even if we give methods a faithful wat/binary repr (like, e.g. flag types, which are a shorthand for a struct full of bools), the wat/binary form will necessarily have much worse syntax than the wit will, and we think having a syntax that doesn’t send you rubbing your temples reading the component model spec will help adoption.
Speaking as someone who has had to read and write a nontrivial amount of wat over the last 5 years, it’s lovely for small examples and it’s pretty challenging for large, “real world” programs.
Got it. My concern is just fragmentation of compiler/parser tooling in order to also support wit and may introduce compatibility burden to maintain (I know wasm<->wat is 1:1, not sure about wit). However, I'm looking forward to compiling my toy lang to WASI once its MVP is solid.
My outsider's interpretation is that a lot of the details are being held up while the Component Model spec is finalised, so even if lots of ideas are being bounced around and people are experimenting, we won't see a "Snapshot 2" or many new modules (etc.) until that settles.
After that is done, I'd expect to see a lot of focus shift to fleshing out various WASI APIs, which can then also IINM start to be released individually at their own pace.
Ah, I see, you posted your comment a minute before mine. I got my impression from obsessively reading Zulip threads, pull requests, and meeting minutes. It's nice to hear it from the horse's mouth and not just the tea leaves!
WasmEdge is one of the most exciting Wasm runtimes out there. It is part of the CNCF and does support a bunch interesting functionality (wasi-nn, integration with Docker via runwasi, etc)
Disclaimer: I am a maintainer of WasmEdge. WASI-NN allows Wasm to be a wrapper of native tensor frameworks — very much the same way Python is a wrapper for Tensorflow and PyTorch.
The benefit of using Wasm as a wrapper is its high performance (Use Rust to prepare data) and multi language support (inference in JS!)
WasmEdge supports Tensorflow, PyTorch, and OpenVINO as wasi-NN backends.
Ten minutes into reading the documentation and I believe that this is a really important technology. I can't wait to find how to integrate this into my own code.
Given the amount of C++ in this project, I wouldn't run this in an adversarial environment. Meaning, I would not accept untrusted Wasm modules. It is about 100kloc of C/C++ including headers.
I am a maintainer of the WasmEdge project. Yes, it is written in C++ for two reasons:
1 C++ apps can run a lot of edge hardware devices and OSes. We are running on RTOS like seL4 and CPUs like RISC-V.
2 There are already two leading Wasm runtimes written in Rust when we started. We thought runtime diversity is important from security and reliability point of view.
It is indeed more challenging to run C++ programs securely. We are participating Google’s OSS-fuzz program.
Of course, for application developers, WasmEdge provides “Rust first” SDKs and APIs. Almost all of our new features are available in the Rust SDK first. :)
C++ _can_ be written safely, as long as the right static and dynamic analysers are used. So while I wouldn't recommend it for _many_ new projects these days, the WasmEdge authors have good reasons to choose C++ and appear to be using it responsibly.
Also, there are so many things that need to be done right for a VM specifically to actually be safe that merely being written in a safer language is lightyears away from being adequate. You _still_ need very careful design, dynamic analysers, and a whole lot of fuzzing if you want to end up with something you can reasonably trust. So if it were written in Rust as Wasmtime is, that alone wouldn't actually give me much more confidence that it's safe to use it on untrusted inputs.
I say all this as a huge advocate for Rust in general. Sometimes the less attractive tool is actually a better choice.
One can do lots of things. Yes all of those things are still issues, but in enumerating all the things as you have, you make a compelling argument not to use C++. If one picks something else like SML, Go or even Rust, it allows them to focus on those common areas of correctness and not also, all that other stuff.
I don't think you are apologizing for C++, but I hear this a lot, whatabout all the other parts that are fixed by a more sound system? My response is that we only have so many decisions we can make in the day, using a system that removes whole classes of problems allows me to focus on the non-accidental (forced) complexity.
> Rust as Wasmtime is, that alone wouldn't actually give me much more confidence that it's safe
I never made that argument. But let's say I did. Given two systems, one made in Rust and one made in C++. How do I audit the C++ code to ensure that it has the same level of safety as basically _anything_ else?
No application of control flow guard [2], either in the build or in the generated LLVM output.
Infact the only sanitization flags they are passing are -fsanitize=fuzzer,address
0 mentions of 'control flow' in the WasmEdge codebase, 65 mentions in Wasmtime. Although it doesn't appear that Wasmtime is itself being compiled with cfguard.
309 mention of spectre mitigations in Wasmtime, 1 link to a cloudflare blogpost in WasmEdge.
In my cursor look at Wasmtime, I see
Much better fuzzing support, 85 uses of unsafe in cranelift itself, 1400+ uses of unsafe in crates/*
The changelog and git messages for WasmEdge talk about type errors (those type errors look impossible in Rust), bus errors, out of bounds, etc.
It does not inspire confidence. C++ is already starting from behind. This software is targeted to be run on bare metal, embedded and widely distributed. It just doesn't have the level of detail around testing and correctness that I would expect for such a critical piece of infrastructure.
I am not saying that Wasmtime does not also have issues, that would be preposterous. But on the face of it, WasmEdge has a lot of work to do to catch up to where Wasmtime already is.
Thank you for your detailed response. You've looked a lot more closely at what WasmEdge is doing than I did, and I find myself agreeing with you more and more as I read your findings. (I don't know how I got the impression that their tooling use was more thorough than it actually is, but long story short: I was wrong.)
I'm left with one difficult question, though. What should we do when trying to target environments that aren't well-served by languages like Rust? The demand is clearly there, so people will keep building new things using C and C++. What do you see as the most realistic path forward for these use cases? Push harder on platform support in Rust?
I would sincerely be curious to know how many “kloc of C/C++” you are already running in an adversarial environment and how much of a dent this project would make.
How much of a dent (non-quantitative) it would make, depends on how it is deployed. In my environment, the risk of WasmEdge outweighs its advantages.
By that argument there is no difference between 80% and 85% or 1% and 2%. One should absolutely reduce their attack surface. If the goal is to be "lightweight" as compared to containers, then WasmEdge would be running as a regular OS process and not in a jail or cgroups container. By construction, WasmEdge has a greater threat model vs rootless containers. I am huge fan of Wasm, but my appreciation for a technology doesn't outweigh the risk posed by the runtime. Its security properties are intractable.
Are you saying that end users should overlook this?
I think of the edge as a fancy term for services where minimizing I/O is one of the main goals. Which is sometimes, but not always the case with microservices. (Disclaimer: I'm not associated with this project in any way)
I am a maintainer at WasmEdge. :) The name “edge” signifies that it is lightweight. We think it is a lightweight and secure alternative to Linux containers.
The application use cases include containerization on edge devices, serverless functions on edge cloud, embedded functions for databases or SaaS, stream functions for data pipelines, or even smart contracts for blockchains.
All of the above are “edge” use cases in the sense that they are typically outside of mega data centers. :)
Edge typically means that the software is running neither on an end device (e.g. a consumers phone/PC/tablet/TV), nor on the very backend in a big datacenter (like AWS us-east-1 or another region), but somewhere in between. E.g. a "mini-datacenter" close to the user (CDN points-of-presence (POPs) are called edge locations", or some aggregation points in between (e.g. gateway devices in factories that proxy between on-site components and in-cloud components).
The term doesn't have a lot to do with how efficient or non-efficient the software that is run in those places is.
> The wasmedge CLI tool will execute the WebAssembly in ahead-of-time(AOT) mode if available in the input WASM file.
> The wasmedgec can compile WebAssembly into native machine code (i.e., the AOT compiler). For the pure WebAssembly, the wasmedge tool will execute the WASM in interpreter mode. After compiling with the wasmedgec AOT compiler, the wasmedge tool can execute the WASM in AOT mode which is much faster.
They added an option (--force-interpreter) to disable running untrusted native code [2]:
> Thanks for reporting this. I think we can have a flag to disable the automatically loaded AOT sections from an unknown given universal wasm format. One possible situation is that users want to execute a wasm file with interpreter mode, however, they use a universal wasm format received from a malicious developer, and then the bad thing happens.
[0]: https://wasmedge.org/book/en/cli/wasmedge.html
[1]: https://wasmedge.org/book/en/cli/wasmedgec.html
[2]: https://github.com/WasmEdge/WasmEdge/issues/1631