Requirements for running non-rust wasm?

What’s goin on folks!?

I learned about ICP at an event back in 2024 and was impressed by the domain resolution speed on my lil static site.

I saw y’all are doing backends via wasm now so I figured I’d have a go at deploying a wasmified rails app.

the build currently sits at 85 MB and I can deploy it as a PWA.

this is the error I get when I try to deploy via “@dfinity/prebuilt@v2.0.0”

'direct update call failed: The replica returned a rejection error: reject code CanisterError, reject message Error from Canister a5dhi-k7777-77775-aaabq-cai: Canister’s Wasm module is not valid: Wasm module has an invalid import section. Module imports function ‘rb_wasm_throw_prohibit_rewind_exception’ from ‘rb-js-abi-host’ that is not exported by the runtime..

Any pointers for me on what I need to do to get a canister accepting the rb imports?

I’ll be presenting my results at a programming meetup in a couple weeks.

Cheers

Hey, welcome back to ICP! A Wasmified Rails app is a good project, great to see someone working on it.

The error means your compiled app is trying to use a function called rb_wasm_throw_prohibit_rewind_exception that ICP’s runtime simply doesn’t provide. It’s related to how Ruby Wasm handles exceptions/stack rewinding in JS environments,something ICP doesn’t support, so it rejects the module at deploy time.

A few directions worth exploring, though I’d recommend verifying each against the latest ruby_wasm docs since this toolchain moves fast:

1. Convert your build for ICP specifically Ruby Wasm targets a generic Wasm environment by default. There’s a tool called wasi2ic that converts it for the IC, make sure you’re deploying the converted output:

wasi2ic your_app.wasm your_app_ic.wasm

2. Disable exception/rewind support at build time The IC doesn’t support Wasm exceptions so the toolchain needs to stub this out rather than import it from the host. The exact flag depends on your setup — worth checking the ruby_wasm GitHub for the current recommended approach, as the syntax may have changed recently.

3. Check for version mismatches Run this to see if the problematic function is still present in your build:

wasm-objdump -x your_app.wasm | grep import

If rb_wasm_throw_prohibit_rewind_exception shows up, the build isn’t stubbing it out correctly yet.

Bigger concern, your 85 MB size Honestly this might be the harder wall to hit. ICP has a 10 MB Wasm module size limit, though you can push slightly past that by gzip-compressing before uploading (gzip my-canister.wasm then deploying the .wasm.gz file) .
ICP will decompress it automatically. That said, 85 MB is way beyond what compression alone will fix, so you’ll want to look at running wasm-opt passes and stripping unused gems to get the size down meaningfully before anything else.

Greeting @mitnick2012 ! Thanks for the engagement and feedback!

Oof ya even the current releases of the wasmified ruby interpreter sit at 22Mb. so rails ain’t gonna fly. Even getting the ruby interpreter to run will be a challenge.

Here are the unsupported imports of ruby 3.3 (the version I was able to build with rails build): https://github.com/ruby/ruby.wasm/releases/download/2.9.4/ruby-3.3-wasm32-unknown-wasip1-minimal.tar.gz

Does this list indicate all imports? Or only the unsupported ones?

Module imports:
import “wasi_snapshot_preview1” “args_get”
import “wasi_snapshot_preview1” “args_sizes_get”
import “wasi_snapshot_preview1” “environ_get”
import “wasi_snapshot_preview1” “environ_sizes_get”
import “wasi_snapshot_preview1” “clock_res_get”
import “wasi_snapshot_preview1” “clock_time_get”
import “wasi_snapshot_preview1” “fd_advise”
import “wasi_snapshot_preview1” “fd_close”
import “wasi_snapshot_preview1” “fd_datasync”
import “wasi_snapshot_preview1” “fd_fdstat_get”
import “wasi_snapshot_preview1” “fd_fdstat_set_flags”
import “wasi_snapshot_preview1” “fd_filestat_get”
import “wasi_snapshot_preview1” “fd_filestat_set_size”
import “wasi_snapshot_preview1” “fd_pread”
import “wasi_snapshot_preview1” “fd_prestat_get”
import “wasi_snapshot_preview1” “fd_prestat_dir_name”
import “wasi_snapshot_preview1” “fd_pwrite”
import “wasi_snapshot_preview1” “fd_read”
import “wasi_snapshot_preview1” “fd_readdir”
import “wasi_snapshot_preview1” “fd_renumber”
import “wasi_snapshot_preview1” “fd_seek”
import “wasi_snapshot_preview1” “fd_sync”
import “wasi_snapshot_preview1” “fd_tell”
import “wasi_snapshot_preview1” “fd_write”
import “wasi_snapshot_preview1” “path_create_directory”
import “wasi_snapshot_preview1” “path_filestat_get”
import “wasi_snapshot_preview1” “path_filestat_set_times”
import “wasi_snapshot_preview1” “path_link”
import “wasi_snapshot_preview1” “path_open”
import “wasi_snapshot_preview1” “path_readlink”
import “wasi_snapshot_preview1” “path_remove_directory”
import “wasi_snapshot_preview1” “path_rename”
import “wasi_snapshot_preview1” “path_symlink”
import “wasi_snapshot_preview1” “path_unlink_file”
import “wasi_snapshot_preview1” “poll_oneoff”
import “wasi_snapshot_preview1” “proc_exit”
import “wasi_snapshot_preview1” “random_get”
Error: There are imports remaining that are not compatible with the Internet Computer.


Happily, no new (unsupported?) imports are introduced from ‘minimal’ to ‘full’


These are the additional imports introduced by my rails build:

Module imports:
import "rb-js-abi-host" "rb_wasm_throw_prohibit_rewind_exception"
import "canonical_abi" "resource_drop_js-abi-value"
import "rb-js-abi-host" "eval-js: func(code: string) -> variant { success(handle), failure(handle) }"
import "rb-js-abi-host" "is-js: func(value: handle) -> bool"
import "rb-js-abi-host" "instance-of: func(value: handle, klass: handle) -> bool"
import "rb-js-abi-host" "global-this: func() -> handle"
import "rb-js-abi-host" "int-to-js-number: func(value: s32) -> handle"
import "rb-js-abi-host" "float-to-js-number: func(value: float64) -> handle"
import "rb-js-abi-host" "string-to-js-string: func(value: string) -> handle"
import "rb-js-abi-host" "bool-to-js-bool: func(value: bool) -> handle"
import "rb-js-abi-host" "proc-to-js-function: func(value: u32) -> handle"
import "rb-js-abi-host" "rb-object-to-js-rb-value: func(raw-rb-abi-value: u32) -> handle"
import "rb-js-abi-host" "js-value-to-string: func(value: handle) -> string"
import "rb-js-abi-host" "js-value-to-integer: func(value: handle) -> variant { as-float(float64), bignum(string) }"
import "rb-js-abi-host" "export-js-value-to-host: func(value: handle) -> ()"
import "rb-js-abi-host" "import-js-value-from-host: func() -> handle"
import "rb-js-abi-host" "js-value-typeof: func(value: handle) -> string"
import "rb-js-abi-host" "js-value-equal: func(lhs: handle, rhs: handle) -> bool"
import "rb-js-abi-host" "js-value-strictly-equal: func(lhs: handle, rhs: handle) -> bool"
import "rb-js-abi-host" "reflect-apply: func(target: handle, this-argument: handle, arguments: list<handle>) -> variant { success(handle), failure(handle) }"
import "rb-js-abi-host" "reflect-get: func(target: handle, property-key: string) -> variant { success(handle), failure(handle) }"
import "rb-js-abi-host" "reflect-set: func(target: handle, property-key: string, value: handle) -> variant { success(handle), failure(handle) }"
import "canonical_abi" "resource_new_rb-abi-value"
import "canonical_abi" "resource_get_rb-abi-value"


When you say ‘the runtime doesn’t provide the import’ that sounds like an issue lower down the stack than the Canister Development Kits that I saw a category about in this forum, correct?

This is the reference runtime that ruby-wasm refers to

Where there is divergence in supported imports, is this because IC hasn’t caught up to the standard? or are the ‘rb_js_’ & ‘wasi_snapshot_’ imports a customization that IC would have to decide to support?

Aside from your suggestions about trimming down imports in my build, what options do I have on the ICP side for trying to customize the canister/runtime?

Cheers

You will basically need to:

  1. ICify your runtime.
  2. Port the software to motoko and be cool.
  3. Port the software to rust and be lame.

ICify means rewrite from the ground up for a 40B instruction limit world.

Greetings,

Thanks for the comments

I must be missing something, isn’t the canister the runtime? Or is there some kind of wrapper?

I’m not seeing runtime technicals in the docs. Have a link by chance?

regarding the ‘40b instruction limit’, am I correct in guessing that this is outside the web assembly standard?

If so, perhaps ic would benefit from defining its target rather than directing the user to use “wasm32-unknown-unknown”?

Cheers

The Ic has a replica runtime. I think the core RTS system wasm that interacts with that runtime is written in rust and compiled to wasm. Motoko canister use this as well…it has all the close to the metal bits that pitching in stable memory and interchange data with the replica. The replica runs across all the nodes in the subnet and they take turns ordering messages, and signing state roots and p2p sending state around. The do this often and all have to agree about every 2 seconds. So any program can only run for about 2 seconds (5B instructions)before returning an answer or it traps. There is fancy math that lets the replica go 8 rounds(40B instructions) on one core with out settling. This is called Deterministic time slicing. Now ruby also likely has a runtime that needs to run in wasm that will load your ruby code and do things. It is very unlikely that this ruby vm stops every 2-8 seconds and cedes control back to the replica to record state…likely it expects that it can run forever. It can’t because the replica will rollback any work if you don’t report a settled state in time. Every once in a while(on the order of minutes I think there is a state checkpoint as well. This limits the theoretical max length of DTS…you HAVE to yield sometime.Most software doesn’t make these assumptions and thus doesn’t run in the IC without heavy engineering. This is why supporting existing languages was a mistake. We always had to rewrite all the software…so why not lean in? Much easier to do now than it was five years ago…they had to ship so they went with rust…one of the most anti-social and cryptic languages with difficult compile and project structure. Thus no cross project collaboration and a fractured community(yes I blame rust…great language for very smart people…terrible to build a community around). Throw this into ChatGPT and it will give you more info if you’d like it.

To run ruby on the ic you need to go to the ruby to wasm runtime repo, plug it into an llm and ask what it would take to make it operate in tight rounds. Build that software and then it will work. Not impossible! Someone did something similar with Wordpress recently and that run on php.

Oh wow, I completely misunderstood you original comment:

I thought you meant 40bytes per assembly line/instruction.

40 billion instructions sounds non-trivial, but less daunting.

Regarding wordpress, I guess this is the one your are talking about right?

I see the fellow mentioning that they need 200B cycles for a page load.

Now ruby also likely has a runtime that needs to run in wasm that will load your ruby code and do things. It is very unlikely that this ruby vm stops every 2-8 seconds and cedes control back to the replica to record state

I will take a look at the icp demo backends to better understand what you are talking about here and how the memory gets persisted from run to run. for example, does the IC runtime send a progression of kill messages that increase in severity as it approaches the DTS limit?

It seems as though you are saying that IC canisters are pretty much limited to individual lambdas/functions, and you’d need to rig a bunch of them together to perform workflows, etc.

I mean, that’s pretty much what my initial inquiry is no? How far I lean in is TDB, a lot of this is new for me and just a lil hobby project.

It’s much easier now on the ruby-side also, ruby-wasm shipped its first release in 2021 I think.

I do think that IC is gonna need to define a target-triplet (or whatever you wanna call it). So that the appropriate build flags can evolve with the spec/standard.

If IC’s objective is to become a runtime that wins widespread adoption at least.

Cheers

Basically yes…but if you can have your lambdas dump state before they hit the limit you can simulate traditional programs.

This was more targeted to the broader community than you. You are asking great questions!

Ok fair enough, cheers.

I’ve got lots to chew on now.

Any final pointers on where I might look to get more info & documentation about the IC runtime/build target?

I can open a new thread to refocus when I’ve made a bit of tangible progress.

There is a thing called wasm2wat that will
disassemble an IC wasm it something a bit more readable…this is probably a good place to start looking at all the system functions that you’ll need to hook into for the replica life cycle.

I’m not sure I understand when you write ‘an IC wasm’, are you referring to:

a. a wasm that has been built for deployment to ic? ie the ones spit out by the starter kit?

b. a wasm of a runtime that the built wasm app gets deployed into? (if this one, where do I find it?)

Hi graial, congrats on your ambition.

That said, 85 MB is way beyond what compression alone will fix

The right approach here is to upload Wasm chunks and then use the chunked install: IC management canister | ICP Developer Docs
With this, you can have up to 100MiB (uncompressed). Given that you got an import error and not a module size error, the tooling you use is probably already doing that for you. So the Ruby interpreter will fit. Unfortunately I don’t know anything about Ruby’s execution model, so some of my assumptions below might be flawed.

how the memory gets persisted from run to run. for example, does the IC runtime send a progression of kill messages that increase in severity as it approaches the DTS limit?

Wasm modules that are usable on the IC don’t have a main function, they are “reactor” modules (not technically quite true because “reactor” is a Wasi term whereas the IC does not natively support Wasi. But it’s fine to think about it this way). That means that code does not simply run at all times like e.g. a webapp or some other process might. Instead, code is being run only when somebody calls a canister function (the public interface of your app). Such functions have to terminate within reasonable time though, so we have limits in place.
For web requests this should probably not be an issue. I would ignore this problem for now. Focus on whether you can compile the Ruby interpreter (or whatever you are using) to a Wasm module that the IC can accept.

The canister state between function calls is persisted. I.e., if function call 1 changes some global variable, then function call 2 will be able to observe that change (unless you use “query” in which case the state is rolled back). From this perspective a canister almost looks like a process that is frozen until a function is called, which it executes to completion and then goes back to being suspended - but the “process state” meaning its internal (global) values are retained.

There are no kill messages because the IC tracks a function’s execution time via “instructions” which basically count the number of Wasm operations that have been executed in your function call so far. If it exceeds some limit, the IC aborts the function and rolls back the state. Never mind DTS, limits, cycles and all that, it’s not important here.

do think that IC is gonna need to define a target-triplet

In the interface specification Canister interface (system API) | ICP Developer Docs , we detail what interface a canister Wasm module has to satisfy. It’s not Wasi, it’s its own standard. So any Wasm module compiled to adhere to the Wasi standard (like you are presumably doing) must polyfill the Wasi interface with the IC’s system interface. But they are not congruent. Some features are just not supported on the IC, like sockets, randomness, files, … There are workarounds for some of them though.
Ultimately though, if your runtime/interpreter relies on syscalls for e.g. sockets, it’s a non-starter on the IC (currently. We do want to bring Wasi2 to the IC, but this will take a while).

Polyfill means: If your compiler emits Wasm code that imports “some_unix_syscall_time” or perhaps “some_js_thing_y”, the IC cannot satisfy these imports. But you can link (or transform) your module using a polyfill library, which instead imports e.g. “ic0.time” and points “some_unix_syscall_time” to this import. For the calling code it looks like it’s calling the intended “some_unix_syscall_time”, but it’s just relayed to the actual import that the IC can provide. This won’t work for some things.

Anyway, that’s the reason for your import errors. The only imports we allow are the IC’s system API. In order to use those sensibly, you probably want an SDK for your language. That is: Ruby FFI (foreign function interface) bindings that wrap these system functions and some logic that inserts them in the right places. It’s a big topic…

I would try to answer these questions in this order:

  • Can you compile Ruby (user program plus its runtime or interpreter) to a “reactor” conforming to wasip1? (I think you have already done this)
    • If yes, can you polyfill all necessary imports or does it have requirements we’ll never satisfy? This might involve tuning how exactly the Ruby->Wasm compile emits code, check out the tooling and its options (like mitnick suggests).
  • How can you use exernal “C” symbols in Ruby? Can the Ruby->Wasm compiler transform them into well-typed Wasm imports? I.e., how to do FFI in Ruby?
  • Try to write a minimal SDK that lets you call a canister function (using the IC syscalls msg_arg_data_size, msg_arg_data_copy, msg_reply_data_append, msg_reply)

Any final pointers on where I might look to get more info & documentation about the IC runtime/build target?

The interface spec. IC interface specification | ICP Developer Docs

Good luck.

Greetings @michael-weigelt

Thank you very much for the detailed response! This looks great.

Yes it’d be a big undertaking. I’ve already started deconstructing the app.

Being able to upload 100Mb takes a huge problem off the initial plate so that’s very encouraging!

Onto the imports for me now I guess!

Cheers