ruby_wasm 2.5.1-aarch64-linux-musl
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.clang-format +8 -0
- data/CONTRIBUTING.md +128 -0
- data/Gemfile +17 -0
- data/LICENSE +21 -0
- data/NOTICE +1293 -0
- data/README.md +154 -0
- data/Rakefile +164 -0
- data/Steepfile +24 -0
- data/benchmarks/vm_deep_call.rb +55 -0
- data/docs/api.md +2 -0
- data/docs/cheat_sheet.md +195 -0
- data/docs/faq.md +25 -0
- data/exe/rbwasm +7 -0
- data/ext/.gitignore +2 -0
- data/ext/README.md +11 -0
- data/ext/extinit.c.erb +32 -0
- data/lib/ruby_wasm/3.1/ruby_wasm.so +0 -0
- data/lib/ruby_wasm/3.2/ruby_wasm.so +0 -0
- data/lib/ruby_wasm/3.3/ruby_wasm.so +0 -0
- data/lib/ruby_wasm/build/build_params.rb +3 -0
- data/lib/ruby_wasm/build/downloader.rb +18 -0
- data/lib/ruby_wasm/build/executor.rb +191 -0
- data/lib/ruby_wasm/build/product/baseruby.rb +37 -0
- data/lib/ruby_wasm/build/product/crossruby.rb +360 -0
- data/lib/ruby_wasm/build/product/libyaml.rb +70 -0
- data/lib/ruby_wasm/build/product/openssl.rb +93 -0
- data/lib/ruby_wasm/build/product/product.rb +39 -0
- data/lib/ruby_wasm/build/product/ruby_source.rb +103 -0
- data/lib/ruby_wasm/build/product/wasi_vfs.rb +45 -0
- data/lib/ruby_wasm/build/product/zlib.rb +70 -0
- data/lib/ruby_wasm/build/product.rb +8 -0
- data/lib/ruby_wasm/build/target.rb +24 -0
- data/lib/ruby_wasm/build/toolchain/wit_bindgen.rb +31 -0
- data/lib/ruby_wasm/build/toolchain.rb +193 -0
- data/lib/ruby_wasm/build.rb +92 -0
- data/lib/ruby_wasm/cli.rb +347 -0
- data/lib/ruby_wasm/packager/component_adapter/wasi_snapshot_preview1.command.wasm +0 -0
- data/lib/ruby_wasm/packager/component_adapter/wasi_snapshot_preview1.reactor.wasm +0 -0
- data/lib/ruby_wasm/packager/component_adapter.rb +14 -0
- data/lib/ruby_wasm/packager/core.rb +333 -0
- data/lib/ruby_wasm/packager/file_system.rb +160 -0
- data/lib/ruby_wasm/packager.rb +96 -0
- data/lib/ruby_wasm/rake_task.rb +60 -0
- data/lib/ruby_wasm/util.rb +15 -0
- data/lib/ruby_wasm/version.rb +3 -0
- data/lib/ruby_wasm.rb +34 -0
- data/package-lock.json +9777 -0
- data/package.json +12 -0
- data/rakelib/check.rake +37 -0
- data/rakelib/ci.rake +152 -0
- data/rakelib/doc.rake +29 -0
- data/rakelib/format.rake +35 -0
- data/rakelib/gem.rake +22 -0
- data/rakelib/packaging.rake +165 -0
- data/rakelib/version.rake +40 -0
- data/sig/open_uri.rbs +4 -0
- data/sig/ruby_wasm/build.rbs +327 -0
- data/sig/ruby_wasm/cli.rbs +51 -0
- data/sig/ruby_wasm/ext.rbs +26 -0
- data/sig/ruby_wasm/packager.rbs +122 -0
- data/sig/ruby_wasm/util.rbs +5 -0
- data/tools/clang-format-diff.sh +18 -0
- data/tools/exe/rbminify +12 -0
- data/tools/lib/syntax_tree/minify_ruby.rb +63 -0
- metadata +114 -0
data/README.md
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
# ruby.wasm
|
2
|
+
|
3
|
+
[![Build ruby.wasm](https://github.com/ruby/ruby.wasm/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/ruby/ruby.wasm/actions/workflows/build.yml)
|
4
|
+
|
5
|
+
ruby.wasm is a collection of WebAssembly ports of the [CRuby](https://github.com/ruby/ruby).
|
6
|
+
It enables running Ruby application on browsers, WASI compatible WebAssembly runtimes, and Edge Computing platforms.
|
7
|
+
|
8
|
+
## Try ruby.wasm (no installation needed)
|
9
|
+
|
10
|
+
Try ruby.wasm in [TryRuby](https://try.ruby-lang.org/playground#code=puts+RUBY_DESCRIPTION&engine=cruby-3.2.0dev) in your browser.
|
11
|
+
|
12
|
+
## Quick Links
|
13
|
+
|
14
|
+
- [Cheat Sheet](https://github.com/ruby/ruby.wasm/blob/main/docs/cheat_sheet.md)
|
15
|
+
- [FAQ](https://github.com/ruby/ruby.wasm/blob/main/docs/faq.md)
|
16
|
+
- [API Reference](https://github.com/ruby/ruby.wasm/blob/main/docs/api.md)
|
17
|
+
- [Complete Examples](https://github.com/ruby/ruby.wasm/tree/main/packages/npm-packages/ruby-wasm-wasi/example)
|
18
|
+
- [Community Showcase](https://github.com/ruby/ruby.wasm/wiki/Showcase)
|
19
|
+
|
20
|
+
## Quick Example: Ruby on browser
|
21
|
+
|
22
|
+
Create and save `index.html` page with the following contents:
|
23
|
+
|
24
|
+
```html
|
25
|
+
<html>
|
26
|
+
<script src="https://cdn.jsdelivr.net/npm/@ruby/3.3-wasm-wasi@2.5.1/dist/browser.script.iife.js"></script>
|
27
|
+
<script type="text/ruby">
|
28
|
+
require "js"
|
29
|
+
|
30
|
+
puts RUBY_VERSION # => Hello, world! (printed to the browser console)
|
31
|
+
JS.global[:document].write "Hello, world!"
|
32
|
+
</script>
|
33
|
+
</html>
|
34
|
+
```
|
35
|
+
|
36
|
+
## Quick Example: How to package your Ruby application as a WASI application
|
37
|
+
|
38
|
+
Dependencies: [wasmtime](https://github.com/bytecodealliance/wasmtime)
|
39
|
+
|
40
|
+
```console
|
41
|
+
$ gem install ruby_wasm
|
42
|
+
# Download a prebuilt Ruby release
|
43
|
+
$ curl -LO https://github.com/ruby/ruby.wasm/releases/latest/download/ruby-3.3-wasm32-unknown-wasip1-full.tar.gz
|
44
|
+
$ tar xfz ruby-3.3-wasm32-unknown-wasip1-full.tar.gz
|
45
|
+
|
46
|
+
# Extract ruby binary not to pack itself
|
47
|
+
$ mv ruby-3.3-wasm32-unknown-wasip1-full/usr/local/bin/ruby ruby.wasm
|
48
|
+
|
49
|
+
# Put your app code
|
50
|
+
$ mkdir src
|
51
|
+
$ echo "puts 'Hello'" > src/my_app.rb
|
52
|
+
|
53
|
+
# Pack the whole directory under /usr and your app dir
|
54
|
+
$ rbwasm pack ruby.wasm --dir ./src::/src --dir ./ruby-3.3-wasm32-unknown-wasip1-full/usr::/usr -o my-ruby-app.wasm
|
55
|
+
|
56
|
+
# Run the packed scripts
|
57
|
+
$ wasmtime my-ruby-app.wasm /src/my_app.rb
|
58
|
+
Hello
|
59
|
+
```
|
60
|
+
|
61
|
+
## npm packages (for JavaScript host environments)
|
62
|
+
|
63
|
+
See the `README.md` of each package for more detail and its usage.
|
64
|
+
|
65
|
+
<table>
|
66
|
+
<thead>
|
67
|
+
<tr>
|
68
|
+
<th>Package</th>
|
69
|
+
<th>Description</th>
|
70
|
+
<th>npm</th>
|
71
|
+
</tr>
|
72
|
+
</thead>
|
73
|
+
<tbody>
|
74
|
+
<tr>
|
75
|
+
<td><a href="/packages/npm-packages/ruby-3.3-wasm-wasi">@ruby/3.3-wasm-wasi</a></td>
|
76
|
+
<td>CRuby 3.3 built on WASI with JS interop support</td>
|
77
|
+
<td><a href="https://www.npmjs.com/package/@ruby/3.3-wasm-wasi" rel="nofollow"><img src="https://badge.fury.io/js/@ruby%2F3.3-wasm-wasi.svg" alt="npm version" style="max-width: 100%;"></a></td>
|
78
|
+
</tr>
|
79
|
+
<tr>
|
80
|
+
<td><a href="/packages/npm-packages/ruby-3.2-wasm-wasi">@ruby/3.2-wasm-wasi</a></td>
|
81
|
+
<td>CRuby 3.2 built on WASI with JS interop support</td>
|
82
|
+
<td><a href="https://www.npmjs.com/package/@ruby/3.2-wasm-wasi" rel="nofollow"><img src="https://badge.fury.io/js/@ruby%2F3.2-wasm-wasi.svg" alt="npm version" style="max-width: 100%;"></a></td>
|
83
|
+
</tr>
|
84
|
+
<tr>
|
85
|
+
<td><a href="/packages/npm-packages/ruby-head-wasm-wasi">@ruby/head-wasm-wasi</a></td>
|
86
|
+
<td>HEAD CRuby built on WASI with JS interop support</td>
|
87
|
+
<td><a href="https://www.npmjs.com/package/@ruby/head-wasm-wasi" rel="nofollow"><img src="https://badge.fury.io/js/@ruby%2Fhead-wasm-wasi.svg" alt="npm version" style="max-width: 100%;"></a></td>
|
88
|
+
</tr>
|
89
|
+
<tr>
|
90
|
+
<td><a href="/packages/npm-packages/ruby-head-wasm-emscripten">@ruby/head-wasm-emscripten</a></td>
|
91
|
+
<td>HEAD CRuby built on Emscripten (not well tested)</td>
|
92
|
+
<td><a href="https://www.npmjs.com/package/@ruby/head-wasm-emscripten" rel="nofollow"><img src="https://badge.fury.io/js/@ruby%2Fhead-wasm-emscripten.svg" alt="npm version" style="max-width: 100%;"></a></td>
|
93
|
+
</tr>
|
94
|
+
</tbody>
|
95
|
+
</table>
|
96
|
+
|
97
|
+
## Prebuilt binaries
|
98
|
+
|
99
|
+
This project distributes [prebuilt Ruby binaries in GitHub Releases](https://github.com/ruby/ruby.wasm/releases).
|
100
|
+
A _build_ is a combination of ruby version, _profile_, and _target_.
|
101
|
+
|
102
|
+
### Supported Target Triples
|
103
|
+
|
104
|
+
<table>
|
105
|
+
<thead>
|
106
|
+
<tr>
|
107
|
+
<th>Triple</th>
|
108
|
+
<th>Description</th>
|
109
|
+
</tr>
|
110
|
+
</thead>
|
111
|
+
<tbody>
|
112
|
+
<tr>
|
113
|
+
<td><code>wasm32-unknown-wasip1</code></td>
|
114
|
+
<td>Targeting <a href="https://github.com/WebAssembly/WASI/tree/main/legacy/preview1">WASI Preview1</a> compatible environments <br>(e.g. Node.js, browsers with polyfill, <a href="https://github.com/bytecodealliance/wasmtime">wasmtime</a>, and so on)</td>
|
115
|
+
</tr>
|
116
|
+
<tr>
|
117
|
+
<td><code>wasm32-unknown-emscripten</code></td>
|
118
|
+
<td>Targeting JavaScript environments including Node.js and browsers</td>
|
119
|
+
</tr>
|
120
|
+
</tbody>
|
121
|
+
</table>
|
122
|
+
|
123
|
+
### Profiles
|
124
|
+
|
125
|
+
<table>
|
126
|
+
<thead>
|
127
|
+
<tr>
|
128
|
+
<th>Profile</th>
|
129
|
+
<th>Description</th>
|
130
|
+
</tr>
|
131
|
+
</thead>
|
132
|
+
<tbody>
|
133
|
+
<tr>
|
134
|
+
<td><code>minimal</code></td>
|
135
|
+
<td>No standard extension libraries (like <code>json</code>, <code>yaml</code>, or <code>stringio</code>)</td>
|
136
|
+
</tr>
|
137
|
+
<tr>
|
138
|
+
<td><code>full</code></td>
|
139
|
+
<td>All standard extension libraries</td>
|
140
|
+
</tr>
|
141
|
+
</tbody>
|
142
|
+
</table>
|
143
|
+
|
144
|
+
## Notable Limitations
|
145
|
+
|
146
|
+
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.
|
147
|
+
|
148
|
+
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.
|
149
|
+
|
150
|
+
|
151
|
+
## Contributing
|
152
|
+
|
153
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for how to build and test, and how to contribute to this project.
|
154
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/ruby.wasm
|
data/Rakefile
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
require "rake"
|
2
|
+
require "json"
|
3
|
+
require "open-uri"
|
4
|
+
|
5
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), "lib")
|
6
|
+
|
7
|
+
require "bundler/gem_tasks"
|
8
|
+
require "ruby_wasm/rake_task"
|
9
|
+
require "ruby_wasm/packager"
|
10
|
+
require "ruby_wasm/cli"
|
11
|
+
|
12
|
+
BUILD_SOURCES = %w[3.3 3.2 head]
|
13
|
+
BUILD_PROFILES = %w[full minimal]
|
14
|
+
|
15
|
+
BUILDS =
|
16
|
+
BUILD_SOURCES
|
17
|
+
.product(BUILD_PROFILES)
|
18
|
+
.map { |src, profile| [src, "wasm32-unknown-wasip1", profile] } +
|
19
|
+
BUILD_SOURCES.map { |src| [src, "wasm32-unknown-emscripten", "full"] }
|
20
|
+
|
21
|
+
NPM_PACKAGES = [
|
22
|
+
{
|
23
|
+
name: "ruby-head-wasm-emscripten",
|
24
|
+
ruby_version: "head",
|
25
|
+
gemfile: nil,
|
26
|
+
target: "wasm32-unknown-emscripten"
|
27
|
+
},
|
28
|
+
{
|
29
|
+
name: "ruby-head-wasm-wasi",
|
30
|
+
ruby_version: "head",
|
31
|
+
gemfile: "packages/npm-packages/ruby-wasm-wasi/Gemfile",
|
32
|
+
target: "wasm32-unknown-wasip1"
|
33
|
+
},
|
34
|
+
{
|
35
|
+
name: "ruby-3.3-wasm-wasi",
|
36
|
+
ruby_version: "3.3",
|
37
|
+
gemfile: "packages/npm-packages/ruby-wasm-wasi/Gemfile",
|
38
|
+
target: "wasm32-unknown-wasip1"
|
39
|
+
},
|
40
|
+
{
|
41
|
+
name: "ruby-3.2-wasm-wasi",
|
42
|
+
ruby_version: "3.2",
|
43
|
+
gemfile: "packages/npm-packages/ruby-wasm-wasi/Gemfile",
|
44
|
+
target: "wasm32-unknown-wasip1"
|
45
|
+
},
|
46
|
+
{ name: "ruby-wasm-wasi", target: "wasm32-unknown-wasip1" }
|
47
|
+
]
|
48
|
+
|
49
|
+
STANDALONE_PACKAGES = [
|
50
|
+
{ name: "ruby", build: "head-wasm32-unknown-wasip1-full" },
|
51
|
+
{ name: "irb", build: "head-wasm32-unknown-wasip1-full" }
|
52
|
+
]
|
53
|
+
|
54
|
+
LIB_ROOT = File.dirname(__FILE__)
|
55
|
+
|
56
|
+
TOOLCHAINS = {}
|
57
|
+
BUILDS
|
58
|
+
.map { |_, target, _| target }
|
59
|
+
.uniq
|
60
|
+
.each do |target|
|
61
|
+
build_dir = File.join(LIB_ROOT, "build")
|
62
|
+
toolchain = RubyWasm::Toolchain.get(target, build_dir)
|
63
|
+
TOOLCHAINS[toolchain.name] = toolchain
|
64
|
+
end
|
65
|
+
|
66
|
+
class BuildTask < Struct.new(:name, :target, :build_command)
|
67
|
+
def ruby_cache_key
|
68
|
+
return @key if @key
|
69
|
+
require "open3"
|
70
|
+
cmd = build_command + ["--print-ruby-cache-key"]
|
71
|
+
stdout, status = Open3.capture2(*cmd)
|
72
|
+
unless status.success?
|
73
|
+
raise "Command failed with status (#{status.exitstatus}): #{cmd.join ""}"
|
74
|
+
end
|
75
|
+
require "json"
|
76
|
+
@key = JSON.parse(stdout)
|
77
|
+
end
|
78
|
+
|
79
|
+
def hexdigest
|
80
|
+
ruby_cache_key["hexdigest"]
|
81
|
+
end
|
82
|
+
def artifact
|
83
|
+
ruby_cache_key["artifact"]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
namespace :build do
|
88
|
+
BUILD_TASKS =
|
89
|
+
BUILDS.map do |src, target, profile|
|
90
|
+
name = "#{src}-#{target}-#{profile}"
|
91
|
+
|
92
|
+
build_command = [
|
93
|
+
"exe/rbwasm",
|
94
|
+
"build",
|
95
|
+
"--ruby-version",
|
96
|
+
src,
|
97
|
+
"--target",
|
98
|
+
target,
|
99
|
+
"--build-profile",
|
100
|
+
profile,
|
101
|
+
"--disable-gems",
|
102
|
+
"-o",
|
103
|
+
"/dev/null"
|
104
|
+
]
|
105
|
+
desc "Cross-build Ruby for #{target}"
|
106
|
+
task name do
|
107
|
+
sh *build_command
|
108
|
+
end
|
109
|
+
namespace name do
|
110
|
+
task :remake do
|
111
|
+
sh *build_command, "--remake"
|
112
|
+
end
|
113
|
+
task :reconfigure do
|
114
|
+
sh *build_command, "--reconfigure"
|
115
|
+
end
|
116
|
+
task :clean do
|
117
|
+
sh *build_command, "--clean"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
BuildTask.new(name, target, build_command)
|
122
|
+
end
|
123
|
+
|
124
|
+
desc "Clean build directories"
|
125
|
+
task :clean do
|
126
|
+
rm_rf "./build"
|
127
|
+
rm_rf "./rubies"
|
128
|
+
end
|
129
|
+
|
130
|
+
desc "Download prebuilt Ruby"
|
131
|
+
task :download_prebuilt, :tag do |t, args|
|
132
|
+
require "ruby_wasm/build/downloader"
|
133
|
+
|
134
|
+
release =
|
135
|
+
if args[:tag]
|
136
|
+
url =
|
137
|
+
"https://api.github.com/repos/ruby/ruby.wasm/releases/tags/#{args[:tag]}"
|
138
|
+
OpenURI.open_uri(url) { |f| JSON.load(f.read) }
|
139
|
+
else
|
140
|
+
url = "https://api.github.com/repos/ruby/ruby.wasm/releases?per_page=1"
|
141
|
+
OpenURI.open_uri(url) { |f| JSON.load(f.read)[0] }
|
142
|
+
end
|
143
|
+
|
144
|
+
puts "Downloading from release \"#{release["tag_name"]}\""
|
145
|
+
|
146
|
+
rubies_dir = "./rubies"
|
147
|
+
downloader = RubyWasm::Downloader.new
|
148
|
+
rm_rf rubies_dir
|
149
|
+
mkdir_p rubies_dir
|
150
|
+
|
151
|
+
assets = release["assets"].select { |a| a["name"].end_with? ".tar.gz" }
|
152
|
+
assets.each_with_index do |asset, i|
|
153
|
+
url = asset["browser_download_url"]
|
154
|
+
tarball = File.join("rubies", asset["name"])
|
155
|
+
rm_rf tarball, verbose: false
|
156
|
+
downloader.download(
|
157
|
+
url,
|
158
|
+
tarball,
|
159
|
+
"[%2d/%2d] Downloading #{File.basename(url)}" % [i + 1, assets.size]
|
160
|
+
)
|
161
|
+
sh "tar xzf #{tarball} -C ./rubies"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
data/Steepfile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
D = Steep::Diagnostic
|
2
|
+
|
3
|
+
target :lib do
|
4
|
+
signature "sig"
|
5
|
+
|
6
|
+
check "lib"
|
7
|
+
# RBS's stdlib signatures don't have rake signatures yet.
|
8
|
+
ignore "lib/ruby_wasm/rake_task.rb"
|
9
|
+
|
10
|
+
library "digest"
|
11
|
+
library "tmpdir"
|
12
|
+
library "fileutils"
|
13
|
+
library "open-uri"
|
14
|
+
library "uri"
|
15
|
+
library "shellwords"
|
16
|
+
library "io-console"
|
17
|
+
library "optparse"
|
18
|
+
library "json"
|
19
|
+
library "logger"
|
20
|
+
library "pathname"
|
21
|
+
library "forwardable"
|
22
|
+
|
23
|
+
configure_code_diagnostics(D::Ruby.default)
|
24
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# This script checks the max number of call frames under WebAssembly restriction
|
2
|
+
#
|
3
|
+
# Example runs
|
4
|
+
# $ ruby vm_deep_call.rb
|
5
|
+
# $ RUBY_EXE="wasmtime run --dir /::/ head-wasm32-unknown-wasi-minimal/usr/local/bin/ruby --" ruby vm_deep_call.rb
|
6
|
+
# $ RUBY_EXE="wasmtime run --env RUBY_FIBER_MACHINE_STACK_SIZE=20971520 --dir /::/ head-wasm32-unknown-wasi-minimal/usr/local/bin/ruby --" ruby vm_deep_call.rb
|
7
|
+
|
8
|
+
def vm_rec n
|
9
|
+
vm_rec n - 1 if n > 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def vm_c_rec n
|
13
|
+
1.times do
|
14
|
+
vm_c_rec n - 1 if n > 0
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def vm_rec_fiber n
|
19
|
+
Fiber.new { vm_rec n }.resume
|
20
|
+
end
|
21
|
+
|
22
|
+
def vm_c_rec_fiber n
|
23
|
+
Fiber.new { vm_c_rec n }.resume
|
24
|
+
end
|
25
|
+
|
26
|
+
def check(ruby_exe, target, n)
|
27
|
+
cmd = %Q(#{ruby_exe} -r #{File.expand_path(__FILE__)} -e "#{target}(#{n})")
|
28
|
+
Kernel.system(cmd, err: File::NULL)
|
29
|
+
end
|
30
|
+
|
31
|
+
def bisect(ruby_exe, target)
|
32
|
+
min = 0
|
33
|
+
max = 15000
|
34
|
+
while min < max
|
35
|
+
mid = (min + max) / 2
|
36
|
+
ok = check(ruby_exe, target, mid)
|
37
|
+
if ok
|
38
|
+
min = mid + 1
|
39
|
+
else
|
40
|
+
max = mid
|
41
|
+
end
|
42
|
+
end
|
43
|
+
min
|
44
|
+
end
|
45
|
+
|
46
|
+
def main
|
47
|
+
ruby_exe = ENV['RUBY_EXE'] || 'ruby'
|
48
|
+
puts "How deep the call stack can be"
|
49
|
+
puts " with only VM calls: " + bisect(ruby_exe, "vm_rec").to_s
|
50
|
+
puts " with only VM calls in Fiber: " + bisect(ruby_exe, "vm_rec_fiber").to_s
|
51
|
+
puts " with VM and C calls: " + bisect(ruby_exe, "vm_c_rec").to_s
|
52
|
+
puts " with VM and C calls in Fiber: " + bisect(ruby_exe, "vm_c_rec_fiber").to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
main if $0 == __FILE__
|
data/docs/api.md
ADDED
data/docs/cheat_sheet.md
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
[[**Cheat Sheet**]](./cheat_sheet.md)
|
2
|
+
[[**FAQ**]](./faq.md)
|
3
|
+
[[**API Reference**]](./api.md)
|
4
|
+
[[**Complete Examples**]](https://github.com/ruby/ruby.wasm/tree/main/packages/npm-packages/ruby-wasm-wasi/example)
|
5
|
+
[[**Community Showcase**]](https://github.com/ruby/ruby.wasm/wiki/Showcase)
|
6
|
+
|
7
|
+
# ruby.wasm Cheat Sheet
|
8
|
+
|
9
|
+
## Node.js
|
10
|
+
|
11
|
+
To install the package, install `@ruby/3.3-wasm-wasi` and `@ruby/wasm-wasi` from npm:
|
12
|
+
|
13
|
+
```console
|
14
|
+
npm install --save @ruby/3.3-wasm-wasi @ruby/wasm-wasi
|
15
|
+
```
|
16
|
+
|
17
|
+
Then instantiate a Ruby VM by the following code:
|
18
|
+
|
19
|
+
```javascript
|
20
|
+
import fs from "fs/promises";
|
21
|
+
import { DefaultRubyVM } from "@ruby/wasm-wasi/dist/node";
|
22
|
+
|
23
|
+
const binary = await fs.readFile("./node_modules/@ruby/3.3-wasm-wasi/dist/ruby.wasm");
|
24
|
+
const module = await WebAssembly.compile(binary);
|
25
|
+
const { vm } = await DefaultRubyVM(module);
|
26
|
+
vm.eval(`puts "hello world"`);
|
27
|
+
```
|
28
|
+
|
29
|
+
Then run the example code with `--experimental-wasi-unstable-preview1` flag to enable WASI support:
|
30
|
+
|
31
|
+
```console
|
32
|
+
$ node --experimental-wasi-unstable-preview1 index.mjs
|
33
|
+
```
|
34
|
+
|
35
|
+
## Browser
|
36
|
+
|
37
|
+
The easiest way to run Ruby on browser is to use `browser.script.iife.js` script from CDN:
|
38
|
+
|
39
|
+
```html
|
40
|
+
<html>
|
41
|
+
<script src="https://cdn.jsdelivr.net/npm/@ruby/3.3-wasm-wasi@2.5.1/dist/browser.script.iife.js"></script>
|
42
|
+
<script type="text/ruby">
|
43
|
+
require "js"
|
44
|
+
JS.global[:document].write "Hello, world!"
|
45
|
+
</script>
|
46
|
+
</html>
|
47
|
+
```
|
48
|
+
|
49
|
+
If you want to control Ruby VM from JavaScript, you can use `@ruby/wasm-wasi` package API:
|
50
|
+
|
51
|
+
```html
|
52
|
+
<html>
|
53
|
+
<script type="module">
|
54
|
+
import { DefaultRubyVM } from "https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.5.1/dist/browser/+esm";
|
55
|
+
const response = await fetch("https://cdn.jsdelivr.net/npm/@ruby/3.3-wasm-wasi@2.5.1/dist/ruby+stdlib.wasm");
|
56
|
+
const module = await WebAssembly.compileStreaming(response);
|
57
|
+
const { vm } = await DefaultRubyVM(module);
|
58
|
+
|
59
|
+
vm.eval(`
|
60
|
+
require "js"
|
61
|
+
JS.global[:document].write "Hello, world!"
|
62
|
+
`);
|
63
|
+
</script>
|
64
|
+
</html>
|
65
|
+
```
|
66
|
+
|
67
|
+
<details>
|
68
|
+
<summary>Alternative: Without ES Modules</summary>
|
69
|
+
|
70
|
+
```html
|
71
|
+
<html>
|
72
|
+
<script src="https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.5.1/dist/browser.umd.js"></script>
|
73
|
+
<script>
|
74
|
+
const main = async () => {
|
75
|
+
const { DefaultRubyVM } = window["ruby-wasm-wasi"];
|
76
|
+
const response = await fetch("https://cdn.jsdelivr.net/npm/@ruby/3.3-wasm-wasi@2.5.1/dist/ruby+stdlib.wasm");
|
77
|
+
const module = await WebAssembly.compileStreaming(response);
|
78
|
+
const { vm } = await DefaultRubyVM(module);
|
79
|
+
|
80
|
+
vm.eval(`
|
81
|
+
require "js"
|
82
|
+
JS.global[:document].write "Hello, world!"
|
83
|
+
`);
|
84
|
+
}
|
85
|
+
main()
|
86
|
+
</script>
|
87
|
+
</html>
|
88
|
+
```
|
89
|
+
</details>
|
90
|
+
|
91
|
+
## Use JavaScript from Ruby
|
92
|
+
|
93
|
+
### Get/set JavaScript variables from Ruby
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
require "js"
|
97
|
+
|
98
|
+
document = JS.global[:document]
|
99
|
+
document[:title] = "Hello, world!"
|
100
|
+
```
|
101
|
+
|
102
|
+
### Call JavaScript methods from Ruby
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
require "js"
|
106
|
+
|
107
|
+
JS.global[:document].createElement("div")
|
108
|
+
|
109
|
+
JS.global[:document].call(:createElement, "div".to_js) # same as above
|
110
|
+
```
|
111
|
+
|
112
|
+
### Pass Ruby `Proc` to JavaScript (Callback to Ruby)
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
require "js"
|
116
|
+
|
117
|
+
JS.global.setTimeout(proc { puts "Hello, world!" }, 1000)
|
118
|
+
|
119
|
+
input = JS.global[:document].querySelector("input")
|
120
|
+
input.addEventListener("change") do |event|
|
121
|
+
puts event[:target][:value].to_s
|
122
|
+
end
|
123
|
+
```
|
124
|
+
|
125
|
+
### `await` JavaScript `Promise` from Ruby
|
126
|
+
|
127
|
+
`data-eval="async"` attribute is required to use `await` in `<script>` tag:
|
128
|
+
|
129
|
+
```html
|
130
|
+
<html>
|
131
|
+
<script src="https://cdn.jsdelivr.net/npm/@ruby/3.3-wasm-wasi@2.5.1/dist/browser.script.iife.js"></script>
|
132
|
+
<script type="text/ruby" data-eval="async">
|
133
|
+
require "js"
|
134
|
+
|
135
|
+
response = JS.global.fetch("https://www.ruby-lang.org/").await
|
136
|
+
puts response[:status]
|
137
|
+
</script>
|
138
|
+
</html>
|
139
|
+
```
|
140
|
+
|
141
|
+
Or using `@ruby/wasm-wasi` package API `RubyVM#evalAsync`:
|
142
|
+
|
143
|
+
```html
|
144
|
+
<html>
|
145
|
+
<script type="module">
|
146
|
+
import { DefaultRubyVM } from "https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.5.1/dist/browser/+esm";
|
147
|
+
const response = await fetch("https://cdn.jsdelivr.net/npm/@ruby/3.3-wasm-wasi@2.5.1/dist/ruby+stdlib.wasm");
|
148
|
+
const module = await WebAssembly.compileStreaming(response);
|
149
|
+
const { vm } = await DefaultRubyVM(module);
|
150
|
+
|
151
|
+
vm.evalAsync(`
|
152
|
+
require "js"
|
153
|
+
|
154
|
+
response = JS.global.fetch("https://www.ruby-lang.org/").await
|
155
|
+
puts response[:status]
|
156
|
+
`);
|
157
|
+
</script>
|
158
|
+
</html>
|
159
|
+
```
|
160
|
+
|
161
|
+
### `new` JavaScript instance from Ruby
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
require "js"
|
165
|
+
|
166
|
+
JS.global[:Date].new(2000, 9, 13)
|
167
|
+
```
|
168
|
+
|
169
|
+
### Convert returned JavaScript `String` value to Ruby `String`
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
require "js"
|
173
|
+
|
174
|
+
title = JS.global[:document].title # => JS::Object("Hello, world!")
|
175
|
+
title.to_s # => "Hello, world!"
|
176
|
+
```
|
177
|
+
|
178
|
+
### Convert JavaScript `Boolean` value to Ruby `true`/`false`
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
require "js"
|
182
|
+
|
183
|
+
JS.global[:document].hasFocus? # => true
|
184
|
+
JS.global[:document].hasFocus # => JS::Object(true)
|
185
|
+
```
|
186
|
+
|
187
|
+
### Convert JavaScript `Number` value to Ruby `Integer`/`Float`
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
require "js"
|
191
|
+
|
192
|
+
rand = JS.global[:Math].random # JS::Object(0.123456789)
|
193
|
+
rand.to_i # => 0
|
194
|
+
rand.to_f # => 0.123456789
|
195
|
+
```
|
data/docs/faq.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
[[**Cheat Sheet**]](./cheat_sheet.md)
|
2
|
+
[[**FAQ**]](./faq.md)
|
3
|
+
[[**API Reference**]](./api.md)
|
4
|
+
[[**Complete Examples**]](https://github.com/ruby/ruby.wasm/tree/main/packages/npm-packages/ruby-wasm-wasi/example)
|
5
|
+
[[**Community Showcase**]](https://github.com/ruby/ruby.wasm/wiki/Showcase)
|
6
|
+
|
7
|
+
# FAQ
|
8
|
+
|
9
|
+
## Where my `puts` output goes?
|
10
|
+
|
11
|
+
By default, `puts` output goes to `STDOUT` which is a JavaScript `console.log` function. You can override it by setting `$stdout` to a Ruby object which has `write` method.
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
$stdout = Object.new.tap do |obj|
|
15
|
+
def obj.write(str)
|
16
|
+
JS.global[:document].write(str)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
puts "Hello, world!" # => Prints "Hello, world!" to the HTML document
|
21
|
+
```
|
22
|
+
|
23
|
+
## How to run WebAssembly in Ruby
|
24
|
+
|
25
|
+
Use [`wasmtime` Ruby gem](https://rubygems.org/gems/wasmtime).
|
data/exe/rbwasm
ADDED
data/ext/.gitignore
ADDED
data/ext/README.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Ruby extensions
|
2
|
+
`ruby.wasm` uses two C extensions to turn Ruby in to a guest module.
|
3
|
+
The `js` extension enables Ruby to use JavaScript APIs.
|
4
|
+
The `witapi` extension exports Ruby's interpreter interface to allow the host to use the Ruby interpreter.
|
5
|
+
In other words, `js` allows Ruby to talk to Javascript and `witapi` allows a host to talk to Ruby.
|
6
|
+
|
7
|
+
Under each subdirectory, there is a `bindgen/*.wit` file outlining the interfaces for each form of communication.
|
8
|
+
Specifically, `bindgen/rb-js-abi-host.wit` describes embedder's requirements and `bindgen/rb-js-abi-guest.wit` describes exported API from Ruby interpreter.
|
9
|
+
The `.c` and `.h` files are autogenerated from [wit-bindgen](https://github.com/bytecodealliance/wit-bindgen#host-runtimes-for-components).
|
10
|
+
You can read more about it in the [contributing guide](/CONTRIBUTING.md#re-bindgen-from-wit-files).
|
11
|
+
Note that we currently do not use the latest version of wit-bindgen because of how fast it is changing, with features being changed or even [removed](https://github.com/bytecodealliance/wit-bindgen/pull/346) at times.
|
data/ext/extinit.c.erb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require "erb"
|
2
|
+
require "optparse"
|
3
|
+
|
4
|
+
opts = OptionParser.new
|
5
|
+
opts.on("--cc CC") {|cc| @cc = cc }
|
6
|
+
opts.on("--output FILE") {|o| @o = o }
|
7
|
+
|
8
|
+
opts.parse!(ARGV)
|
9
|
+
if @cc.nil? || @o.nil?
|
10
|
+
puts opts.help
|
11
|
+
exit 1
|
12
|
+
end
|
13
|
+
|
14
|
+
exts = ARGV
|
15
|
+
|
16
|
+
c_src = ERB.new(DATA.read).result
|
17
|
+
IO.popen([@cc, "-c", "-xc", "-", "-o", @o], "w") {|f| f << c_src }
|
18
|
+
exit $?.exitstatus
|
19
|
+
|
20
|
+
__END__
|
21
|
+
#define init(func, name) { \
|
22
|
+
extern void func(void); \
|
23
|
+
ruby_init_ext(name".so", func); \
|
24
|
+
}
|
25
|
+
|
26
|
+
void ruby_init_ext(const char *name, void (*init)(void));
|
27
|
+
|
28
|
+
void Init_extra_exts(void) {
|
29
|
+
<% exts.each do |ext| %>
|
30
|
+
init(<%= "Init_#{File.basename ext}" %>, "<%= ext %>");
|
31
|
+
<% end %>
|
32
|
+
}
|
Binary file
|
Binary file
|
Binary file
|