spin icon indicating copy to clipboard operation
spin copied to clipboard

I’d really like to opt-in to Wasm features

Open swankjesse opened this issue 3 weeks ago • 17 comments

With a significant amount of effort I was able to write ‘hello world‘ in Kotlin and run it on Spin! Hooray!

Image

One major roadblock I faced was Spin’s lack of support for 4 opt-in features in Wasmtime:

WasmFeatures::GC
WasmFeatures::REFERENCE_TYPES
WasmFeatures::EXCEPTIONS
WasmFeatures::FUNCTION_REFERENCES

I was able to get it working locally by changing Spin’s Wasmtime configuration. I enabled the required features in crates/core/src/lib.rs:

 impl Default for Config {
     fn default() -> Self {
         let mut inner = wasmtime::Config::new();
         inner.async_support(true);
         inner.epoch_interruption(true);
         inner.wasm_component_model(true);
         inner.wasm_component_model_async(true);
+        inner.wasm_gc(true);
+        inner.wasm_reference_types(true);
+        inner.wasm_exceptions(true);
+        inner.wasm_function_references(true);
         // If targeting musl, disable native unwind to address this issue:

REFERENCE_TYPES is in WasmFeatures::WASM2, and the other ones are in WasmFeatures::WASM3.

I need this capability to run Kotlin programs in Spin. I expect other languages would similarly benefit from their availability.

One solution would be to let me opt-in to Wasm features in my spin.toml:

[component.rounds-experimental]
source = "rounds-experimental.wasm"
allowed_outbound_hosts = []
require_wasm_features = ["gc", "reference_types", "exceptions", "function_references"]

An alternate solution would be for Spin to automatically inspect my .wasm file and enable the Wasmtime features required to run my app. Perhaps it could issue a warning if any experimental features are used.

If you’d like me to submit a PR to implement this behaviour, I’d be quite happy to. But I won’t proceed without your blessing. You all might have better ideas!

swankjesse avatar Dec 04 '25 02:12 swankjesse

Thanks @swankjesse for your perseverance with Kotlin and for the offer of help!

@dicej @lann @tschneidereit Would you have any concerns around stability or maintainability if these features were to be turned on? I'm not sure of their status w.r.t. the Wasmtime engine.

itowlson avatar Dec 04 '25 02:12 itowlson

cc @alexcrichton who might have the best perspective on whether these should be enabled.

lann avatar Dec 04 '25 13:12 lann

My understanding is that Wasmtime's implementations of WasmGC and exception handling are still in an experimental state and not yet production-ready, so I don't think it's time yet to enable them automatically. Opting in via a manifest setting (which is clearly documented as being experimental) seems reasonable, though.

dicej avatar Dec 04 '25 14:12 dicej

More generally speaking, the reasons for specific proposals/features not being enabled by default in Wasmtime are all such that we absolutely shouldn't make different decisions in Spin: either the spec isn't stable enough, or we don't trust the implementation robustness sufficiently.

I agree that we could add support for enabling proposals to enable local testing, same as Wasmtime does, but I think we should probably do it via CLI flags or env vars and for the entire process, instead of per-component in the manifest.

tschneidereit avatar Dec 04 '25 14:12 tschneidereit

One concern here is runtime fragmentation: a big part of Spin's usefulness (on top of Wasmtime) is in ensuring that a Spin app developed locally with spin up will be able to run in other Spin-compatible environments (SpinKube, etc). I'm in favor of enabling experimentation but we just want to be careful about how we design features that could compromise this compatibility.

lann avatar Dec 04 '25 14:12 lann

I don't have much more to add here over what others have already said. I would agree this should be a runtime/CLI switch rather than a component-level switch since it's a operator-level decision whether to enable these features or not, not a publisher of a component. I definitely think it's worthwhile to add CLI flags to Spin, for example, to enable experimentation and further motivate the tier 1 status of these features.

alexcrichton avatar Dec 04 '25 15:12 alexcrichton

wasmtime has CLI flags to configure semantic execution of WebAssembly.

wasmtime -W help
$ wasmtime -W help
Available wasm options:

  -W           nan-canonicalization[=y|n] -- Enable canonicalization of all NaN values.
  -W                               fuel=N -- Enable execution fuel with N units fuel, trapping after running out of fuel.
  -W             epoch-interruption[=y|n] -- Yield when a global epoch counter changes, allowing for async operation without blocking the executor.
  -W                     max-wasm-stack=N -- Maximum stack size, in bytes, that wasm is allowed to consume before a stack overflow is reported.
  -W                   async-stack-size=N -- Stack size, in bytes, that will be allocated for async stacks.
  -W            async-stack-zeroing[=y|n] -- Configures whether or not stacks used for async futures are zeroed before (re)use as a defense-in-depth mechanism. (default: false)
  -W          unknown-exports-allow[=y|n] -- Allow unknown exports when running commands.
  -W           unknown-imports-trap[=y|n] -- Allow the main module to import unknown functions, using an implementation that immediately traps, when running commands.
  -W        unknown-imports-default[=y|n] -- Allow the main module to import unknown functions, using an implementation that returns default values, when running commands.
  -W                      wmemcheck[=y|n] -- Enables memory error checking. (see wmemcheck.md for more info)
  -W                    max-memory-size=N -- Maximum size, in bytes, that a linear memory is allowed to reach.
  -W                 max-table-elements=N -- Maximum size, in table elements, that a table is allowed to reach.
  -W                      max-instances=N -- Maximum number of WebAssembly instances allowed to be created.
  -W                         max-tables=N -- Maximum number of WebAssembly tables allowed to be created.
  -W                       max-memories=N -- Maximum number of WebAssembly linear memories allowed to be created.
  -W           trap-on-grow-failure[=y|n] -- Force a trap to be raised on `memory.grow` and `table.grow` failure instead of returning -1 from these instructions.
  -W                  timeout=N|Ns|Nms|.. -- Maximum execution time of wasm code before timing out (1, 2s, 100ms, etc)
  -W                  all-proposals[=y|n] -- Configures support for all WebAssembly proposals implemented.
  -W                    bulk-memory[=y|n] -- Configure support for the bulk memory proposal.
  -W                   multi-memory[=y|n] -- Configure support for the multi-memory proposal.
  -W                    multi-value[=y|n] -- Configure support for the multi-value proposal.
  -W                reference-types[=y|n] -- Configure support for the reference-types proposal.
  -W                           simd[=y|n] -- Configure support for the simd proposal.
  -W                   relaxed-simd[=y|n] -- Configure support for the relaxed-simd proposal.
  -W     relaxed-simd-deterministic[=y|n] -- Configure forcing deterministic and host-independent behavior of the relaxed-simd instructions.
  -W                      tail-call[=y|n] -- Configure support for the tail-call proposal.
  -W                        threads[=y|n] -- Configure support for the threads proposal.
  -W      shared-everything-threads[=y|n] -- Configure support for the shared-everything-threads proposal.
  -W                       memory64[=y|n] -- Configure support for the memory64 proposal.
  -W                component-model[=y|n] -- Configure support for the component-model proposal.
  -W          component-model-async[=y|n] -- Component model support for async lifting/lowering.
  -W component-model-async-builtins[=y|n] -- Component model support for async lifting/lowering: this corresponds to the 🚝 emoji in the component model specification.
  -W component-model-async-stackful[=y|n] -- Component model support for async lifting/lowering: this corresponds to the 🚟 emoji in the component model specification.
  -W      component-model-threading[=y|n] -- Component model support for threading: this corresponds to the 🧵 emoji in the component model specification.
  -W  component-model-error-context[=y|n] -- Component model support for `error-context`: this corresponds to the 📝 emoji in the component model specification.
  -W             component-model-gc[=y|n] -- GC support in the component model: this corresponds to the 🛸 emoji in the component model specification.
  -W            function-references[=y|n] -- Configure support for the function-references proposal.
  -W                stack-switching[=y|n] -- Configure support for the stack-switching proposal.
  -W                             gc[=y|n] -- Configure support for the GC proposal.
  -W              custom-page-sizes[=y|n] -- Configure support for the custom-page-sizes proposal.
  -W                wide-arithmetic[=y|n] -- Configure support for the wide-arithmetic proposal.
  -W                 extended-const[=y|n] -- Configure support for the extended-const proposal.
  -W                     exceptions[=y|n] -- Configure support for the exceptions proposal.
  -W                     gc-support[=y|n] -- Whether or not any GC infrastructure in Wasmtime is enabled or not.

Perhaps spin could reuse this exact syntax for its CLI?

Though you may prefer to limit which features can be enabled this way. I’d like four features I’d like to expose as CLI options for Kotlin. Starting there and adding more as necessary seems pragmatic.

swankjesse avatar Dec 04 '25 16:12 swankjesse

Regarding CLI flags vs manifest settings:

If we use a manifest setting to say "this needs GC" (or whatever), then that can participate in the host requirements and target environments checking, so we can detect incompatibility with a non-GC environment at deployment time or build time. If we use a spin up flag, then there's no static information about the requirement, and the app will fail in non-GC environments only at runtime.

I appreciate that's not the only consideration and there may be compelling reasons to prefer the CLI flag anyway, but just noting this as input to the discussion.

itowlson avatar Dec 04 '25 18:12 itowlson

If we have manifest flag(s) I would want to also have a CLI arg.

lann avatar Dec 04 '25 19:12 lann

If we do anything with manifest flags, we should call them out as experimental, and make any attempt to publish the resulting app fail. And yes, we'd get requests to weaken that, but I think we should absolutely resist those: either the features are ready to deploy and ship, or they're not.

(I could see a future where we might want to be selective about wasm feature availability for other reasons, but we should tackle that then. E.g. if we at some point decide that Spin should support targeting environments that don't support, say, GC despise that being stable and shipping to production in Wasmtime, expressing that via a target environment makes sense.)

tschneidereit avatar Dec 04 '25 20:12 tschneidereit

@lann The much talked about "enable experimental things" flag(s) would certainly make sense; and, of course, at that point we will undoubtedly have to ponder "well if they already have to give the flag then why duplicate that in the manifest."

itowlson avatar Dec 04 '25 20:12 itowlson

How about this:

  • Add a new flag spin up --enable-unstable-feature <X>
  • When this flag is present, print a loud warning along the lines of "You have enabled unstable features which might go away at any time, bring a plague of locusts to your land, etc."
  • Let's start conservatively and just add the 4 features @swankjesse needs. I think its fine for this to be very verbose, e.g.:
    $ spin up --enable-unstable-feature wasm_gc \
              --enable-unstable-feature wasm_reference_types \
              --enable-unstable-feature wasm_exceptions \
              --enable-unstable-feature wasm_function_references
    

lann avatar Dec 05 '25 13:12 lann

This is a very nice bikeshed; I'd like to contribute to it by arguing about the paint color!

Where really all I'd suggest is to make this --enable-unstable-features wasm_gc,wasm_reference_types,wasm_exceptions,wasm_function_references

tschneidereit avatar Dec 05 '25 15:12 tschneidereit

@swankjesse Is this enough to go on? We don't currently have the plumbing for an --enable-unstable-features flag and the associated data flow, so I'm afraid at this point you'd be on the hook for that too - of course we would be happy to provide guidance, discussion, or feedback.

If you're okay with taking it on, my suggestion for flag syntax would be to go with what @lann describes (because clap gives us that for free and it's how we do other "allow multiple" flags), with @tschneidereit's suggestion as a bonus alternative if it falls out easily. (If not, we can add that in post-production, because it's orthogonal to what you care about.)

If you're not comfortable with the additional plumbing work, let us know and we can have a crack at planning it in (and will probably do GC etc. into the bargain because we'll need something to test it on!).

itowlson avatar Dec 07 '25 19:12 itowlson

I'll take a run at it!

swankjesse avatar Dec 07 '25 22:12 swankjesse

I would argue that we should only allow this in canary releases and not in official releases. The reason being is similar to the reason that the Rust compiler only allows unstable feature in nightly builds. This sets a fairly high bar to adoption of these unstable features which makes the likelihood that someone will feel mad when these features disappear/change/etc should be relatively low.

rylev avatar Dec 15 '25 15:12 rylev

More concretely, we could add an e.g. unstable-features cargo feature flag that is only enabled on canary builds that gates these CLI flags.

lann avatar Dec 15 '25 15:12 lann