I'm trying to figure out what Extism is actually providing here.
The underlying runtime appears to be Wasmtime, an existing project that you can use separately.
For the system API, Extism supports WASI (a POSIX-like API for WASM) which is already supported by Wasmtime. However it sounds like it also has a different, non-WASI API that provides similar functionality. I'm curious to hear a compare/contrast between these two system APIs, or what motivated inventing a new non-WASI system API. It does appear that WASI is in its early days; WASI was announced in 2019 (https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webas...) but it appears that all of the specific proposals are still early stage and far from being standardized (https://github.com/WebAssembly/WASI/blob/main/Proposals.md).
For the API, Extism provides a C API/ABI that can be called through FFI, but AIUI Wasmtime already has such an API: https://docs.wasmtime.dev/c-api/
Basically I'm trying to understand the conceptual difference between this project and Wasmtime.
It's a good question. It's hard to understand what all is involved in building something like that until you have to get into the details. You can think of Extism as a layer around wasmtime. There is an ABI of sorts that helps you communicate with the plugin: Send data to it, invoke functions, and get data out. With our system, you don't need to enable WASI to use a plugin. You can invoke functions directly. However, you can use WASI for other purposes. And we are working on a way to do POSIX-like stuff (e.g. programs with a main function that use stdin and stdout).
Because we wrap wasmtime, we can also switch it out. That's how we are able to run the same plugins in the browser. There we use the browsers' implementation.
I think one day we will replace some internal pieces of Extism with pieces of the Component Model spec. As of today it's not in a state where we can do all of this in this many languages.
That makes sense, thanks. It sounds like a key difference is that Extism is trying to be portable to both web and standalone WASM runtimes. And maybe part of the motivation for a new WASI-like API is that WASI doesn't aspire to be portable to the web, and Emscripten's standard library only works on the web, whereas your system API is designed to be portable to both.
Linking wasmtime with host functions through the c abi is a bit cumbersome so I believe (in theory) this product can serve a real need by making plug-in integration more streamlined. Wasm as new plugin target / intermediary is a nobrainer (unless your game is written in c# or you like lua)
Hey! Super exciting to see your progress on Extism.
I'm Syrus, from Wasmer. I did a quick review of your plugin architecture and it looks great (tons of languages supported, congrats!). I think Wasmer might be able able to help you so you have the same implementation of the runtime inside and outside of the browser (something similar as we did in wasmer-js [1], where we reuse same WASI implementation, but using the native Wasm engine from the browser).
I'd love to see how we can integrate your plugins into Wasmer as well!
Looks interesting. Based on prior experience, here are some concerns people will bring up with this approach:
1. Spectre. You may have to assume the plugin code can read anything in the address space including any secrets like passwords, keys, file contents etc. If the plugin can't communicate with the outside world this may not be a problem.
2. When you say WASM has a "sandboxing architecture", this is only partially true. It's quite easy to define a simple language that doesn't provide any useful IO APIs and then claim it's sandboxed - that's practically the default state of a new language that's being interpreted. The problems start when you begin offering actual features exposed to the sandboxed code. The app will have to offer APIs to the code being run by the WASM engine and those APIs can/will contain holes through which sandboxed code can escape. If you look at the history of sandboxing, most sandbox escapes were due to bugs in the higher privileged code exposed to sandboxed code so it could be useful, but you can't help devs with that.
3. WASM is mostly meant for low level languages (C, C++, Rust etc). Not many devs want to write plugins in such low level languages these days, they will often want to be using high level languages. Even game engines are like that: "plugin" code is often written in C#, Lua, Blueprint, etc. This is especially true because WASM doesn't try to solve the API typing/object interop problem (as far as I know?), which is why your example APIs are all C ABI style APIs - the world moved on from those a long time ago. You'll probably end up needing something like COM as otherwise the APIs the host app can expose will be so limited and require so much boilerplate that the plugin extension points will just be kind of trivial.
I've been saying it for years, but I think finally 2023 has the chance of being the year in which Wasm GC ships and managed languages start targeting Wasm more widely. We've made a lot of progress with the design, and V8 has a basically complete implementation. Google is targeting some internal apps to Wasm GC and seeing perf improvements over compile-to-JS, so I think this will likely be a success.
That would be cool but I wonder how much difference it will make. Most managed languages share at least two attributes:
1. Large runtimes and std libs. Python's "batteries included" is the epitome of this but even just java.base is large. This doesn't play well at all with browser cache segmentation.
2. You need a JIT for performance.
A good test of whether WASM is really heading towards generality is whether you could implement V8 as https://www.google.com/v8-js.wasm and just auto-include it into HTML pages for backwards compatibility. I know you have unusual experience and expertise in meta-circular VMs - is WASM really heading in this direction? The two obvious sticking points today are: V8 would get downloaded fresh on each origin, and JITd fresh on each page load, and what does such a runtime emit as compiled code?. Is your JITC being JITCd by a JITC and if so is the JITCd output then being JITCd a second time? If so, how on earth does this make sense?
An alternative would be to explore whether the process level sandboxes are now good enough to just allow native code to run inside them and let people use their existing managed language VMs. Google thought that was close to plausible many years ago with NaCL, and kernel sandboxes got a lot stronger since then. It seems we ended up with WASM more due to Mozilla politics than what makes sense technically.
I think there’s somewhat of a disconnect between the original idea of WASM (in browser) versus headless. In the browser folks get JavaScript for free which collects its own garbage. WASM is there to supplement higher level language for performance-intensive tasks and as such, “lower level” languages make more sense for these code paths.
I’d like to point out also that providing users a million languages to write plugins in for a product could create a lot of bloat. Imagine an image editor with 5 plugins, each written in its own language running in WASM sandboxes: golang, C#, assemblyscript, ruby, python. That’s 5 runtimes each running it’s own garbage collection logic.
I can see the value for compute hosts because the very nature of the provided service is allowing users to write sandboxed apps. But I think for stand-alone applications it’s best to support one or two simple targets, whether sandboxed or otherwise.
There are languages (Lua for example) optimized for this already.
I suppose the benefit is that each application which uses the WASM backend can decide on their “official” language and provide a decent built-in IDE experience.
A one size fits all GC is never going to be a great solution though, is it?
Different languages have different GCs that are designed to work with their semantics. Some languages use a flag bit to tell the runtime if a value is stack or heap (Go, Ocaml), some languages allocate almost everything and assume a lot of short lived objects (Java)...
There's no such thing as one-size-fits-all GC. Wasm GC will probably be just as successful as other attempts at generic "managed" runtimes, which is to say, not very.
Lots of good points here. I'll try my best to address them. But you are right, There aren't really complete answers to each of these problems.
Regarding #1, it's good to point out that spectre is still an ongoing problem. wasmtime, the runtime we use, has mitigations for spectre but things will likely come up. I think we need more time and tools to work on things like detecting attacks and mitigating future problems. Fortunately there are some big companies working on it. Our team cannot solve that problem.
Regarding #2, sandboxing here refers to the memory access model and fault isolation. For each capability you give the plugin you may introduce risk. We don't claim to address this problem but I think education and mitigation are good goals for us.
Regarding #3, I think there has been some good progress here. I have an experimental C# plugin PDK and a JS one based on quickjs. We also support Haskell. I think there will be improvement here. And regarding the ABI comment, there are going to be layers on top of this to make it more ergonomic.
At least the common plugin language lua runs in wasm, and micropython is less than a megabyte in wasm, so that point is a bit weak. (having an extra level of abstraction may make the plugins minimally slower, but running lua in wasm adds an extra level of security)
Authors are here to answer questions! To add a little bit of detail:
Extism is an open-source universal plug-in system with the goal of making all software programmable. Easily embed Extism into 13 popular programming languages (server or browser) and safely run high-performance WebAssembly plug-ins inside your code.
What does it mean to "make software programmable"? Simply put, you can give end-users the ability to extend your software with their code.
Plug-in systems are usually implemented in 3 ways:
1. execute a binary that is external to your process (like protoc)
2. re-compile a program with an implementation of some interface
3. dynamically link to native code / dlls
All 3 have major trade-offs on the performance-to-security ratio. Extism provides a way to "have your cake and eat it too" in a sense, that it doesn't compromise security for performance. True, executing WebAssembly as we do here is not running at fully native speed, but it's darn close. And its sandboxed architecture offers the security you want if you're considering an alternative to dll/dlopen.
We're only getting started here, and welcome feedback good and bad. Please join our Discord[0] if you want to chat, or open issues on GitHub[1].
There's at least one more approach to program extensibility that you should probably address: interpreted languages, especially Lua. Many games and utilities today use Lua or JS as an extension language. They have battle-tested runtimes, come with high-performance JITs, and provide the potential for rich interactive debugging and incremental development options that are not free with a WASM-based statically-compiled approach.
There are even entire applications written this way - Adobe Lightroom was originally built from some core C++ imaging code lifted from Photoshop but with all of the UI built in Lua. The dev builds of Lightroom have an entire integrated Lua dev environment in them.
This is really interesting to me. I used to run a team that owned a software library (that you've probably heard of) that runs on Mac, Windows, iOS, Android, and embedded Linux. The product is over a decade old, so it's in C++ because that was the only real choice back then. We bolted on static analysis yet still had senior engineers working full time to iron out stability issues (deadlocks and other race conditions). I've been thinking about alternatives for a while now; Rust naturally, but also C# with Native AOT in .NET 7 (only supports Windows and Linux so far, but maybe in the future). It got me thinking though, why have to choose? There are a good number of languages that can compile to WASM, so maybe just bridge the platform-native presentation layer to WASM and put all the core logic in WASM. It "should" provide near-native performance and open up many other languages, and provide a nice migration path to run the existing C++ and new code in another language side-by-side. I know it's not explicitly the goal, but Extism seems like it could provide that functionality. I'll be following this project!
Very interesting, would love to know more about the library ;)
It does sound like you have found a _great_ use case for WebAssembly, and yes, Extism could provide similar functionality! We call it a "plug-in system" because that's a fairly concrete thing to say, but there are many other ways to use it.
Awesome. Now I just need a way to define a cross-language contract and this would be very helpful for my uses.
Ignoring performance for a second, one wonders if you could take proto and use that as the data in/out _and_ the API/RPC definition. Like many others, I've been wanting to add plugin support to my software, but I want a well-defined contract usable from others w/ complex types and known RPC calls. I think code-gen from proto for all supported client/host languages could get you there (at a perf cost compared to flat buffers or capn proto).
I've had great success with a fourth way: my project language compiles to C, which is loaded at runtime with libtcc (specifically: git://repo.or.cz/tinycc). I've gone down the .so/.dll route a few times in the past, but I can safely say: never again. libtcc has the advantages of a jit (native C speed), but with an elegant API and laudable portability.
If that is something you need come join the discord and let's work on it! I've been experimenting with higher level languages, specifically quickjs (js) and c#. Python should work from what I know, but I can't speak intelligently to how well it would work.
Hey folks, one of the authors here. Extism is a plug-in system. If you aren't familiar with this terminology, plug-in systems make your software programmable by end users such as your customers or your open source community (e.g. VS-Code Extensions). Currently plug-in systems are quite difficult to build and limited to certain host and guest languages. Extism makes this easier regardless of your language or domain knowledge.
Thank you - I think the intro page could help make this clearer. I’m not super familiar with the programmable nature of things beyond something like an Arduino, so I was very confused what “programmable software” meant in this context, and the landing page still isn’t super clear in that regard.
Your comment helps - would make sense to recalibrate on the shared page.
Extism tries to cover two completely different things in one package: 1) runtime 2) component model. And this is a problem. Those things should be separated because the component model would be useful independently, without any ties to the specific runtime environment be it WebAssembly or something else.
We already saw this playbook many times before. For instance, .NET. It has a handy set of system types that define the component model, but surprise, it can only be used in the realm of .NET which makes it unusable in, say, Go. Java - the same thing. C++ is the same. Everything more abstract than some primitive data types and plain functions just cannot cross the boundaries between the language ecosystems.
If we had a standard component model then it would allow us to create a library in one language and then use it from many other languages without any manual work. But all we have now is the old plain C-like API as a common denominator between them. It's 1978 all over again for the last 40 years. But I still believe that we can do better than that.
Instead of m * n complexity when every library is doomed to be manually ported to n languages, we may have just m + n complexity by leveraging the benefits of a standard component model. Just imagine the economic effects of such achievement.
The main problem of an imaginary de-facto standard component model is the danger of being opinionated. We should solve that, otherwise all such efforts will be doomed to fail from the beginning.
Data and functions are good starting points, and they are universal by the laws of math and algebra. That's why they are not opinionated. Consequently, they are used everywhere. We should build a similar formal apparatus for, say, objects to allow OOP interoperability between the languages once and for all.
Another marketing related comment: Extism is not a very nice name. It sounds like exorcism, which apart from being a rather unsympathetic concept, is more about ridding yourself of foreign stuff that’s controlling you rather than the other way round. If anyone has any suggestions, drop them here.
* I am confused about why it's used for a project like this and not some kind of internal NSA project, or maybe a nu-witch house band logo, or something
I intended to invoke thoughts about how Extism enables one to "extend from within", thus the octopus crawling out from the skull. And also that the thing Extism should replace is rather cursed... loading DLLs / dlopen tons of plug-ins into your program to execute untrusted code is downright scary.
Interesting (I think), but the page lacks a problem statement that the project seeks to solve.
As others have noted, "make all software programmable" seems a bit vague. And it's vague because there's no problem in there. When is software not programmable?
I think the idea is to make application extensions easier to integrate (like photoshop plugins), but I'm not sure.
Start with the problem statement, and everything else will fall into place. Leave out the problem statement, and leave your readers mystified.
Point taken. We've been focused on talking to people who have built, or tried to build, plug-in systems. But it's still a confusing concept to the general public without enumerating some examples.
The underlying runtime appears to be Wasmtime, an existing project that you can use separately.
For the system API, Extism supports WASI (a POSIX-like API for WASM) which is already supported by Wasmtime. However it sounds like it also has a different, non-WASI API that provides similar functionality. I'm curious to hear a compare/contrast between these two system APIs, or what motivated inventing a new non-WASI system API. It does appear that WASI is in its early days; WASI was announced in 2019 (https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webas...) but it appears that all of the specific proposals are still early stage and far from being standardized (https://github.com/WebAssembly/WASI/blob/main/Proposals.md).
For the API, Extism provides a C API/ABI that can be called through FFI, but AIUI Wasmtime already has such an API: https://docs.wasmtime.dev/c-api/
Basically I'm trying to understand the conceptual difference between this project and Wasmtime.
Because we wrap wasmtime, we can also switch it out. That's how we are able to run the same plugins in the browser. There we use the browsers' implementation.
I think one day we will replace some internal pieces of Extism with pieces of the Component Model spec. As of today it's not in a state where we can do all of this in this many languages.
edit: rewording, grammar
I'm Syrus, from Wasmer. I did a quick review of your plugin architecture and it looks great (tons of languages supported, congrats!). I think Wasmer might be able able to help you so you have the same implementation of the runtime inside and outside of the browser (something similar as we did in wasmer-js [1], where we reuse same WASI implementation, but using the native Wasm engine from the browser).
I'd love to see how we can integrate your plugins into Wasmer as well!
[1] https://github.com/wasmerio/wasmer-js
I'm trying to figure out if people really need compile to WASM/WASI -> run it back in a WASM runtime.
1. Spectre. You may have to assume the plugin code can read anything in the address space including any secrets like passwords, keys, file contents etc. If the plugin can't communicate with the outside world this may not be a problem.
2. When you say WASM has a "sandboxing architecture", this is only partially true. It's quite easy to define a simple language that doesn't provide any useful IO APIs and then claim it's sandboxed - that's practically the default state of a new language that's being interpreted. The problems start when you begin offering actual features exposed to the sandboxed code. The app will have to offer APIs to the code being run by the WASM engine and those APIs can/will contain holes through which sandboxed code can escape. If you look at the history of sandboxing, most sandbox escapes were due to bugs in the higher privileged code exposed to sandboxed code so it could be useful, but you can't help devs with that.
3. WASM is mostly meant for low level languages (C, C++, Rust etc). Not many devs want to write plugins in such low level languages these days, they will often want to be using high level languages. Even game engines are like that: "plugin" code is often written in C#, Lua, Blueprint, etc. This is especially true because WASM doesn't try to solve the API typing/object interop problem (as far as I know?), which is why your example APIs are all C ABI style APIs - the world moved on from those a long time ago. You'll probably end up needing something like COM as otherwise the APIs the host app can expose will be so limited and require so much boilerplate that the plugin extension points will just be kind of trivial.
I've been saying it for years, but I think finally 2023 has the chance of being the year in which Wasm GC ships and managed languages start targeting Wasm more widely. We've made a lot of progress with the design, and V8 has a basically complete implementation. Google is targeting some internal apps to Wasm GC and seeing perf improvements over compile-to-JS, so I think this will likely be a success.
1. Large runtimes and std libs. Python's "batteries included" is the epitome of this but even just java.base is large. This doesn't play well at all with browser cache segmentation.
2. You need a JIT for performance.
A good test of whether WASM is really heading towards generality is whether you could implement V8 as https://www.google.com/v8-js.wasm and just auto-include it into HTML pages for backwards compatibility. I know you have unusual experience and expertise in meta-circular VMs - is WASM really heading in this direction? The two obvious sticking points today are: V8 would get downloaded fresh on each origin, and JITd fresh on each page load, and what does such a runtime emit as compiled code?. Is your JITC being JITCd by a JITC and if so is the JITCd output then being JITCd a second time? If so, how on earth does this make sense?
An alternative would be to explore whether the process level sandboxes are now good enough to just allow native code to run inside them and let people use their existing managed language VMs. Google thought that was close to plausible many years ago with NaCL, and kernel sandboxes got a lot stronger since then. It seems we ended up with WASM more due to Mozilla politics than what makes sense technically.
I’d like to point out also that providing users a million languages to write plugins in for a product could create a lot of bloat. Imagine an image editor with 5 plugins, each written in its own language running in WASM sandboxes: golang, C#, assemblyscript, ruby, python. That’s 5 runtimes each running it’s own garbage collection logic.
I can see the value for compute hosts because the very nature of the provided service is allowing users to write sandboxed apps. But I think for stand-alone applications it’s best to support one or two simple targets, whether sandboxed or otherwise.
There are languages (Lua for example) optimized for this already.
I suppose the benefit is that each application which uses the WASM backend can decide on their “official” language and provide a decent built-in IDE experience.
Different languages have different GCs that are designed to work with their semantics. Some languages use a flag bit to tell the runtime if a value is stack or heap (Go, Ocaml), some languages allocate almost everything and assume a lot of short lived objects (Java)...
How well will a generic WASM GC really work here?
Regarding #1, it's good to point out that spectre is still an ongoing problem. wasmtime, the runtime we use, has mitigations for spectre but things will likely come up. I think we need more time and tools to work on things like detecting attacks and mitigating future problems. Fortunately there are some big companies working on it. Our team cannot solve that problem.
Regarding #2, sandboxing here refers to the memory access model and fault isolation. For each capability you give the plugin you may introduce risk. We don't claim to address this problem but I think education and mitigation are good goals for us.
Regarding #3, I think there has been some good progress here. I have an experimental C# plugin PDK and a JS one based on quickjs. We also support Haskell. I think there will be improvement here. And regarding the ABI comment, there are going to be layers on top of this to make it more ergonomic.
https://groups.google.com/g/cap-talk/c/NSDM-05fEuU/
There many initiative around wasm, one of them being WebAssembly interface types, aimed at that problem. This makes using wasm simple.
Extism is an open-source universal plug-in system with the goal of making all software programmable. Easily embed Extism into 13 popular programming languages (server or browser) and safely run high-performance WebAssembly plug-ins inside your code.
What does it mean to "make software programmable"? Simply put, you can give end-users the ability to extend your software with their code.
Plug-in systems are usually implemented in 3 ways: 1. execute a binary that is external to your process (like protoc) 2. re-compile a program with an implementation of some interface 3. dynamically link to native code / dlls
All 3 have major trade-offs on the performance-to-security ratio. Extism provides a way to "have your cake and eat it too" in a sense, that it doesn't compromise security for performance. True, executing WebAssembly as we do here is not running at fully native speed, but it's darn close. And its sandboxed architecture offers the security you want if you're considering an alternative to dll/dlopen.
We're only getting started here, and welcome feedback good and bad. Please join our Discord[0] if you want to chat, or open issues on GitHub[1].
[0]: https://discord.gg/cx3usBCWnc [1]: https://github.com/extism/extism/issues
It does sound like you have found a _great_ use case for WebAssembly, and yes, Extism could provide similar functionality! We call it a "plug-in system" because that's a fairly concrete thing to say, but there are many other ways to use it.
Ignoring performance for a second, one wonders if you could take proto and use that as the data in/out _and_ the API/RPC definition. Like many others, I've been wanting to add plugin support to my software, but I want a well-defined contract usable from others w/ complex types and known RPC calls. I think code-gen from proto for all supported client/host languages could get you there (at a perf cost compared to flat buffers or capn proto).
Your comment helps - would make sense to recalibrate on the shared page.
We already saw this playbook many times before. For instance, .NET. It has a handy set of system types that define the component model, but surprise, it can only be used in the realm of .NET which makes it unusable in, say, Go. Java - the same thing. C++ is the same. Everything more abstract than some primitive data types and plain functions just cannot cross the boundaries between the language ecosystems.
If we had a standard component model then it would allow us to create a library in one language and then use it from many other languages without any manual work. But all we have now is the old plain C-like API as a common denominator between them. It's 1978 all over again for the last 40 years. But I still believe that we can do better than that.
Instead of m * n complexity when every library is doomed to be manually ported to n languages, we may have just m + n complexity by leveraging the benefits of a standard component model. Just imagine the economic effects of such achievement.
The main problem of an imaginary de-facto standard component model is the danger of being opinionated. We should solve that, otherwise all such efforts will be doomed to fail from the beginning.
Data and functions are good starting points, and they are universal by the laws of math and algebra. That's why they are not opinionated. Consequently, they are used everywhere. We should build a similar formal apparatus for, say, objects to allow OOP interoperability between the languages once and for all.
doesn't mean anything to me, and I doubt it means much to a lot of others either.
I'm just talking a basic marketing fail here. bhelx below made it make more sense that their whole intro page.
(:
* That's an absolutely viscerally horrifying logo
* I absolutely love it and its design
* I am confused about why it's used for a project like this and not some kind of internal NSA project, or maybe a nu-witch house band logo, or something
Thank you on all 3 points!!
I intended to invoke thoughts about how Extism enables one to "extend from within", thus the octopus crawling out from the skull. And also that the thing Extism should replace is rather cursed... loading DLLs / dlopen tons of plug-ins into your program to execute untrusted code is downright scary.
Not sure I would get the escaping from part by looking at a logo.
As others have noted, "make all software programmable" seems a bit vague. And it's vague because there's no problem in there. When is software not programmable?
I think the idea is to make application extensions easier to integrate (like photoshop plugins), but I'm not sure.
Start with the problem statement, and everything else will fall into place. Leave out the problem statement, and leave your readers mystified.