Coder Social home page Coder Social logo

ruby / ruby.wasm Goto Github PK

View Code? Open in Web Editor NEW
636.0 21.0 52.0 2.89 MB

ruby.wasm is a collection of WebAssembly ports of the CRuby.

Home Page: https://ruby.github.io/ruby.wasm/

License: MIT License

Dockerfile 1.04% Shell 3.21% JavaScript 13.99% Ruby 36.58% C 28.88% TypeScript 13.17% HTML 0.67% Rust 2.46%
webassembly wasm ruby wasi emscripten

ruby.wasm's Introduction

ruby.wasm

Build ruby.wasm

ruby.wasm is a collection of WebAssembly ports of the CRuby. It enables running Ruby application on browsers, WASI compatible WebAssembly runtimes, and Edge Computing platforms.

Try ruby.wasm (no installation needed)

Try ruby.wasm in TryRuby in your browser.

Quick Links

Quick Example: Ruby on Web browser

Create and save index.html page with the following contents:

<html>
  <script src="https://cdn.jsdelivr.net/npm/@ruby/[email protected]/dist/browser.script.iife.js"></script>
  <script type="text/ruby">
    require "js"

    puts RUBY_VERSION # (Printed to the Web browser console)
    JS.global[:document].write "Hello, world!"
  </script>
</html>

Quick Example: How to package your Ruby application as a WASI application

Dependencies: wasmtime

$ gem install ruby_wasm
# Download a prebuilt Ruby release
$ curl -LO https://github.com/ruby/ruby.wasm/releases/latest/download/ruby-3.3-wasm32-unknown-wasip1-full.tar.gz
$ tar xfz ruby-3.3-wasm32-unknown-wasip1-full.tar.gz

# Extract ruby binary not to pack itself
$ mv ruby-3.3-wasm32-unknown-wasip1-full/usr/local/bin/ruby ruby.wasm

# Put your app code
$ mkdir src
$ echo "puts 'Hello'" > src/my_app.rb

# Pack the whole directory under /usr and your app dir
$ rbwasm pack ruby.wasm --dir ./src::/src --dir ./ruby-3.3-wasm32-unknown-wasip1-full/usr::/usr -o my-ruby-app.wasm

# Run the packed scripts
$ wasmtime my-ruby-app.wasm /src/my_app.rb
Hello

npm packages (for JavaScript host environments)

See the README.md of each package for more detail and its usage.

Package Description npm
@ruby/3.3-wasm-wasi CRuby 3.3 built on WASI with JS interop support npm version
@ruby/3.2-wasm-wasi CRuby 3.2 built on WASI with JS interop support npm version
@ruby/head-wasm-wasi HEAD CRuby built on WASI with JS interop support npm version
@ruby/head-wasm-emscripten HEAD CRuby built on Emscripten (not well tested) npm version

Prebuilt binaries

This project distributes prebuilt Ruby binaries in GitHub Releases. A build is a combination of ruby version, profile, and target.

Supported Target Triples

Triple Description
wasm32-unknown-wasip1 Targeting WASI Preview1 compatible environments
(e.g. Node.js, browsers with polyfill, wasmtime, and so on)
wasm32-unknown-emscripten Targeting JavaScript environments including Node.js and browsers

Profiles

Profile Description
minimal No standard extension libraries (like json, yaml, or stringio)
full All standard extension libraries

Notable Limitations

The current WASI target build does not yet support Thread related APIs. Specifically, WASI does not yet have an API for creating and managing threads yet.

Also there is no support for networking. It is one of the goal of WASI to support networking in the future, but it is not yet implemented.

Contributing

See CONTRIBUTING.md for how to build and test, and how to contribute to this project. Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/ruby.wasm

ruby.wasm's People

Contributors

ahogappa0613 avatar dependabot[bot] avatar forthoney avatar geeknees avatar gifvex avatar hparker avatar hsbt avatar indigolain avatar kaiquekandykoga avatar kateinoigakukun avatar ko1 avatar kojix2 avatar krmbn0576 avatar kyanny avatar ledsun avatar makenowjust avatar mame avatar richardboehme avatar shugo avatar skryukov avatar terrablue avatar woxtu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ruby.wasm's Issues

Using Node.js 20, rake npm:ruby-head-wasm-wasi:check does not work

What happened.

I installed Node.js 20.8.0 and ran rake npm:ruby-head-wasm-wasi:check and got the following error:

►rake npm:ruby-head-wasm-wasi:check
npm test

> [email protected] test
> RUBY_NPM_PACKAGE_ROOT=../ruby-head-wasm-wasi npm -C ../ruby-wasm-wasi run test:run


> @ruby/[email protected] test:run
> npm run test:unit && npm run test:jest && npm run test:e2e


> @ruby/[email protected] test:unit
> ./tools/run-test-unit.mjs

(node:66391) ExperimentalWarning: WASI is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
node:internal/errors:497
    ErrorCaptureStackTrace(err);
    ^

TypeError [ERR_INVALID_ARG_TYPE]: The "options.version" property must be of type string. Received undefined
    at new NodeError (node:internal/errors:406:5)
    at validateString (node:internal/validators:162:11)
    at new WASI (node:wasi:51:5)
    at instantiateNodeWasi (file:///home/ledsun/ruby.wasm/packages/npm-packages/ruby-wasm-wasi/tools/run-test-unit.mjs:30:16)
    at async test (file:///home/ledsun/ruby.wasm/packages/npm-packages/ruby-wasm-wasi/tools/run-test-unit.mjs:128:24)
    at async main (file:///home/ledsun/ruby.wasm/packages/npm-packages/ruby-wasm-wasi/tools/run-test-unit.mjs:162:3) {
  code: 'ERR_INVALID_ARG_TYPE'
}

Node.js v20.8.0
npm ERR! Lifecycle script `test:unit` failed with error:
npm ERR! Error: command failed
npm ERR!   in workspace: @ruby/[email protected]
npm ERR!   at location: /home/ledsun/ruby.wasm/packages/npm-packages/ruby-wasm-wasi
npm ERR! Lifecycle script `test` failed with error:
npm ERR! Error: command failed
npm ERR!   in workspace: [email protected]
npm ERR!   at location: /home/ledsun/ruby.wasm/packages/npm-packages/ruby-head-wasm-wasi
rake aborted!
Command failed with status (1): [npm test]
/home/ledsun/ruby.wasm/tasks/packaging.rake:21:in `block (4 levels) in <top (required)>'
Tasks: TOP => npm:ruby-head-wasm-wasi:check
(See full trace by running task with --trace)

It worked fine using Node.js 18.8.2.

What I tried

Added version property to the argument of the WASI constructor.

const wasi = new nodeWasi.WASI({
stdio: "inherit",
args: ["ruby.wasm"].concat(process.argv.slice(2)),
env: {
...process.env,
// Extend fiber stack size to be able to run test-unit
"RUBY_FIBER_MACHINE_STACK_SIZE": String(1024 * 1024 * 20),
},
preopens,
});

I have tried both unstable and preview1 according to https://nodejs.org/docs/latest-v20.x/api/wasi.html.
The test ended with a Segmentation fault.

ledsun@MSI:~/ruby.wasm►rake npm:ruby-head-wasm-wasi:check
npm test

> [email protected] test
> RUBY_NPM_PACKAGE_ROOT=../ruby-head-wasm-wasi npm -C ../ruby-wasm-wasi run test:run


> @ruby/[email protected] test:run
> npm run test:unit && npm run test:jest && npm run test:e2e


> @ruby/[email protected] test:unit
> ./tools/run-test-unit.mjs

(node:68755) ExperimentalWarning: WASI is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
Ignoring debug-1.8.0 because its extensions are not built. Try: gem pristine debug --version 1.8.0
Ignoring racc-1.7.3 because its extensions are not built. Try: gem pristine racc --version 1.7.3
Ignoring rbs-3.3.2 because its extensions are not built. Try: gem pristine rbs --version 3.3.2
Loaded suite /__root__/test/test_unit
Started
Segmentation fault
npm ERR! Lifecycle script `test:unit` failed with error:
npm ERR! Error: command failed
npm ERR!   in workspace: @ruby/[email protected]
npm ERR!   at location: /home/ledsun/ruby.wasm/packages/npm-packages/ruby-wasm-wasi
npm ERR! Lifecycle script `test` failed with error:
npm ERR! Error: command failed
npm ERR!   in workspace: [email protected]
npm ERR!   at location: /home/ledsun/ruby.wasm/packages/npm-packages/ruby-head-wasm-wasi
rake aborted!
Command failed with status (1): [npm test]
/home/ledsun/ruby.wasm/tasks/packaging.rake:21:in `block (4 levels) in <top (required)>'
Tasks: TOP => npm:ruby-head-wasm-wasi:check
(See full trace by running task with --trace)

build/toolchain/binaryen/bin/wasm-opt: No such file or directory

I ran the following command

rake npm:ruby-head-wasm-wasi

The command succeeds.
However, I get an error in the execution log.

/home/ledsun/ruby.wasm/packages/npm-packages/ruby-head-wasm-wasi/. /ruby-wasm-wasi/tools/pack-ruby-wasm.sh: line 18: /home/ledsun/ruby.wasm/build/toolchain/binaryen/bin/wasm-opt: no such file or directory

And packages/npm-packages/ruby-head-wasm-wasi/dist/ruby.stdlib.wasm was not created.

The full execution log can be found at https://gist.github.com/ledsun/b76fb84feb5b6cff97a52ae2fac43d4e .

Is it possible to use with wasm32-unknown-unkown target

I am looking to use Ruby as a scripting solution. Is it possible to compile(or use) to wasm32-unknown-unkown target?
Currently I can do this:

wasmtime ruby.wasm -- /src/my_app.rb

I am curious if I can use it without wasi(assuming I have a text blob which is valid ruby code). I am hoping there can be a function like run.

ruby-3_2-wasm-wasi sample does not work

https://github.com/ruby/ruby.wasm/tree/main/packages/npm-packages/ruby-3_2-wasm-wasi

I wanted to use the next version, but it doesn't seem to work after 1.0.1-2023-02-10-a. It worked until 1.0.1-2023-02-09-a.

Work

<html>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/browser.umd.js"></script>
  <script>
    const { DefaultRubyVM } = window["ruby-wasm-wasi"];
    const main = async () => {
      // Fetch and instantiate WebAssembly binary
      const response = await fetch(
        //      Tips: Replace the binary with debug info if you want symbolicated stack trace.
        //      (only nightly release for now)
        //      "https://cdn.jsdelivr.net/npm/ruby-3_2-wasm-wasi@next/dist/ruby.debug+stdlib.wasm"
        "https://cdn.jsdelivr.net/npm/[email protected]/dist/ruby.wasm"
      );
      const buffer = await response.arrayBuffer();
      const module = await WebAssembly.compile(buffer);
      const { vm } = await DefaultRubyVM(module);

      vm.printVersion();
      vm.eval(`
        require "js"
        luckiness = ["Lucky", "Unlucky"].sample
        JS::eval("document.body.innerText = '#{luckiness}'")
      `);
    };

    main();
  </script>
  <body></body>
</html>

Not Work

<html>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/browser.umd.js"></script>
  <script>
    const { DefaultRubyVM } = window["ruby-wasm-wasi"];
    const main = async () => {
      // Fetch and instantiate WebAssembly binary
      const response = await fetch(
        //      Tips: Replace the binary with debug info if you want symbolicated stack trace.
        //      (only nightly release for now)
        //      "https://cdn.jsdelivr.net/npm/ruby-3_2-wasm-wasi@next/dist/ruby.debug+stdlib.wasm"
        "https://cdn.jsdelivr.net/npm/[email protected]/dist/ruby.wasm"
      );
      const buffer = await response.arrayBuffer();
      const module = await WebAssembly.compile(buffer);
      const { vm } = await DefaultRubyVM(module);

      vm.printVersion();
      vm.eval(`
        require "js"
        luckiness = ["Lucky", "Unlucky"].sample
        JS::eval("document.body.innerText = '#{luckiness}'")
      `);
    };

    main();
  </script>
  <body></body>
</html>

Floats not automatically converted (but Integers are)

This might be an issue?

It is definitely a showstopper in many situation where one does not know whether the value (coming from somewhere else) might be an Integer or a Float. In one case the call to JS will succeed and in another it will fail.

Discussed in #211

Originally posted by sebnozzi May 12, 2023
When passing parameters in a JavaScript call Integers and Strings are converted just fine; but Floats aren't.

Why is this so?

Consider this code:

require "js"

JS.global[:window].call(:alert, 123) # works
JS.global[:window].call(:alert, 123.5) # FAILS

Instead, one has to resort to this:

JS.global[:window].call(:alert, JS.eval("return 123.5")) # works

Is this intended? Am I missing something? (seems like an overkill to me)

Also see my other thread about not being able to call to_js to a Float value.

Link in "try Ruby Playground"

Examples of using the exports for embedding

I'd like to be able to use Ruby as an embedded language, but I don't understand what all of the exports do. I'm assuming that init starts the interpreter, and eval-string-protect runs arbitrary code. I'd also like to be able to invoke an an arbitrary function from a library. Are there any examples or documentation on the exports?

[Documentation] Please add a short explanation what to do before the "Create and save index.html page with the following contents:"

Hey there ruby.wasm maintainers,

The README right now states that we should create a file such as index.html and put into it this content:

<html>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/browser.script.iife.js"></script>
  <script type="text/ruby">
    puts "Hello, world!"
  </script>
</html>

However had, showing this today via the chrome browser, I do not get any result. So I probably miss some
step. I did not even download ruby.wasm. Newbie mistake perhaps. :)

At any rate, could you guys specify what should happen before that step? I looked above the "Quick Example"
part, but there was no explanation what else I should have done - if someone has time, would it be possible
to expand on the README and provide instructions what to do or how? I would like to try out ruby + wasm
as soon as possible. At any rate, thank you for reading; please do feel free to close this issue request at
any moment in time.

New @ruby/wasm-wasi package without .wasm?

I just spotted the deprecation warning:

DEPRECATED(ruby-3_2-wasm-wasi): "browser.esm.js" will be moved to "@ruby/wasm-wasi" in the next major release.Please replace your `import * from 'ruby-3_2-wasm-wasi/dist/browser.esm.js';` with `import * from '@ruby/wasm-wasi/dist/browser.esm.js';`

However, it's not clear to me why I need one CDN to grab @ruby/wasm-wasi/dist/browser.esm.js and another CDN url to grab its ruby-3_2-wasm-wasi@latest/dist/ruby.wasm counterpart.

Your documentation is also a bit off around this deprecation.

Is there by any chance a will to move per version the ruby.wasm file within the main package? We pin the version and resolve the URL with ease this way, keeping track of versioning and 2 different URLs looks a bit unnecessary to me, or surely not ideal or user friendly, thank you!

Bug with passing Procs around: "RuntimeError: unreachable"

Hey! Sorry for not coming back to #32 yet! I was trying things out and stumbled around a really weird bug that I cannot explain.

I extracted the following snippet that reproduces the problem (at least on my machine):

import { DefaultRubyVM } from 'ruby-head-wasm-wasi/dist/browser.umd'

const main = async () => {
  // Fetch and instantiate WebAssembly binary
  const response = await fetch(
    "https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@next/dist/ruby.debug+stdlib.wasm"
  );
  const buffer = await response.arrayBuffer();
  const module = await WebAssembly.compile(buffer);
  const { vm } = await DefaultRubyVM(module);

  window.wrapProc = (proc) => {
    return () => {
      proc.call('call')
    }
  }

  vm.eval(`
    require 'js'
    require 'json'
    
    class Test
      def initialize
      end
    end

    proc = Proc.new do 
      json = {a: 1}.to_json
      Test.new
    end
    wrapped_proc = JS::global.call('wrapProc', JS::Object.wrap(proc))
    wrapped_proc.call(:call)
  `)
};

main();

The error this produces is:

Uncaught (in promise) RuntimeError: unreachable
    at asyncify_stop_unwind (09f33406:0x674834)
    at rb_abi_guest_rb_funcallv_protect (09f33406:0x97c7)
    at __wasm_export_rb_abi_guest_rb_funcallv_protect (09f33406:0xa4dc)
    at RbAbiGuest.rbFuncallvProtect (browser.umd.js:669:33)
    at callRbMethod (browser.umd.js:1456:40)
    at RbValue.call (browser.umd.js:1343:30)
    at main.js?t=1660228644277:14:12
    at Object.reflectApply (browser.umd.js:1201:34)
    at imports.rb-js-abi-host.reflect-apply (browser.umd.js:906:23)
    at rb_js_abi_host_reflect_apply (09f33406:0x86bf)

I tried to wrap my head around this for some time now and can't find the source of the problem. The error disappears when removing for example the to_json line or the class initialization or when removing the empty initialize method of the Test class.

Let me know if more information is needed of if I can help on this somehow else!

Re-design `evalAsync` API

Think of the following design points:

  1. evalAsync and eval can be merged?
    Let's think about returning Promise only when the execution actually requires async-ness.
  2. Should we use evalAsync in browser.script.iife.js instead of eval by default? Or provide option in <script> tag?
  3. How can we prohibiteval re-entrance?
    We can't allow re-entrance to eval because Asyncify cannot unwind the JS call frames without manual Asyncify.
    Another topic here is should we still need to prohibit it once we will be able to remove Asyncify.

How to pass non primitive Ruby constructs to JS

I've been working with using React from ruby.wasm, and came across some issues trying to pass things like Arrays and Ruby procs to JS.

Here's an example with arrays, for instance:

map_call = JS.eval <<-JS
  return {testMap: function(arr) {
    arr.forEach(i => console.log(i))
  }}
JS
map_call.call :testMap, [1, 2, 3]

This fails with the error "argument 2 is not a JS::Object like object".

I can get it working with something like this:

class Array
  def to_js
    JS.global.call :Array, *self
  end
end

Is this intended? Is ruby.wasm in need of more functionality to handle more than just raw String/Int/Bool args being passed to JS? Or is it intended to be something another external library adds? Or is there a simpler way to be doing this?

(I am having the same troubles with passing Procs)

Move npm packages under @ruby org

Managing packages under a single organization would be better for discoverability and management

TODO:

  • Rename package names
  • Update documentations
  • Publish compat packages with old package names to let users know about the new packages.

Async evaluation and line-tracing result in stack-overflow

Discussed in #215

Using @next version as of 16 May 2023, commit 9aef33e.

Originally posted by sebnozzi May 16, 2023

Basically I'm evaluating some Ruby code asynchronously and using a line-handler (TracePoint).

<html>
<head>
  <title>Ruby/Wasm - Infinite Recursion on Eval/Tracing</title>
  <link rel="icon" href="data:;base64,iVBORw0KGgo=">
</head>
<body>

  <script src="https://cdn.jsdelivr.net/npm/ruby-3_2-wasm-wasi@next/dist/browser.script.iife.js"></script>

  <script type="text/ruby">
    require "js"   
    
    # We boostrap the whole process once Ruby processing is in place
    JS.global[:window].bootStrapAsyncEval()
  </script>
  <script type="text/javascript">

    // This is the final code we want to evaluate
    window.CODE_TO_EVAL = `
      def print_numbers(e)
        (1..e).each do |x|
            puts "Number: #{x}"
        end
      end

      (1..5).each do |n|
          print_numbers n
      end
      `;
    
    // This contains the setup for the line-tracer
    window.TRACER_SETUP = `
      line_tracer = TracePoint.new(:line) do |tp|
        # One could remove the restriction to "(EVAL)" and the result would
        # be the same.
        puts "Line #{tp.lineno} of #{tp.path}" if tp.path == "(EVAL)"
      end
      line_tracer.enable
      `;

    // This allows us to evaluate code in JS's global scope,
    // giving the evaluation a ("file") name. By giving it a name
    // we can filter by it in the line-handler.
    window.EVAL_FN = `
      require 'js'
      def eval_code(code_name, eval_name)
        code = JS.global[:window][code_name].to_s
        scope = binding
        eval(code, scope, eval_name, 1)
      end
      `;

    window.bootStrapAsyncEval = function () {
      // I do this to rule out problems of inkoving "evalAsync" inside of
      // an already running "eval". This should take us back to the JS 
      // main event loop.
      setTimeout(async function() {
        evalCodeAsync();
      }, 0);
    }

    async function evalCodeAsync() {
      const vm = window.rubyVM;

      // Get the code snippets
      const tracerSetup = window["TRACER_SETUP"];
      const evalFn = window["EVAL_FN"];
      const codeToEval = window["CODE_TO_EVAL"];

      console.log("Setting up eval function ...");
      await vm.evalAsync(evalFn);

      console.log("Setting up line tracer ...");
      await vm.evalAsync(tracerSetup);

      console.log("Evaluating code ...");
      // Either evaluating the code directly or through one layer
      // or Ruby's eval results the same problem (too much recursion).
      // I tried running Ruby's eval to change the name of the evaluated
      // code, so that I could filter it out in the line-tracer, but the
      // problem remains.
      //await vm.evalAsync(codeToEval);
      await vm.evalAsync("eval_code('CODE_TO_EVAL','(EVAL)')")
    }

  </script>

</body>
</html>

In Chrome this results in:

image

Not evaluating it "async", however, runs just fine:
(the async, await on JavaScript are gone)

<html>
<head>
  <title>Ruby/Wasm - Infinite Recursion on Eval/Tracing</title>
  <link rel="icon" href="data:;base64,iVBORw0KGgo=">
</head>
<body>

  <script src="https://cdn.jsdelivr.net/npm/ruby-3_2-wasm-wasi@next/dist/browser.script.iife.js"></script>

  <script type="text/ruby">
    require "js"   
    
    # We boostrap the whole process once Ruby processing is in place
    JS.global[:window].evalCodeSync()
  </script>
  <script type="text/javascript">

    // This is the final code we want to evaluate
    window.CODE_TO_EVAL = `
      def print_numbers(e)
        (1..e).each do |x|
            puts "Number: #{x}"
        end
      end

      (1..5).each do |n|
          print_numbers n
      end
      `;
    
    // This contains the setup for the line-tracer
    window.TRACER_SETUP = `
      line_tracer = TracePoint.new(:line) do |tp|
        # One could remove the restriction to "(EVAL)" and the result would
        # be the same.
        puts "Line #{tp.lineno} of #{tp.path}" if tp.path == "(EVAL)"
      end
      line_tracer.enable
      `;

    // This allows us to evaluate code in JS's global scope,
    // giving the evaluation a ("file") name. By giving it a name
    // we can filter by it in the line-handler.
    window.EVAL_FN = `
      require 'js'
      def eval_code(code_name, eval_name)
        code = JS.global[:window][code_name].to_s
        scope = binding
        eval(code, scope, eval_name, 1)
      end
      `;

    function evalCodeSync() {
      const vm = window.rubyVM;

      // Get the code snippets
      const tracerSetup = window["TRACER_SETUP"];
      const evalFn = window["EVAL_FN"];
      const codeToEval = window["CODE_TO_EVAL"];

      console.log("Setting up eval function ...");
      vm.eval(evalFn);

      console.log("Setting up line tracer ...");
      vm.eval(tracerSetup);

      console.log("Evaluating code ...");
      vm.eval("eval_code('CODE_TO_EVAL','(EVAL)')")
    }

  </script>

</body>
</html>

What's going on? I am doing anything wrong?

Any ideas?

Handle JS null values within Ruby code

Hey, thanks for providing those great NPM packages!

I'm currently experimenting with writing DOM-Interaction using the JS interoperability module that this package provides. I wonder if it's possible to handle null values with the JS module?

I try to read from localStorage and if a key was not set localStorage.getItem will return null. This null value gets wrapped into a JS::Object which isn't really useful because all reflection methods complain that null is not an object. Could we return nil instead or add a method to JS::Object to identify it being null?

My example:

null_value = JS::global[:localStorage].call('getItem', 'foobar')
p null_value.inspect
# => Uncaught TypeError: Cannot read properties of null (reading 'toString')

I'm happy to help, just not sure how we should approach this.

Enumerable#all? in rubyVM.evalAsync causes SystemStackError

It seems that Enumerable#all? in rubyVM.evalAsync causes SystemStackError:

<html>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/browser.script.iife.js"></script>

  <button id="start">Start</button>

  <script type="text/javascript">
    async function start() {
      await window.rubyVM.evalAsync("p (0..3).all? { |i| i.even? }")
    }
    var startButton = document.getElementById("start")
    startButton.addEventListener("click", () => {
      start()
    }, false)
  </script>
</html>
browser.script.iife.js:1505 Uncaught (in promise) Error: SystemStackError: stack level too deep
    at checkStatusTag (browser.script.iife.js:1505:21)
    at browser.script.iife.js:1535:11
    at wrapRbOperation (browser.script.iife.js:1512:18)
    at callRbMethod (browser.script.iife.js:1533:14)
    at RbValue.call (browser.script.iife.js:1390:30)
    at browser.script.iife.js:1293:18
    at new Promise (<anonymous>)
    at RubyVM.evalAsync (browser.script.iife.js:1292:18)
    at start (test.html:8:27)
    at HTMLButtonElement.<anonymous> (test.html:12:7)
checkStatusTag @ browser.script.iife.js:1505
(匿名) @ browser.script.iife.js:1535
wrapRbOperation @ browser.script.iife.js:1512
callRbMethod @ browser.script.iife.js:1533
call @ browser.script.iife.js:1390
(匿名) @ browser.script.iife.js:1293
evalAsync @ browser.script.iife.js:1292
start @ test.html:8
(匿名) @ test.html:12
await in (匿名)(非同期)
(匿名) @ test.html:12

Enumerable#all? in rubyVM.eval works fine.

"SystemStackError: stack level too deep" when requiring json with latest built ruby-3_2-wasm-wasi

Repro:

  • build rake npm:ruby-3_2-wasm-wasi:build locally with prebuilt rubies
  • create html file like this referencing built package
<!DOCTYPE html>
<html>

<head>
  <title>rwasm test</title>
</head>

<body>
</body>
<script src="ruby-3_2-wasm-wasi/dist/browser.script.iife.js"></script>
<script data-eval="async" type="text/ruby">
  require 'json'
</script>


</html>

Error:
Unhandled Promise Rejection: Error: SystemStackError: stack level too deep

Removing require json or data-eval="async" fixes the problem.
Theory: Is it due to some kind of lack of thread safety in the original CRuby json lib?

Page crashes on iOS Safari and Chrome when using ruby-head-wasm-wasi@latest

Description

When I open a webpage that includes the https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@latest/dist/browser.script.iife.js script, the page crashes on iOS browsers like Safari and Chrome. However, changing to https://cdn.jsdelivr.net/npm/[email protected]/dist/browser.script.iife.js resolves the issue and the page no longer crashes.

Steps to reproduce

Load a webpage that includes https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@latest/dist/browser.script.iife.js.
Open the page in iOS Safari or Chrome.

Expected behavior

The page should load and function normally.

Actual behavior

The page crashes upon loading.

Environment

iOS version: 16.6

Example needed: running Ruby code in Chrome

Hello all - I'm glad to be back in a Ruby codebase after so long dealing with Javascript.

My goal is to combine these two languages by enabling the running of Ruby code from inside a webpage.
Here is my approach so far:

import { WASI, init } from '@wasmer/wasi'

var main = async () => {
  await init();

  let wasi = new WASI({
    env: {},
    args: ['ruby -e "puts 0"'],
  });

  console.log(1)
  const moduleBytes = fetch("/processors/ruby+stdlib.wasm");
  const module = await WebAssembly.compileStreaming(moduleBytes);

  console.log(2)
  // Instantiate the WASI module
  await wasi.instantiate(module, {});

  console.log(3)
  // Run the start function
  let exitCode = wasi.start();
  let stdout = wasi.getStdoutString();

   // This should print "hello world (exit code: 0)"
  console.log(`${stdout}(exit code: ${exitCode})`);
}

main()

I am running this code, packaged up by webpack, with a polyfill for Buffer.
I based my code on https://wasmer.io/posts/wasmer-js-a-new-hope -
the example there uses the WASM binary https://deno.land/x/[email protected]/tests/demo.wasm,
which runs properly in my code.

Sadly, changing the binary to ruby+stdlib.wasm produces an error:

[application.js:16:10](webpack://app/src/application.js)
Uncaught (in promise) Error: Failed to instantiate WASI: RuntimeError: `

My main goal is to be able to pass a measure of Ruby code into the Ruby WASI process,
and use the response inside my webpage.
I am happy to use the memfs filesystem for recording and passing in my Ruby programs,
so long as the approach enables me to send in any Ruby code I like with no precompilation.

This may be an ambitious goal; though I imagine this could become a necessary use case for many people.

Installation instructions?

Could someone add installation instructions to the main README? I'd love to try this on my linux system. Right now I don't know what to do to get it to work.

[Documentation] Call wasm imports

Not sure this is the right place to ask, but is there a way to call imported wasm functions directly (or if not, indirectly) from Ruby?

Conversion method `to_js` does not work on Float?

Discussed in #210

Originally posted by sebnozzi May 12, 2023
According to the documentation I should be able to call to_js in a Float value, right?

https://ruby.github.io/ruby.wasm/Float.html

Then why does this throw an error?

require "js"

(Math::PI).to_js

Output:

(file): eval:3:in `<main>': undefined method `to_js' for 3.141592653589793:Float (NoMethodError)
 (Exception)

Link:
https://try.ruby-lang.org/playground/#code=require+%22js%22%0A%0A(Math%3A%3API).to_js&engine=cruby-3.2.0

Is it possible to convert ruby to WebAssembly and run it in a react application?

In a current project, we have a situation where we want to centralize where some calculations are being done, so everything is being calculated on the backend.

The tradeoff is that whenever the front end needs the results of said calculations it needs to make a request and wait for the response. Given it's only a second or two at most, we figured if we could export the calculating function as web assembly, we could import it as a function for JS.

Is this a supported use case?

Communication from javascript to ruby and back with ruby.wasm

So I'm trying to figure out how to go between js <=> rb, but I'm no js expert.

I'd like to do something like the following. In other words, having both the ability to use <script type="text/ruby"> AND being able to access the VM directly to call eval (or have some other way of calling back into ruby from js). All the examples I can find do either or, but never both. And if I combine them (by using the browser.umd.js examples) I'm getting two wasm-downloads plus I can't figure out how to combine their contexts.

I'm guessing it's rather simple (?), but I don't know enough about js/wasm to figure it out. And there aren't many resources online. So I thought I'd ask here.

<script src="https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@latest/dist/browser.script.iife.js"></script>
<script type="text/ruby">
  def say_hello(name)
    puts "Hello #{name}!"
  end
</script>
<script>
  RubyVM.eval(`require "js"; JS::eval("console.log('#{say_hello(\"John Doe\")}')")`);
</script>

[Documentation] non-docker way to build wasm?

The documentation currently states:

"It's recommended building on a builder Docker container, which installs all dependencies and provides environment variables:"

However had, I have not used docker so far myself.

I compile stuff from source, including ruby. Is it possible to use ruby-wasm without docker, or is docker mandatory?

If docker is optional then perhaps the main README could mention how to get ruby.wasm installed.

I have compiled node-js from source successfully; npm works fine. So all that would be necessary is a better
clarification how to get ruby-wasm to work without being dependent on docker (I'd prefer to not add another
dependency if that is possible, e. g. keep stuff as much as "ruby" as possible).

I had a look at ruby-wasm due to that: https://bugs.ruby-lang.org/issues/18761 but I did not even get to the
part where I could test the other instructions given, since I failed at the docker step. ("rake" does not work
for me in general, no idea why; "gem install" on the other hand works almost always. "rake" only gave
me a useless message such as "Don't know how to build task 'default'", which of course, as is typical for
rake, doesn't tell the user anything at all. Hopefully we can improve the ruby ecosystem in general - gem
is really the best from all the alternatives, in my opinion.).

example JS::Object doesn't work

This is the code found in : example JS::Object.

This code raise the following error:

Unhandled Promise Rejection: Error: eval:22:in 'call': TypeError: Reflect.apply requires the first argument be a function (JS::Error) eval:22:in '<main>'

Here is the code :

require 'js'
document = JS.global[:document]  
document[:title]               
document[:title] = "Hello, Ruby!"

document.write("Hello, world!")   # is equivalent to the following:
document.call(:write, "Hello, world!")
js_obj = JS.eval(<<-JS)
  return {
    method1: function(str, num) {
      // str is a JavaScript string and num is a JavaScript number.
      str.length + num
   },
    method2: function(rbObject) {
      // Call String#upcase method for the given Ruby object (RbValue).
      return rbObject.call("upcase").toString();
    }
  }

js_obj[:method1].call("Hello", 5) # => 10
js_obj[:method2].call(JS::Object.wrap("Hello, Ruby"))

ruby.wasm doesn't work on Wasmer 3.0

Summary

I tried to execute ruby.wasm via below commands on wasmer 3.0.1, but it doesn't work.

$ wasmer --version
wasmer 3.0.1
$ wasmer run ./ruby.wasm -- --version
thread 'main' panicked at 'not yet implemented: could not serialize number 2 to enum Clockid', lib/wasi-types/src/wasi/extra.rs:2725:18
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

It worked on wasmer 2.3.0 and wasmtime 3.0.0.

$ curl https://get.wasmer.io -sSfL | sh -s "2.3.0"
$ wasmer --version
wasmer 2.3.0
$ wasmer run ./ruby.wasm -- --version
ruby 3.2.0dev (2022-11-11T22:40:31Z master 0a9d51ee9d) [wasm32-wasi]
$ wasmtime --version
wasmtime-cli 3.0.0
$ wasmtime run ./ruby.wasm -- --version
ruby 3.2.0dev (2022-11-11T22:40:31Z master 0a9d51ee9d) [wasm32-wasi]

I don't have confidence this issue is ruby problem or wasmer problem, but wasmer 3.0.2 can execute some WASM which are created by Rust. So, I report it here.

Environments and install

OS

3.2.0-preview3-bullseye(Docker)

ruby.wasm

$ curl -LO https://github.com/ruby/ruby.wasm/releases/latest/download/ruby-head-wasm32-unknown-wasi-full.tar.gz
$ tar xfz ruby-head-wasm32-unknown-wasi-full.tar.gz
$ mv head-wasm32-unknown-wasi-full/usr/local/bin/ruby ruby.wasm

wasmer

$ curl https://get.wasmer.io -sSfL | sh
$ wasmer --version
wasmer 3.0.1

Expectation

success to run ruby.wasm

$ wasmer run ./ruby.wasm -- --version
ruby 3.2.0dev (2022-11-11T22:40:31Z master 0a9d51ee9d) [wasm32-wasi]

Actual Error

$ wasmer run ./ruby.wasm -- --version
thread 'main' panicked at 'not yet implemented: could not serialize number 2 to enum Clockid', lib/wasi-types/src/wasi/extra.rs:2725:18
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Rubygems and bundler

Hello again - I had some success running Ruby code in an online page,
as you can see on ://ruby.assembled.app.

This is good progress, and I'm now curious if there is an approach
for requiring ruby gem libraries inside the .wasm package,
so my code can use helpers such as 1.second and "hello".titlecase,
among others.

I recognize some libraries including C extensions, such as Nokogiri,
are unlikely to be runnable inside .wasm unless specially compiled.
For non-C gems, I'm curious if there is a non-compilation approach,
though if compiling a new .wasm binary is necessary, I'd be glad for directions.

`new` operator support in `js` library

Discussed in #165

Originally posted by scally February 14, 2023
How do you call new on Javascript objects? I haven't found a workaround yet that will let me do this. I can call the object constructor but it will then tell me that I need to call the new operator:

for example:

observer = JS.global.MutationObserver do
  puts 'here'
end

results in

TypeError: Constructor requires 'new' operator (JS::Error)
```</div>

Recent wasi-sdk broke CRuby build

I am trying building CRuby from source but I get errors around openssl.

windows11 WSL

$ uname -a
Linux LAPTOP-70JOP1HD 4.19.128-microsoft-standard #1 SMP Tue Jun 23 12:58:10 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

log

$ rake build:3_2-wasm32-unknown-wasi-full-js
make: Nothing to be done for 'rbconfig.rb'.
creating Makefile
make: Entering directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js-ext/js'
make: 'js.a' is up to date.
make: Leaving directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js-ext/js'
creating Makefile
make: Entering directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js-ext/witapi'
make: 'witapi.a' is up to date.
make: Leaving directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js-ext/witapi'
rm -rf .bundle/gems/debug-1.7.1/
rm -rf .bundle/gems/rbs-2.8.2/
        BASERUBY = /home/ongaeshi/WslCode/ruby.wasm/build/x86_64-pc-linux/baseruby-3_2/opt/bin/ruby --disable=gems
        CC = /home/ongaeshi/WslCode/ruby.wasm/build/checkouts/3_2/tool/wasm-clangw /home/ongaeshi/wasi-sdk-19.0/bin/clang
        LD = /home/ongaeshi/wasi-sdk-19.0/bin/clang
        LDSHARED = /home/ongaeshi/wasi-sdk-19.0/bin/clang
        CFLAGS = -fdeclspec -O3 -fno-fast-math -g0 -Wall -Wextra -Wextra-tokens -Wdeprecated-declarations -Wdivision-by-zero -Wdiv-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wmisleading-indentation -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wold-style-definition -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wundef   -D_WASI_EMULATED_SIGNAL -D_WASI_EMULATED_MMAN -D_WASI_EMULATED_GETPID -D_WASI_EMULATED_PROCESS_CLOCKS 
        XCFLAGS = -DWASM_SETJMP_STACK_BUFFER_SIZE=24576 -DWASM_FIBER_STACK_BUFFER_SIZE=24576 -DWASM_SCAN_STACK_BUFFER_SIZE=24576 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -fno-strict-overflow -fvisibility=hidden -DRUBY_EXPORT -I. -I.ext/include/wasm32-wasi -I/home/ongaeshi/WslCode/ruby.wasm/build/checkouts/3_2/include -I/home/ongaeshi/WslCode/ruby.wasm/build/checkouts/3_2 -I/home/ongaeshi/WslCode/ruby.wasm/build/checkouts/3_2/enc/unicode/15.0.0 
        CPPFLAGS = -D_WASI_EMULATED_SIGNAL -D_WASI_EMULATED_MMAN -D_WASI_EMULATED_GETPID -D_WASI_EMULATED_PROCESS_CLOCKS   
        DLDFLAGS = -Xlinker -zstack-size=16777216 /home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/wasi-vfs-0.1.1/libwasi_vfs.a @/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js-ext/js/link.filelist @/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js-ext/witapi/link.filelist /home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js-ext/extinit.o  
        SOLIBS =  -lcrypt -lm -lwasi-emulated-mman -lwasi-emulated-signal -lwasi-emulated-getpid -lwasi-emulated-process-clocks 
        LANG = C.UTF-8
        LC_ALL = 
        LC_CTYPE = 
        MFLAGS = 
        RUSTC = rustc
        YJIT_RUSTC_ARGS = --crate-name=yjit --crate-type=staticlib --edition=2021 -g -C opt-level=3 -C overflow-checks=on '--out-dir=/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/yjit/target/release/' /home/ongaeshi/WslCode/ruby.wasm/build/checkouts/3_2/yjit/src/lib.rs
clang version 15.0.7
Target: wasm32-unknown-wasi
Thread model: posix
InstalledDir: /home/ongaeshi/wasi-sdk-19.0/bin
generating enc.mk
making srcs under enc
make[1]: Entering directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js'
make[1]: Nothing to be done for 'srcs'.
make[1]: Leaving directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js'
generating transdb.h
transdb.h updated
generating makefiles ext/configure-ext.mk
ext/configure-ext.mk unchanged
make[1]: Entering directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js'
make[2]: Entering directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/ext/bigdecimal'
make[2]: Leaving directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/ext/bigdecimal'
make[2]: Entering directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/ext/coverage'
make[2]: Leaving directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/ext/coverage'
make[2]: Entering directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/ext/date'
make[2]: Leaving directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/ext/date'
make[2]: Entering directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/ext/digest'
make[2]: Leaving directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/ext/digest'
make[2]: Entering directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/ext/digest/sha2'
make[2]: Leaving directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/ext/digest/sha2'
make[2]: Entering directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/ext/json'
make[2]: Leaving directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/ext/json'
make[2]: Entering directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/ext/monitor'
make[2]: Leaving directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/ext/monitor'
make[2]: Entering directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/ext/nkf'
make[2]: Leaving directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/ext/nkf'
make[2]: Entering directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/ext/objspace'
make[2]: Leaving directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/ext/objspace'
make[2]: Entering directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/ext/openssl'
compiling /home/ongaeshi/WslCode/ruby.wasm/build/checkouts/3_2/ext/openssl/ossl_x509store.c
/home/ongaeshi/WslCode/ruby.wasm/build/checkouts/3_2/ext/openssl/ossl_x509store.c:190:5: error: incomplete definition of type 'struct x509_store_st'
    X509_STORE_set_ex_data(store, store_ex_verify_cb_idx, (void *)cb);
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/ongaeshi/WslCode/ruby.wasm/build/checkouts/3_2/ext/openssl/openssl_missing.h:31:25: note: expanded from macro 'X509_STORE_set_ex_data'
        CRYPTO_set_ex_data(&(x)->ex_data, (idx), (data))
                            ~~~^
/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/openssl-3.0.5/opt/usr/local/include/openssl/types.h:164:16: note: forward declaration of 'struct x509_store_st'
typedef struct x509_store_st X509_STORE;
               ^
1 error generated.
make[2]: *** [Makefile:327: ossl_x509store.o] Error 1
make[2]: Leaving directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/ext/openssl'
make[1]: *** [exts.mk:207: ext/openssl/static] Error 2
make[1]: Leaving directory '/home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js'
make: *** [uncommon.mk:331: build-ext] Error 2
rake aborted!
Errno::ENOENT: No such file or directory @ rb_check_realpath_internal - /home/ongaeshi/WslCode/ruby.wasm/build/wasm32-unknown-wasi/3_2-wasm32-unknown-wasi-full-js/install
/home/ongaeshi/WslCode/ruby.wasm/lib/ruby_wasm/build_system/product/crossruby.rb:157:in `build'
/home/ongaeshi/WslCode/ruby.wasm/lib/ruby_wasm/rake_task.rb:77:in `block in initialize'
Tasks: TOP => build:3_2-wasm32-unknown-wasi-full-js
(See full trace by running task with --trace)

[ruby-head-wasm-wasi] Feature request to customize default import functions

Related to #126

I have looked around the code and the current JS extension is really impressive. The problem for me is I want to use subset of its capabilitiies for security concerns.

As far as I understand, it looks like this is the final list of the default import functions to WebAssembly and if we omit a function from here, the ruby side's JS extension will fail because there are no associated JS functions:

{
evalJs: wrapTry((code) => {
return Function(code)();
}),
isJs: (value) => {
// Just for compatibility with the old JS API
return true;
},
globalThis: () => {
if (typeof globalThis !== "undefined") {
return globalThis;
} else if (typeof global !== "undefined") {
return global;
} else if (typeof window !== "undefined") {
return window;
}
throw new Error("unable to locate global object");
},
intToJsNumber: (value) => {
return value;
},
stringToJsString: (value) => {
return value;
},
boolToJsBool: (value) => {
return value;
},
procToJsFunction: (rawRbAbiValue) => {
const rbValue = this.rbValueofPointer(rawRbAbiValue);
return (...args) => {
rbValue.call("call", ...args.map((arg) => this.wrap(arg)));
};
},
rbObjectToJsRbValue: (rawRbAbiValue) => {
return this.rbValueofPointer(rawRbAbiValue);
},
jsValueToString: (value) => {
// According to the [spec](https://tc39.es/ecma262/multipage/text-processing.html#sec-string-constructor-string-value)
// `String(value)` always returns a string.
return String(value);
},
jsValueToInteger(value) {
if (typeof value === "number") {
return { tag: "f64", val: value };
} else if (typeof value === "bigint") {
return { tag: "bignum", val: BigInt(value).toString(10) + "\0" };
} else if (typeof value === "string") {
return { tag: "bignum", val: value + "\0" };
} else if (typeof value === "undefined") {
return { tag: "f64", val: 0 };
} else {
return { tag: "f64", val: Number(value) };
}
},
exportJsValueToHost: (value) => {
// See `JsValueExporter` for the reason why we need to do this
this.transport.takeJsValue(value);
},
importJsValueFromHost: () => {
return this.transport.consumeJsValue();
},
instanceOf: (value, klass) => {
if (typeof klass === "function") {
return value instanceof klass;
} else {
return false;
}
},
jsValueTypeof(value) {
return typeof value;
},
jsValueEqual(lhs, rhs) {
return lhs == rhs;
},
jsValueStrictlyEqual(lhs, rhs) {
return lhs === rhs;
},
reflectApply: wrapTry((target, thisArgument, args) => {
return Reflect.apply(target as any, thisArgument, args);
}),
reflectConstruct: function (target, args) {
throw new Error("Function not implemented.");
},
reflectDeleteProperty: function (target, propertyKey): boolean {
throw new Error("Function not implemented.");
},
reflectGet: wrapTry((target, propertyKey) => {
return target[propertyKey];
}),
reflectGetOwnPropertyDescriptor: function (
target,
propertyKey: string
) {
throw new Error("Function not implemented.");
},
reflectGetPrototypeOf: function (target) {
throw new Error("Function not implemented.");
},
reflectHas: function (target, propertyKey): boolean {
throw new Error("Function not implemented.");
},
reflectIsExtensible: function (target): boolean {
throw new Error("Function not implemented.");
},
reflectOwnKeys: function (target) {
throw new Error("Function not implemented.");
},
reflectPreventExtensions: function (target): boolean {
throw new Error("Function not implemented.");
},
reflectSet: wrapTry((target, propertyKey, value) => {
return Reflect.set(target, propertyKey, value);
}),
reflectSetPrototypeOf: function (target, prototype): boolean {
throw new Error("Function not implemented.");
},
},

For example, if I remove evalJs here, JS::eval in ruby fails, which is ideal for me.

For security concerns, evalJs and globalThis needs more control. Therefore, I'm wondering whether we can add an option to customize these default import functions. The interface might look like:

const { vm } = await DefaultRubyVM(module, {
  importEvalJs: false,  // I don't want to allow `JS::Eval`
  importGlobalThis: ["Promise"],  // I don't want to allow to access `document`, etc., but `Promise` to use `.await` in the JS extension
});

Or, more transparent to the import functions definition:

const { vm } = await DefaultRubyVM(module, {
  imports: { // Override default imports if the field has the same name.
    evalJs: (code) => {
      throw new Error("Not supported");
    },
    globalThis: () => {
      if (typeof globalThis !== "undefined") { 
        return { Promise: globalThis.Promise }; 
      } else if (typeof global !== "undefined") { 
        return { Promise: global.Promise }; 
      } else if (typeof window !== "undefined") { 
        return { Promise: window.Promise }; 
      }
      throw new Error("unable to locate global object");  
    },
    // Other functions are imported untouched.
  }
});

I imagine some functionalities rely on the others, so these options might not be such simple, but how does this sound to you? If it looks good, I can work on a PR. These changes are only inside JS code and no need to touch wasm side at all.

I believe there are some use cases to allow the customers to run arbitrary program code inside a secure sandbox and WebAssembly is really useful for this because it can control which JS functionality is exposed to WebAssembly and vice versa. I hope this could help not only me but also some others.

Improve diagnostics for Asyncify buffer overflow

Currently, it just says RuntimeError: unreachable but it doesn't say it's caused by Asyncify at all.
We can determine the RuntimeError is caused by Asyncify or not by checking the asyncify buffer struct { start: i32, end: i32 } and the start pointer exceeds end pointer or not.

Make Ruby/Wasm instantiable within a Web Worker

By looking at the code it seems that the initialization scripts write the DefaultRubyVM and the rubyVM to the objects not existing in a Web Worker scope:

  • DefaultRubyVM is written to window in browser.umd.js
  • rubyVM is written to globalThis in browser.script.iife.js

In order to make it "web-worker" friendly ... it could detect whether it's running inside a web-worker and then put these objects in self?


The reason I'm asking this is because Ruby/Wasm seems to not-cooperate (read: regularly yield / return control to) with the JavaScript main event-loop when running things like "sleep" (running async code). The browser complaints that the "script is taking too long" or so ... I'm not complaining. Seems to be the way it is.

For example performing a sequence of paint operations on an HTML canvas does only reveal the end-result when the Ruby program completes. This looks like control is not returned to the browser until completely done. Which makes implementing things like animations / games impossible.

I figured the way to circumvent this would be to have it running as a web-worker and send "messages" to the main script, instructing it, for example, to perform paint operations in a canvas. But then it needs to be "bootstrappable" as a web-worker script, which needs a slightly different setup.

Question: How to implement step-by-step debugging?

Would it be possible to implement step-by-step debugging of a Ruby program driven by a HTML/JS user-inferface?

Or is it out of the question due to the non-blocking nature of JavaScript in the browser?

It seems that the TracePoint API works as expected (by trying it out in TryRuby.

However, I cannot imagine how to "suspend" execution inside of a line-handler, let the user interact with the UI for a while, inspect variables, evaluate things in context, and then continue with the execution.

I have no idea how command-line Ruby debuggers do this, but I suppose they can perform IO blocking operations in the console and when the user writes "next" (or so) then the handler function exits and the execution continues.

Am I right that this would not be possible in a browser context ...?

Any insights appreciated. Thanks.

Update .wit files to allow referencing between host and guest interfaces

Currently, we use JsValueTransport to facilitate moving values between the rb-abi-host and rb-abi-guest. Since the introduction of this workaround in #18 (May 2022), the WIT format implemented features such as worlds and import/exports. If we update our .wit files to use these new features I think JsValueTransport would no longer needed because the host and guest .wit can reference each other.

'Function not implemented' error on requiring gems from mounted FS

Hey @kateinoigakukun! Thanks for your work on ruby.wasm, I have a lot of fun playing with it!

Currently, I'm trying to add a gem to the wasmer instance right from the JS. File.read works fine, but if I'm trying to call require 'dry-initializer', I'm getting such error message:

# /usr/local/lib/ruby_gems is a custom dirrectory
puts File.read '/usr/local/lib/ruby_gems/dry-initializer-3.1.1/lib/dry-initializer.rb'
# =>
# # frozen_string_literal: true
#
# require_relative "dry/initializer"
require 'dry-initializer'
# =>
# Error: eval:3:in `require': Function not implemented -- /usr/local/lib/ruby_gems/dry-initializer-3.1.1/lib/dry-initializer.rb (LoadError)
# eval:3:in `<main>'

I can't find the source of this Function not implemented message, maybe you have an idea what is going on, and how may I found the source of the problem?

Here is a project with reproduction: https://github.com/skryukov/ruby-wasi-playground

[ruby-head-wasm-wasi] Is there any way to disable `js` module?

I'd like to use ruby.wasm as a sandboxed interpreter in browser i.e. just for pure computation with restricted DOM access because it executes untrusted code.

It looks like js module is available by default in ruby-head-wasm-wasi's DefaultRubyVM, but is there any way to disable it?
https://github.com/ruby/ruby.wasm/tree/main/packages/npm-packages/ruby-head-wasm-wasi#quick-start-for-browser

I'm a novice in the WebAssembly world, so it'll take time to read the whole code base of ruby.wasm for me. Therefore, I'm asking such a question beforehand in case someone already knows the answer...

Sorry for bothering you guys.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.