├── .github
├── actions
│ └── publish-doc
│ │ └── action.yml
├── dependabot.yml
└── workflows
│ ├── build-docs.yml
│ ├── build-gems.yml
│ ├── ci.yml
│ ├── memcheck.yml
│ └── release.yml
├── .gitignore
├── .rspec
├── .rubocop.yml
├── .standard.yml
├── .yardopts
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Cargo.lock
├── Cargo.toml
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── NIGHTLY_VERSION
├── README.md
├── Rakefile
├── bench
├── bench.rb
├── compile.rb
├── component_id.rb
├── func_call.rb
├── host_call.rb
└── instantiate.rb
├── bin
├── console
└── setup
├── examples
├── epoch.rb
├── externref.rb
├── externref.wat
├── fuel.rb
├── fuel.wat
├── gcd.rb
├── gcd.wat
├── hello.rb
├── hello.wat
├── linking.rb
├── linking1.wat
├── linking2.wat
├── memory.rb
├── memory.wat
├── multi.rb
├── multi.wat
├── rust-crate
│ ├── .gitignore
│ ├── Cargo.toml
│ └── src
│ │ └── lib.rs
└── wasi.rb
├── ext
├── Cargo.toml
├── build.rs
├── extconf.rb
└── src
│ ├── helpers
│ ├── macros.rs
│ ├── mod.rs
│ ├── nogvl.rs
│ ├── output_limited_buffer.rs
│ ├── static_id.rs
│ ├── symbol_enum.rs
│ └── tmplock.rs
│ ├── lib.rs
│ └── ruby_api
│ ├── caller.rs
│ ├── component.rs
│ ├── component
│ ├── convert.rs
│ ├── func.rs
│ ├── instance.rs
│ └── linker.rs
│ ├── config.rs
│ ├── convert.rs
│ ├── engine.rs
│ ├── errors.rs
│ ├── externals.rs
│ ├── func.rs
│ ├── global.rs
│ ├── instance.rs
│ ├── linker.rs
│ ├── memory.rs
│ ├── memory
│ └── unsafe_slice.rs
│ ├── mod.rs
│ ├── module.rs
│ ├── params.rs
│ ├── pooling_allocation_config.rs
│ ├── store.rs
│ ├── table.rs
│ ├── trap.rs
│ ├── wasi_ctx.rs
│ └── wasi_ctx_builder.rs
├── lib
├── wasmtime.rb
└── wasmtime
│ ├── component.rb
│ ├── error.rb
│ └── version.rb
├── rakelib
├── bench.rake
├── compile.rake
├── doc.rake
├── env.rake
├── examples.rake
├── helpers.rake
├── mem.rake
├── pkg.rake
└── spec.rake
├── spec
├── convert_spec.rb
├── fixtures
│ ├── .gitignore
│ ├── component-types
│ │ ├── .cargo
│ │ │ └── config.toml
│ │ ├── .gitignore
│ │ ├── .vscode
│ │ │ └── settings.json
│ │ ├── .zed
│ │ │ └── settings.json
│ │ ├── Cargo.toml
│ │ ├── README.md
│ │ ├── src
│ │ │ └── lib.rs
│ │ └── wit
│ │ │ └── world.wit
│ ├── component_adder.wat
│ ├── component_trap.wat
│ ├── component_types.wasm
│ ├── empty.wat
│ ├── empty_component.wat
│ ├── wasi-debug.wasm
│ ├── wasi-debug
│ │ ├── .cargo
│ │ │ └── config.toml
│ │ ├── Cargo.toml
│ │ ├── README.md
│ │ └── src
│ │ │ └── main.rs
│ ├── wasi-deterministic.wasm
│ └── wasi-deterministic
│ │ ├── .cargo
│ │ └── config.toml
│ │ ├── Cargo.toml
│ │ ├── README.md
│ │ └── src
│ │ └── main.rs
├── integration
│ ├── epoch_interruption_spec.rb
│ ├── hello_world_spec.rb
│ ├── insanity_spec.rb
│ └── ractor_spec.rb
├── spec_helper.rb
├── unit
│ ├── component
│ │ ├── component_spec.rb
│ │ ├── convert_spec.rb
│ │ ├── func_spec.rb
│ │ ├── instance_spec.rb
│ │ ├── linker_spec.rb
│ │ ├── result_spec.rb
│ │ └── variant_spec.rb
│ ├── engine_spec.rb
│ ├── error_spec.rb
│ ├── extern_spec.rb
│ ├── fuel_spec.rb
│ ├── func_spec.rb
│ ├── global_spec.rb
│ ├── instance_spec.rb
│ ├── linker_spec.rb
│ ├── memory_spec.rb
│ ├── module_spec.rb
│ ├── pooling_allocation_config_spec.rb
│ ├── store_spec.rb
│ ├── table_spec.rb
│ ├── trap_spec.rb
│ ├── wasi_spec.rb
│ └── wasmtime_spec.rb
└── wasmtime_spec.rb
├── suppressions
├── readme.md
└── ruby-3.1.supp
└── wasmtime.gemspec
/.github/actions/publish-doc/action.yml:
--------------------------------------------------------------------------------
1 | name: Publish documentation to GiHub Pages
2 | runs:
3 | using: composite
4 | steps:
5 | - uses: actions/checkout@v3
6 | with:
7 | ref: gh-pages
8 |
9 | - name: Download docs
10 | uses: actions/download-artifact@v4
11 | with:
12 | name: doc
13 | path: doc
14 |
15 | - name: Current doc dir
16 | id: doc-dir
17 | uses: k1LoW/github-script-ruby@v2
18 | with:
19 | result-encoding: string
20 | script: |
21 | context.ref
22 | .gsub(%r{\Arefs/heads/}, "")
23 | .gsub(%r{\Arefs/tags/}, "")
24 |
25 | - name: Move docs to dest folder
26 | shell: bash
27 | run: |
28 | ls -lah
29 | rm -rf ${{steps.doc-dir.outputs.result}}
30 | mv doc ${{steps.doc-dir.outputs.result}}
31 |
32 | - name: Find the latest doc
33 | uses: k1LoW/github-script-ruby@v2
34 | id: latest-dir
35 | with:
36 | result-encoding: string
37 | script: |
38 | build_version = -> (str) do
39 | Gem::Version.new(str.gsub(/\Av/, ""))
40 | rescue nil
41 | end
42 |
43 | Dir
44 | .glob('v*')
45 | .select { File.directory?(_1) && build_version[_1] }
46 | .max_by(&build_version)
47 | &.to_s || "main"
48 |
49 | - name: Commit the changes
50 | shell: bash
51 | run: |
52 | rm -f latest
53 | ln -s ${{steps.latest-dir.outputs.result}} latest
54 | git add ${{steps.doc-dir.outputs.result}} latest
55 |
56 | # Exit if there's no changes
57 | if [[ ! $(git diff --name-only --cached) ]]; then
58 | exit 0
59 | fi
60 |
61 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
62 | git config user.name "github-actions[bot]"
63 | git commit -m "Bump doc"
64 |
65 | - name: Push changes
66 | uses: ad-m/github-push-action@master
67 | with:
68 | github_token: ${{ github.token }}
69 | branch: gh-pages
70 |
71 | # Return to the original REF so that post-action
72 | # can still run with the action available.
73 | - uses: actions/checkout@v3
74 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | ---
2 | version: 2
3 | updates:
4 | - package-ecosystem: "github-actions"
5 | directory: "/"
6 | schedule:
7 | interval: "monthly"
8 |
9 | - package-ecosystem: "bundler"
10 | directory: "/"
11 | schedule:
12 | interval: "monthly"
13 |
14 | - package-ecosystem: "cargo"
15 | directory: "/"
16 | schedule:
17 | interval: "monthly"
18 |
--------------------------------------------------------------------------------
/.github/workflows/build-docs.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Build documentation
3 |
4 | on:
5 | workflow_dispatch:
6 | push:
7 | pull_request:
8 |
9 | concurrency:
10 | group: ${{ github.workflow }}-${{ github.ref }}
11 | cancel-in-progress: true
12 |
13 | jobs:
14 | build_doc:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v4
18 |
19 | - name: Derive nightly version
20 | run: |
21 | version="$(cat ./NIGHTLY_VERSION)"
22 | echo "NIGHTLY_VERSION=$version" >> $GITHUB_ENV
23 |
24 | - name: Remove Gemfile.lock
25 | run: rm Gemfile.lock
26 |
27 | - uses: oxidize-rb/actions/setup-ruby-and-rust@v1
28 | with:
29 | ruby-version: "3.4"
30 | rustup-toolchain: "${{ env.NIGHTLY_VERSION }}"
31 | bundler-cache: true
32 | cargo-cache: true
33 | cache-version: docs-v1
34 |
35 | - name: Generate doc
36 | run: bundle exec rake doc
37 |
38 | - name: Upload generated doc
39 | uses: actions/upload-artifact@v4
40 | with:
41 | name: doc
42 | path: doc
43 | retention-days: 1
44 |
45 | - name: Publish doc
46 | if: contains(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main'
47 | uses: ./.github/actions/publish-doc
48 |
49 |
--------------------------------------------------------------------------------
/.github/workflows/build-gems.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Build gems
3 |
4 | on:
5 | workflow_dispatch:
6 | push:
7 | branches: ["main", "cross-gem/*", "pkg/*"]
8 |
9 | concurrency:
10 | group: ${{ github.workflow }}-${{ github.ref }}
11 | cancel-in-progress: true
12 |
13 | jobs:
14 | ci-data:
15 | runs-on: ubuntu-latest
16 | outputs:
17 | result: ${{ steps.fetch.outputs.result }}
18 | steps:
19 | - id: fetch
20 | uses: oxidize-rb/actions/fetch-ci-data@v1
21 | with:
22 | supported-ruby-platforms: |
23 | # Excluding:
24 | # `arm-linux`: Cranelift doesn't support 32-bit architectures
25 | # `x64-mingw32`: `x64-mingw-ucrt` should be used for Ruby 3.1+ (https://github.com/rake-compiler/rake-compiler-dock?tab=readme-ov-file#windows)
26 | # 3.0 is deprecated as stable ruby version according to:
27 | # https://github.com/oxidize-rb/actions/blob/main/fetch-ci-data/evaluate.rb#L54
28 | exclude: [arm-linux, x64-mingw32]
29 | stable-ruby-versions: |
30 | exclude: [head]
31 |
32 | native:
33 | name: Build native gems
34 | needs: ci-data
35 | runs-on: ubuntu-latest
36 | strategy:
37 | fail-fast: false
38 | matrix:
39 | ruby-platform: ${{ fromJSON(needs.ci-data.outputs.result).supported-ruby-platforms }}
40 | steps:
41 | - uses: actions/checkout@v4
42 |
43 | - uses: ruby/setup-ruby@v1
44 | with:
45 | ruby-version: "3.4"
46 |
47 | - uses: oxidize-rb/actions/cross-gem@v1
48 | id: cross-gem
49 | with:
50 | platform: ${{ matrix.ruby-platform }}
51 | ruby-versions: ${{ join(fromJSON(needs.ci-data.outputs.result).stable-ruby-versions, ',') }}
52 |
53 | - uses: actions/upload-artifact@v4
54 | with:
55 | name: cross-gem-${{ matrix.ruby-platform }}
56 | path: ${{ steps.cross-gem.outputs.gem-path }}
57 | if-no-files-found: error
58 |
59 | - name: Smoke gem install
60 | if: matrix.ruby-platform == 'x86_64-linux' # GitHub actions architecture
61 | run: bundle install && bundle exec rake pkg:${{ matrix.ruby-platform }}:test
62 |
63 | source:
64 | name: Build source gem
65 | runs-on: ${{ matrix.os }}
66 | strategy:
67 | matrix:
68 | os: ["ubuntu-latest"]
69 | ruby: ["3.4"]
70 | steps:
71 | - uses: actions/checkout@v4
72 |
73 | - uses: oxidize-rb/actions/setup-ruby-and-rust@v1
74 | with:
75 | ruby-version: ${{ matrix.ruby }}
76 | bundler-cache: true
77 | cargo-cache: false
78 | cache-version: v2
79 |
80 | - name: Smoke test gem install
81 | shell: bash
82 | run: bundle exec rake pkg:ruby:test
83 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: CI
3 |
4 | on:
5 | workflow_dispatch:
6 | pull_request:
7 |
8 | concurrency:
9 | group: ${{ github.workflow }}-${{ github.ref }}
10 | cancel-in-progress: true
11 |
12 | env:
13 | RSPEC_FORMATTER: doc
14 |
15 | jobs:
16 | ci-data:
17 | runs-on: ubuntu-latest
18 | outputs:
19 | result: ${{ steps.fetch.outputs.result }}
20 | steps:
21 | - id: fetch
22 | uses: oxidize-rb/actions/fetch-ci-data@v1
23 | with:
24 | stable-ruby-versions: |
25 | # See https://github.com/bytecodealliance/wasmtime-rb/issues/286
26 | # for details.
27 | exclude: [head]
28 |
29 | ci:
30 | runs-on: ${{ matrix.os }}
31 | needs: ci-data
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | os: ["ubuntu-latest", "macos-latest", "windows-latest"]
36 | ruby: ${{ fromJSON(needs.ci-data.outputs.result).stable-ruby-versions }}
37 | # include:
38 | # mswin relies on head and we're not creating releases for it
39 | # so disabling it as well.
40 | # - os: windows-latest
41 | # ruby: mswin
42 | steps:
43 | - uses: actions/checkout@v4
44 |
45 | - name: Remove Gemfile.lock
46 | run: rm Gemfile.lock
47 |
48 | - uses: oxidize-rb/actions/setup-ruby-and-rust@v1
49 | with:
50 | ruby-version: ${{ matrix.ruby }}
51 | bundler-cache: true
52 | cargo-cache: true
53 | cache-version: v5
54 |
55 | - name: Compile rust ext
56 | run: bundle exec rake compile:release
57 |
58 | - name: Run ruby tests
59 | run: bundle exec rake spec
60 |
61 | - name: Run ruby tests (hard-mode with GC.stress)
62 | run: bundle exec rake spec
63 | env:
64 | GC_STRESS: "true"
65 |
66 | - name: Run examples
67 | run: bundle exec rake examples
68 |
69 | - name: Run benchmarks
70 | run: bundle exec rake bench:all
71 |
72 | - name: Lint ruby
73 | run: bundle exec rake standard
74 |
75 | - name: Lint rust
76 | run: cargo clippy -- -D warnings && cargo fmt --check
77 |
--------------------------------------------------------------------------------
/.github/workflows/memcheck.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Memcheck
3 |
4 | on:
5 | workflow_dispatch:
6 | inputs:
7 | ruby-version:
8 | description: "Ruby version to memcheck"
9 | required: true
10 | default: "3.4"
11 | type: choice
12 | options:
13 | - "head"
14 | - "3.4"
15 | - "3.3"
16 | - "3.2"
17 | - "3.1"
18 | - "3.0"
19 | debug:
20 | description: "Enable debug mode"
21 | required: false
22 | default: "false"
23 | type: boolean
24 | push:
25 | branches: ["*"]
26 | tags-ignore: ["v*"] # Skip Memcheck for releases
27 |
28 | concurrency:
29 | group: ${{ github.workflow }}-${{ github.ref }}
30 | cancel-in-progress: true
31 |
32 | jobs:
33 | memcheck:
34 | name: Memcheck
35 | runs-on: ubuntu-latest
36 | steps:
37 | - uses: actions/checkout@v4
38 |
39 | - uses: oxidize-rb/actions/setup-ruby-and-rust@v1
40 | with:
41 | ruby-version: ${{ inputs.ruby-version || '3.4' }}
42 | bundler-cache: true
43 | cargo-cache: true
44 | cache-version: v2
45 |
46 | - name: Install deps
47 | run: |
48 | bundle config unset deployment
49 | bundle add ruby_memcheck --version '~> 1.3.1' # avoid usage in Gemfile bc it pulls in nokogiri
50 | sudo apt-get update
51 | sudo apt-get install -y valgrind
52 | bundle config set deployment true
53 |
54 | - name: Run "mem:check" task
55 | env:
56 | RSPEC_FORMATTER: "progress"
57 | RSPEC_FAILURE_EXIT_CODE: "0"
58 | GC_AT_EXIT: "1"
59 | DEBUG: ${{ inputs.debug || 'false' }}
60 | RB_SYS_CARGO_PROFILE: ${{ inputs.debug == 'true' && 'dev' || 'release' }}
61 | WASMTIME_TARGET: "x86_64-unknown-linux-gnu" # use generic target for memcheck
62 | run: |
63 | if ! bundle exec rake mem:check; then
64 | echo "::error::Valgrind memory check failed, for more info please see ./suppressions/readme.md"
65 | exit 1
66 | fi
67 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Release
3 |
4 | on:
5 | workflow_dispatch:
6 | push:
7 | tags: ["v*"]
8 |
9 | concurrency:
10 | group: ${{ github.workflow }}-${{ github.ref }}
11 | cancel-in-progress: true
12 |
13 | jobs:
14 | ci-data:
15 | runs-on: ubuntu-latest
16 | outputs:
17 | result: ${{ steps.fetch.outputs.result }}
18 | steps:
19 | - id: fetch
20 | uses: oxidize-rb/actions/fetch-ci-data@v1
21 | with:
22 | supported-ruby-platforms: |
23 | # Excluding:
24 | # `arm-linux`: Cranelift doesn't support 32-bit architectures
25 | # `x64-mingw32`: `x64-mingw-ucrt` should be used for Ruby 3.1+ (https://github.com/rake-compiler/rake-compiler-dock?tab=readme-ov-file#windows)
26 | # 3.0 is deprecated as stable ruby version according to:
27 | # https://github.com/oxidize-rb/actions/blob/main/fetch-ci-data/evaluate.rb#L54
28 | exclude: [arm-linux, x64-mingw32]
29 | stable-ruby-versions: |
30 | exclude: [head]
31 |
32 | build:
33 | name: Build native gems
34 | needs: ci-data
35 | runs-on: ubuntu-latest
36 | strategy:
37 | fail-fast: false
38 | matrix:
39 | ruby-platform: ${{ fromJSON(needs.ci-data.outputs.result).supported-ruby-platforms }}
40 | steps:
41 | - uses: actions/checkout@v4
42 |
43 | - uses: ruby/setup-ruby@v1
44 | with:
45 | ruby-version: "3.4"
46 |
47 | - uses: oxidize-rb/actions/cross-gem@v1
48 | id: cross-gem
49 | with:
50 | platform: ${{ matrix.ruby-platform }}
51 | ruby-versions: ${{ join(fromJSON(needs.ci-data.outputs.result).stable-ruby-versions, ',') }}
52 |
53 | - uses: actions/upload-artifact@v4
54 | with:
55 | name: cross-gem-${{ matrix.ruby-platform }}
56 | path: pkg/*-${{ matrix.ruby-platform }}.gem
57 | if-no-files-found: error
58 |
59 | - name: Smoke gem install
60 | if: matrix.ruby-platform == 'x86_64-linux' # GitHub actions architecture
61 | run: |
62 | gem install pkg/wasmtime-*.gem --verbose
63 | script="puts Wasmtime::Engine.new.precompile_module('(module)')"
64 | ruby -rwasmtime -e "$script" | grep wasmtime.info
65 | echo "✅ Successfully gem installed"
66 |
67 | release:
68 | name: Release
69 | needs: build
70 | runs-on: ubuntu-latest
71 | steps:
72 | - uses: actions/checkout@v4
73 |
74 | - uses: oxidize-rb/actions/setup-ruby-and-rust@v1
75 | with:
76 | ruby-version: "3.4"
77 | bundler-cache: true
78 | cargo-cache: true
79 | cache-version: v1
80 |
81 | - uses: actions/download-artifact@v4
82 | with:
83 | pattern: cross-gem-*
84 | merge-multiple: true
85 | path: pkg/
86 |
87 | - name: Package source gem
88 | run: bundle exec rake pkg:ruby
89 |
90 | - name: Ensure version matches the tag
91 | run: |
92 | GEM_VERSION=$(grep VERSION lib/wasmtime/version.rb | head -n 1 | cut -d'"' -f2)
93 | if [ "v$GEM_VERSION" != "${{ github.ref_name }}" ]; then
94 | echo "Gem version does not match tag"
95 | echo " v$GEM_VERSION != ${{ github.ref_name }}"
96 | exit 1
97 | fi
98 |
99 | - name: Push Gem
100 | working-directory: pkg/
101 | env:
102 | GEM_HOST_API_KEY: ${{ secrets.RUBYGEMS_KEY }}
103 | run: |
104 | mkdir -p $HOME/.gem
105 | touch $HOME/.gem/credentials
106 | chmod 0600 $HOME/.gem/credentials
107 | printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
108 | ls -l
109 | for i in *.gem; do
110 | if [ -f "$i" ]; then
111 | if ! gem push "$i" >push.out; then
112 | gemerr=$?
113 | sed 's/^/::error:: /' push.out
114 | if ! grep -q "Repushing of gem" push.out; then
115 | exit $gemerr
116 | fi
117 | fi
118 | fi
119 | done
120 |
121 | - name: Create GitHub release
122 | uses: ncipollo/release-action@v1
123 | with:
124 | allowUpdates: true
125 | generateReleaseNotes: true
126 | draft: true
127 | omitBodyDuringUpdate: true
128 | omitNameDuringUpdate: true
129 | omitPrereleaseDuringUpdate: true
130 | skipIfReleaseExists: true
131 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /_yardoc/
4 | /coverage/
5 | /doc/
6 | /pkg/
7 | /spec/reports/
8 | /tmp/
9 | *.bundle
10 | *.so
11 | *.o
12 | *.a
13 | mkmf.log
14 | target/
15 |
16 | # rspec failure tracking
17 | .rspec_status
18 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --require spec_helper
3 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | require: standard
2 |
3 | inherit_gem:
4 | standard: config/base.yml
5 |
6 | AllCops:
7 | Exclude:
8 | - 'vendor/**/*'
9 | - 'pkg/**/*'
10 | - 'tmp/**/*'
11 |
--------------------------------------------------------------------------------
/.standard.yml:
--------------------------------------------------------------------------------
1 | # For available configuration options, see:
2 | # https://github.com/testdouble/standard
3 | ruby_version: 3.0
4 | ignore:
5 | - 'vendor/**/*'
6 | - 'pkg/**/*'
7 | - 'tmp/**/*'
8 |
--------------------------------------------------------------------------------
/.yardopts:
--------------------------------------------------------------------------------
1 | --plugin rustdoc
2 | lib
3 | tmp/doc/wasmtime_rb.json
4 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | `wasmtime-rb` is a [Bytecode Alliance] project. It follows the Bytecode
4 | Alliance's [Code of Conduct] and [Organizational Code of Conduct].
5 |
6 | ## Getting started
7 |
8 | Install dependencies:
9 |
10 | ```
11 | bundle install
12 | ```
13 |
14 | Compile the gem, run the tests & Ruby linter:
15 |
16 | ```
17 | bundle exec rake
18 | ```
19 |
20 | ## Releasing
21 |
22 | 1. Bump the `VERSION` in `lib/wasmtime/version.rb`
23 | 1. Run `bundle install` to bump the version in `Gemfile.lock`
24 | 1. Update the changelog (requires the `github_changelog_generator` gem and being authenticated with `gh`)
25 |
26 | ```
27 | github_changelog_generator \
28 | -u bytecodealliance \
29 | -p wasmtime-rb \
30 | -t $(gh auth token) \
31 | --future-release v$(grep VERSION lib/wasmtime/version.rb | head -n 1 | cut -d'"' -f2)
32 | ```
33 | 1. Commit your changes to the `main` branch and push them. Ensure you are not doing this on a fork of the repository.
34 | 1. Create a new tag for that release, prefixed with `v` (`git tag v1.0.0`):
35 |
36 | ```
37 | git tag v$(grep VERSION lib/wasmtime/version.rb | head -n 1 | cut -d'"' -f2)
38 | git push --tags
39 | ```
40 | 1. The release workflow will run and push a new version to RubyGems and create
41 | a new draft release on GitHub. Edit the release notes if needed, then
42 | mark the release as published when the release workflow succeeds.
43 |
44 |
45 | [Bytecode Alliance]: https://bytecodealliance.org/
46 | [Code of Conduct]: https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
47 | [Organizational Code of Conduct]: https://github.com/bytecodealliance/wasmtime/blob/main/ORG_CODE_OF_CONDUCT.md
48 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | resolver = "2"
3 | members = ["ext"]
4 | exclude = [
5 | "examples/rust-crate",
6 | "spec/fixtures/component-types",
7 | "spec/fixtures/wasi-debug",
8 | "spec/fixtures/wasi-deterministic",
9 | ]
10 |
11 | [profile.release]
12 | codegen-units = 1 # more llvm optimizations
13 | debug = 2 # make perfomance engineers happy
14 | lto = "thin" # cross-crate inlining
15 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source "https://rubygems.org"
4 |
5 | # Specify your gem's dependencies in wasmtime.gemspec
6 | gemspec
7 |
8 | group :development do
9 | gem "rake", "~> 13.2"
10 | gem "rake-compiler"
11 | gem "standard", "~> 1.50"
12 | gem "get_process_mem"
13 | gem "yard", require: false
14 | gem "yard-rustdoc", "~> 0.4.0", require: false
15 | gem "benchmark-ips", require: false
16 | gem "fiddle"
17 | end
18 |
19 | group :test do
20 | gem "rspec", "~> 3.13"
21 | end
22 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | wasmtime (33.0.0)
5 | rb_sys (~> 0.9.108)
6 |
7 | GEM
8 | remote: https://rubygems.org/
9 | specs:
10 | ast (2.4.3)
11 | benchmark-ips (2.14.0)
12 | bigdecimal (3.1.9)
13 | diff-lcs (1.6.2)
14 | ffi (1.17.1)
15 | ffi (1.17.1-aarch64-linux-gnu)
16 | ffi (1.17.1-aarch64-linux-musl)
17 | ffi (1.17.1-arm-linux-gnu)
18 | ffi (1.17.1-arm-linux-musl)
19 | ffi (1.17.1-arm64-darwin)
20 | ffi (1.17.1-x86-linux-gnu)
21 | ffi (1.17.1-x86-linux-musl)
22 | ffi (1.17.1-x86_64-darwin)
23 | ffi (1.17.1-x86_64-linux-gnu)
24 | ffi (1.17.1-x86_64-linux-musl)
25 | fiddle (1.1.8)
26 | get_process_mem (1.0.0)
27 | bigdecimal (>= 2.0)
28 | ffi (~> 1.0)
29 | json (2.12.2)
30 | language_server-protocol (3.17.0.5)
31 | lint_roller (1.1.0)
32 | parallel (1.27.0)
33 | parser (3.3.8.0)
34 | ast (~> 2.4.1)
35 | racc
36 | prettier_print (1.2.1)
37 | prism (1.4.0)
38 | racc (1.8.1)
39 | rainbow (3.1.1)
40 | rake (13.2.1)
41 | rake-compiler (1.3.0)
42 | rake
43 | rake-compiler-dock (1.9.1)
44 | rb_sys (0.9.111)
45 | rake-compiler-dock (= 1.9.1)
46 | regexp_parser (2.10.0)
47 | rspec (3.13.1)
48 | rspec-core (~> 3.13.0)
49 | rspec-expectations (~> 3.13.0)
50 | rspec-mocks (~> 3.13.0)
51 | rspec-core (3.13.4)
52 | rspec-support (~> 3.13.0)
53 | rspec-expectations (3.13.5)
54 | diff-lcs (>= 1.2.0, < 2.0)
55 | rspec-support (~> 3.13.0)
56 | rspec-mocks (3.13.5)
57 | diff-lcs (>= 1.2.0, < 2.0)
58 | rspec-support (~> 3.13.0)
59 | rspec-support (3.13.4)
60 | rubocop (1.75.8)
61 | json (~> 2.3)
62 | language_server-protocol (~> 3.17.0.2)
63 | lint_roller (~> 1.1.0)
64 | parallel (~> 1.10)
65 | parser (>= 3.3.0.2)
66 | rainbow (>= 2.2.2, < 4.0)
67 | regexp_parser (>= 2.9.3, < 3.0)
68 | rubocop-ast (>= 1.44.0, < 2.0)
69 | ruby-progressbar (~> 1.7)
70 | unicode-display_width (>= 2.4.0, < 4.0)
71 | rubocop-ast (1.45.0)
72 | parser (>= 3.3.7.2)
73 | prism (~> 1.4)
74 | rubocop-performance (1.25.0)
75 | lint_roller (~> 1.1)
76 | rubocop (>= 1.75.0, < 2.0)
77 | rubocop-ast (>= 1.38.0, < 2.0)
78 | ruby-progressbar (1.13.0)
79 | standard (1.50.0)
80 | language_server-protocol (~> 3.17.0.2)
81 | lint_roller (~> 1.0)
82 | rubocop (~> 1.75.5)
83 | standard-custom (~> 1.0.0)
84 | standard-performance (~> 1.8)
85 | standard-custom (1.0.2)
86 | lint_roller (~> 1.0)
87 | rubocop (~> 1.50)
88 | standard-performance (1.8.0)
89 | lint_roller (~> 1.1)
90 | rubocop-performance (~> 1.25.0)
91 | syntax_tree (6.2.0)
92 | prettier_print (>= 1.2.0)
93 | unicode-display_width (3.1.4)
94 | unicode-emoji (~> 4.0, >= 4.0.4)
95 | unicode-emoji (4.0.4)
96 | yard (0.9.37)
97 | yard-rustdoc (0.4.1)
98 | syntax_tree (~> 6.0)
99 | yard (~> 0.9)
100 |
101 | PLATFORMS
102 | aarch64-linux-gnu
103 | aarch64-linux-musl
104 | arm-linux-gnu
105 | arm-linux-musl
106 | arm64-darwin
107 | ruby
108 | x86-linux-gnu
109 | x86-linux-musl
110 | x86_64-darwin
111 | x86_64-linux-gnu
112 | x86_64-linux-musl
113 |
114 | DEPENDENCIES
115 | benchmark-ips
116 | fiddle
117 | get_process_mem
118 | rake (~> 13.2)
119 | rake-compiler
120 | rspec (~> 3.13)
121 | standard (~> 1.50)
122 | wasmtime!
123 | yard
124 | yard-rustdoc (~> 0.4.0)
125 |
126 | BUNDLED WITH
127 | 2.5.7
128 |
--------------------------------------------------------------------------------
/NIGHTLY_VERSION:
--------------------------------------------------------------------------------
1 | nightly-2025-03-04
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
17 |
18 | ## Goal
19 |
20 | `wasmtime-rb`'s goal is to expose the full power of Wasmtime in Ruby with
21 | minimal overhead, serving as a foundation layer for other projects or gems.
22 |
23 | ## Installation
24 |
25 | Add the `wasmtime` gem to your Gemfile and run `bundle install`:
26 |
27 | ```ruby
28 | gem "wasmtime"
29 | ```
30 |
31 | Alternatively, you can install the gem manually:
32 |
33 | ```sh
34 | gem install wasmtime
35 | ```
36 |
37 | ### Precompiled gems
38 |
39 | We recommend installing the `wasmtime` precompiled gems available for Linux, macOS, and Windows. Installing a precompiled gem avoids the need to compile from source code, which is generally slower and less reliable.
40 |
41 | When installing the `wasmtime` gem for the first time using `bundle install`, Bundler will automatically download the precompiled gem for your current platform. However, you will need to inform Bundler of any additional platforms you plan to use.
42 |
43 | To do this, lock your Bundle to the required platforms you will need from the list of supported platforms below:
44 |
45 | ```sh
46 | bundle lock --add-platform x86_64-linux # Standard Linux (e.g. Heroku, GitHub Actions, etc.)
47 | bundle lock --add-platform x86_64-linux-musl # MUSL Linux deployments (i.e. Alpine Linux)
48 | bundle lock --add-platform aarch64-linux # ARM64 Linux deployments (i.e. AWS Graviton2)
49 | bundle lock --add-platform x86_64-darwin # Intel MacOS (i.e. pre-M1)
50 | bundle lock --add-platform arm64-darwin # Apple Silicon MacOS (i.e. M1)
51 | ```
52 |
53 | ## Usage
54 |
55 | Example usage:
56 |
57 | ```ruby
58 | require "wasmtime"
59 |
60 | # Create an engine. Generally, you only need a single engine and can
61 | # re-use it throughout your program.
62 | engine = Wasmtime::Engine.new
63 |
64 | # Compile a Wasm module from either Wasm or WAT. The compiled module is
65 | # specific to the Engine's configuration.
66 | mod = Wasmtime::Module.new(engine, <<~WAT)
67 | (module
68 | (func $hello (import "" "hello"))
69 | (func (export "run") (call $hello))
70 | )
71 | WAT
72 |
73 | # Create a store. Store can keep state to be re-used in Funcs.
74 | store = Wasmtime::Store.new(engine, {count: 0})
75 |
76 | # Define a Wasm function from Ruby code.
77 | func = Wasmtime::Func.new(store, [], []) do |caller|
78 | puts "Hello from Func!"
79 | caller.store_data[:count] += 1
80 | puts "Ran #{caller.store_data[:count]} time(s)"
81 | end
82 |
83 | # Build the Wasm instance by providing its imports.
84 | instance = Wasmtime::Instance.new(store, mod, [func])
85 |
86 | # Run the `run` export.
87 | instance.invoke("run")
88 |
89 | # Or: get the `run` export and call it.
90 | instance.export("run").to_func.call
91 | ```
92 |
93 | For more, see [examples](https://github.com/bytecodealliance/wasmtime-rb/tree/main/examples)
94 | or the [API documentation](https://bytecodealliance.github.io/wasmtime-rb/latest/).
95 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "standard/rake"
4 |
5 | GEMSPEC = Gem::Specification.load("wasmtime.gemspec")
6 |
7 | task build: "pkg:ruby"
8 |
9 | task default: %w[env:dev compile spec standard]
10 |
--------------------------------------------------------------------------------
/bench/bench.rb:
--------------------------------------------------------------------------------
1 | require "benchmark/ips"
2 | require "wasmtime"
3 |
4 | module Bench
5 | extend(self)
6 |
7 | def ips
8 | Benchmark.ips do |x|
9 | yield(x)
10 |
11 | x.config(time: 0, warmup: 0) if ENV["CI"]
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/bench/compile.rb:
--------------------------------------------------------------------------------
1 | require_relative "bench"
2 |
3 | Bench.ips do |x|
4 | engine = Wasmtime::Engine.new
5 |
6 | x.report("empty module") do
7 | Wasmtime::Module.new(engine, "(module)")
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/bench/component_id.rb:
--------------------------------------------------------------------------------
1 | require_relative "bench"
2 |
3 | Bench.ips do |x|
4 | engine = Wasmtime::Engine.new
5 | linker = Wasmtime::Component::Linker.new(engine)
6 | component = Wasmtime::Component::Component.from_file(engine, "spec/fixtures/component_types.wasm")
7 | store = Wasmtime::Store.new(engine)
8 | instance = linker.instantiate(store, component)
9 | id_record = instance.get_func("id-record")
10 | id_u32 = instance.get_func("id-u32")
11 |
12 | point_record = {"x" => 1, "y" => 2}
13 |
14 | x.report("identity point record") do
15 | id_record.call(point_record)
16 | end
17 |
18 | x.report("identity u32") do
19 | id_u32.call(10)
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/bench/func_call.rb:
--------------------------------------------------------------------------------
1 | require_relative "bench"
2 |
3 | Bench.ips do |x|
4 | engine = Wasmtime::Engine.new
5 | linker = Wasmtime::Linker.new(engine)
6 | mod = Wasmtime::Module.from_file(engine, "examples/gcd.wat")
7 | store = Wasmtime::Store.new(engine)
8 | instance = linker.instantiate(store, mod)
9 | func = instance.export("gcd").to_func
10 |
11 | x.report("Instance#invoke") do
12 | instance.invoke("gcd", 5, 1)
13 | end
14 |
15 | x.report("Func#call") do
16 | func.call(5, 1)
17 | end
18 |
19 | x.compare!
20 | end
21 |
--------------------------------------------------------------------------------
/bench/host_call.rb:
--------------------------------------------------------------------------------
1 | require_relative "bench"
2 |
3 | Bench.ips do |x|
4 | engine = Wasmtime::Engine.new
5 | [4, 16, 64, 128, 256].each do |n|
6 | result_type_wat = Array.new(n) { |_| :i32 }.join(" ")
7 | mod = Wasmtime::Module.new(engine, <<~WAT)
8 | (module
9 | (import "host" "succ" (func (param i32) (result #{result_type_wat})))
10 | (export "run" (func 0)))
11 | WAT
12 | linker = Wasmtime::Linker.new(engine)
13 | results = Array.new(n) { |_| :i32 }
14 | result_array = Array.new(n) { |i| i }
15 | linker.func_new("host", "succ", [:i32], results) do |_caller, arg1|
16 | result_array
17 | end
18 |
19 | x.report("Call host func (#{n} args)") do
20 | store = Wasmtime::Store.new(engine)
21 | linker.instantiate(store, mod).invoke("run", 101)
22 | end
23 | end
24 |
25 | x.compare!
26 | end
27 |
--------------------------------------------------------------------------------
/bench/instantiate.rb:
--------------------------------------------------------------------------------
1 | require_relative "bench"
2 |
3 | Bench.ips do |x|
4 | engine = Wasmtime::Engine.new
5 | linker = Wasmtime::Linker.new(engine)
6 | mod = Wasmtime::Module.new(engine, "(module)")
7 |
8 | x.report("Linker#instantiate") do
9 | store = Wasmtime::Store.new(engine)
10 | linker.instantiate(store, mod)
11 | end
12 |
13 | x.report("Instance#new") do
14 | store = Wasmtime::Store.new(engine)
15 | Wasmtime::Instance.new(store, mod)
16 | end
17 |
18 | x.compare!
19 | end
20 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require "bundler/setup"
5 | require "wasmtime"
6 |
7 | # You can add fixtures and/or initialization code here to make experimenting
8 | # with your gem easier. You can also use a different console, if you like.
9 |
10 | # (If you use this, don't forget to add pry to your Gemfile!)
11 | # require "pry"
12 | # Pry.start
13 |
14 | require "irb"
15 | IRB.start(__FILE__)
16 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 | set -vx
5 |
6 | bundle install
7 |
8 | # Do any other automated setup that you need to do here
9 |
--------------------------------------------------------------------------------
/examples/epoch.rb:
--------------------------------------------------------------------------------
1 | require "wasmtime"
2 |
3 | engine = Wasmtime::Engine.new(epoch_interruption: true)
4 | # Re-use fibonacci function from the Fuel example
5 | mod = Wasmtime::Module.from_file(engine, "examples/fuel.wat")
6 | store = Wasmtime::Store.new(engine)
7 | instance = Wasmtime::Instance.new(store, mod)
8 |
9 | # Store starts with an epoch deadline of 0, meaning Wasm execution
10 | # will be halted right away.
11 | puts "Running Wasm with default epoch deadline of 0..."
12 | begin
13 | instance.invoke("fibonacci", 5)
14 | raise "Unexpected: Wasm executed past deadline"
15 | rescue Wasmtime::Trap => trap
16 | puts " Wasm trap, code: #{trap.code.inspect}"
17 | puts
18 | end
19 |
20 | # Epoch deadline is manipulated with `Store#set_epoch_deadline`.
21 | store.set_epoch_deadline(1)
22 | puts "Running Wasm with default epoch deadline of 1..."
23 | puts " result: #{instance.invoke("fibonacci", 5)}"
24 | puts
25 |
26 | # The engine's epoch can be incremented manually with `Engine#increment_epoch`.
27 | engine.increment_epoch
28 | puts "Running Wasm after incrementing epoch past the store's deadline..."
29 | begin
30 | instance.invoke("fibonacci", 5)
31 | raise "Unexpected: Wasm executed past deadline"
32 | rescue Wasmtime::Trap => trap
33 | puts " Wasm trap, code: #{trap.code.inspect}"
34 | puts
35 | end
36 |
37 | # The engine provides a method to increment epoch based on a timer.
38 | # This is done with native thread because Wasm execution does not release
39 | # Ruby's Global VM lock.
40 | puts "Setting the store's deadline to be 2 ticks from current epoch..."
41 | store.set_epoch_deadline(2)
42 |
43 | puts "Incrementing epoch interval every 100ms..."
44 | engine.start_epoch_interval(100)
45 | start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
46 | puts "Computing fibonacci of 100..."
47 | begin
48 | instance.invoke("fibonacci", 100)
49 | raise "Unexpected: computed fibonacci of 100 in 200ms"
50 | rescue Wasmtime::Trap => _
51 | elapsed_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000
52 | # This should be around 200ms
53 | puts " Wasm trapped after #{elapsed_ms.round}ms"
54 | end
55 |
--------------------------------------------------------------------------------
/examples/externref.rb:
--------------------------------------------------------------------------------
1 | require "wasmtime"
2 |
3 | engine = Wasmtime::Engine.new
4 | mod = Wasmtime::Module.from_file(engine, "examples/externref.wat")
5 | store = Wasmtime::Store.new(engine)
6 | instance = Wasmtime::Instance.new(store, mod)
7 |
8 | # externrefs can be any Ruby object, here we're using a String.
9 | hello_world = +"Hello World"
10 |
11 | puts "Set and get from table..."
12 | table = instance.export("table").to_table
13 | table.set(3, hello_world)
14 | puts " get: #{table.get(3).inspect}" # => Hello World
15 | puts " same Ruby object: #{table.get(3).eql?(hello_world).inspect}" # => true
16 | puts
17 |
18 | puts "Set and get from global..."
19 | global = instance.export("global").to_global
20 | global.set(hello_world)
21 | puts " get: #{global.get.inspect}" # => "Hello World"
22 | puts
23 |
24 | puts "Return an externref from a function..."
25 | func = instance.export("func").to_func
26 | puts " return: #{func.call(hello_world).inspect}" # => "Hello World"
27 | puts
28 |
29 | puts "nil is a valid externref..."
30 | puts " nil roundtrip: #{func.call(nil).inspect}" # => nil
31 | puts
32 |
33 | puts "nil is also a 'null reference' externref..."
34 | puts " null externref: #{table.get(6).inspect}" # => nil
35 |
--------------------------------------------------------------------------------
/examples/externref.wat:
--------------------------------------------------------------------------------
1 | (module
2 | (table $table (export "table") 10 externref)
3 |
4 | (global $global (export "global") (mut externref) (ref.null extern))
5 |
6 | (func (export "func") (param externref) (result externref)
7 | local.get 0
8 | )
9 | )
10 |
--------------------------------------------------------------------------------
/examples/fuel.rb:
--------------------------------------------------------------------------------
1 | require "wasmtime"
2 |
3 | engine = Wasmtime::Engine.new(consume_fuel: true)
4 | mod = Wasmtime::Module.from_file(engine, "examples/fuel.wat")
5 |
6 | store = Wasmtime::Store.new(engine)
7 | store.set_fuel(10_000)
8 |
9 | instance = Wasmtime::Instance.new(store, mod)
10 |
11 | begin
12 | (1..).each do |i|
13 | initial_fuel = store.get_fuel
14 | result = instance.invoke("fibonacci", i)
15 | fuel_consumed = initial_fuel - store.get_fuel
16 | puts "fib(#{i}) = #{result} [consumed #{fuel_consumed} fuel]"
17 | end
18 | rescue Wasmtime::Trap => trap
19 | puts
20 | puts "Wasm trap, code: #{trap.code.inspect}"
21 | end
22 |
--------------------------------------------------------------------------------
/examples/fuel.wat:
--------------------------------------------------------------------------------
1 | (module
2 | (func $fibonacci (param $n i32) (result i32)
3 | (if
4 | (i32.lt_s (local.get $n) (i32.const 2))
5 | (then
6 | (return (local.get $n))
7 | )
8 | )
9 | (i32.add
10 | (call $fibonacci (i32.sub (local.get $n) (i32.const 1)))
11 | (call $fibonacci (i32.sub (local.get $n) (i32.const 2)))
12 | )
13 | )
14 | (export "fibonacci" (func $fibonacci))
15 | )
16 |
--------------------------------------------------------------------------------
/examples/gcd.rb:
--------------------------------------------------------------------------------
1 | require "wasmtime"
2 |
3 | engine = Wasmtime::Engine.new
4 | mod = Wasmtime::Module.from_file(engine, "examples/gcd.wat")
5 | store = Wasmtime::Store.new(engine)
6 | instance = Wasmtime::Instance.new(store, mod)
7 |
8 | puts "gcd(6, 27) = #{instance.invoke("gcd", 6, 27)}"
9 |
--------------------------------------------------------------------------------
/examples/gcd.wat:
--------------------------------------------------------------------------------
1 | (module
2 | (func $gcd (param i32 i32) (result i32)
3 | (local i32)
4 | block ;; label = @1
5 | block ;; label = @2
6 | local.get 0
7 | br_if 0 (;@2;)
8 | local.get 1
9 | local.set 2
10 | br 1 (;@1;)
11 | end
12 | loop ;; label = @2
13 | local.get 1
14 | local.get 0
15 | local.tee 2
16 | i32.rem_u
17 | local.set 0
18 | local.get 2
19 | local.set 1
20 | local.get 0
21 | br_if 0 (;@2;)
22 | end
23 | end
24 | local.get 2
25 | )
26 | (export "gcd" (func $gcd))
27 | )
28 |
--------------------------------------------------------------------------------
/examples/hello.rb:
--------------------------------------------------------------------------------
1 | require "wasmtime"
2 |
3 | class MyData
4 | attr_reader :count
5 |
6 | def initialize
7 | @count = 0
8 | end
9 |
10 | def increment!
11 | @count += 1
12 | end
13 | end
14 |
15 | data = MyData.new
16 | engine = Wasmtime::Engine.new
17 | mod = Wasmtime::Module.from_file(engine, "examples/hello.wat")
18 | store = Wasmtime::Store.new(engine, data)
19 | func = Wasmtime::Func.new(store, [], []) do |caller|
20 | puts "Hello from Func!"
21 | caller.store_data.increment!
22 | end
23 |
24 | instance = Wasmtime::Instance.new(store, mod, [func])
25 | instance.invoke("run")
26 |
27 | puts "Store's count: #{store.data.count}"
28 |
--------------------------------------------------------------------------------
/examples/hello.wat:
--------------------------------------------------------------------------------
1 | (module
2 | (func $hello (import "" "hello"))
3 | (func (export "run") (call $hello))
4 | )
5 |
--------------------------------------------------------------------------------
/examples/linking.rb:
--------------------------------------------------------------------------------
1 | require "wasmtime"
2 |
3 | engine = Wasmtime::Engine.new
4 |
5 | # Create a linker to link modules together. We want to use WASI with
6 | # the linker, so we pass in `wasi: true`.
7 | linker = Wasmtime::Linker.new(engine, wasi: true)
8 |
9 | mod1 = Wasmtime::Module.from_file(engine, "examples/linking1.wat")
10 | mod2 = Wasmtime::Module.from_file(engine, "examples/linking2.wat")
11 |
12 | wasi_ctx_builder = Wasmtime::WasiCtxBuilder.new
13 | .inherit_stdin
14 | .inherit_stdout
15 | .build
16 |
17 | store = Wasmtime::Store.new(engine, wasi_ctx: wasi_ctx_builder)
18 |
19 | # Instantiate `mod2` which only uses WASI, then register
20 | # that instance with the linker so `mod1` can use it.
21 | instance2 = linker.instantiate(store, mod2)
22 | linker.instance(store, "linking2", instance2)
23 |
24 | # Perform the final link and execute mod1's "run" function.
25 | instance1 = linker.instantiate(store, mod1)
26 | instance1.invoke("run")
27 |
--------------------------------------------------------------------------------
/examples/linking1.wat:
--------------------------------------------------------------------------------
1 | (module
2 | (import "linking2" "double" (func $double (param i32) (result i32)))
3 | (import "linking2" "log" (func $log (param i32 i32)))
4 | (import "linking2" "memory" (memory 1))
5 | (import "linking2" "memory_offset" (global $offset i32))
6 |
7 | (func (export "run")
8 | ;; Call into the other module to double our number, and we could print it
9 | ;; here but for now we just drop it
10 | i32.const 2
11 | call $double
12 | drop
13 |
14 | ;; Our `data` segment initialized our imported memory, so let's print the
15 | ;; string there now.
16 | global.get $offset
17 | i32.const 14
18 | call $log
19 | )
20 |
21 | (data (global.get $offset) "Hello, world!\n")
22 | )
23 |
--------------------------------------------------------------------------------
/examples/linking2.wat:
--------------------------------------------------------------------------------
1 | (module
2 | (type $fd_write_ty (func (param i32 i32 i32 i32) (result i32)))
3 | (import "wasi_snapshot_preview1" "fd_write" (func $fd_write (type $fd_write_ty)))
4 |
5 | (func (export "double") (param i32) (result i32)
6 | local.get 0
7 | i32.const 2
8 | i32.mul
9 | )
10 |
11 | (func (export "log") (param i32 i32)
12 | ;; store the pointer in the first iovec field
13 | i32.const 4
14 | local.get 0
15 | i32.store
16 |
17 | ;; store the length in the first iovec field
18 | i32.const 4
19 | local.get 1
20 | i32.store offset=4
21 |
22 | ;; call the `fd_write` import
23 | i32.const 1 ;; stdout fd
24 | i32.const 4 ;; iovs start
25 | i32.const 1 ;; number of iovs
26 | i32.const 0 ;; where to write nwritten bytes
27 | call $fd_write
28 | drop
29 | )
30 |
31 | (memory (export "memory") 2)
32 | (global (export "memory_offset") i32 (i32.const 65536))
33 | )
34 |
--------------------------------------------------------------------------------
/examples/memory.rb:
--------------------------------------------------------------------------------
1 | require "wasmtime"
2 |
3 | engine = Wasmtime::Engine.new
4 | mod = Wasmtime::Module.from_file(engine, "examples/memory.wat")
5 | store = Wasmtime::Store.new(engine)
6 | instance = Wasmtime::Instance.new(store, mod)
7 | memory = instance.export("memory").to_memory
8 | size_fn = instance.export("size").to_func
9 | load_fn = instance.export("load").to_func
10 | store_fn = instance.export("store").to_func
11 |
12 | puts "Checking memory..."
13 | puts " size: #{memory.size}" # => 2
14 | puts " read(0, 1): #{memory.read(0, 1).inspect}" # => "\x00"
15 | puts
16 | puts " size_fn.call: #{size_fn.call}" # => 2
17 | puts " load_fn.call(0): #{load_fn.call(0)}" # => 0
18 | puts
19 |
20 | puts "Reading out of bounds..."
21 | begin
22 | load_fn.call(0x20000)
23 | rescue Wasmtime::Trap => error
24 | puts " Trap! code: #{error.code.inspect}"
25 | end
26 | puts
27 |
28 | puts "Mutating memory..."
29 | memory.write(0x1002, "\x06")
30 | puts " load_fn.call(0x1002): #{load_fn.call(0x1002).inspect}" # => 6
31 | store_fn.call(0x1003, 7)
32 | puts " load_fn.call(0x1003): #{load_fn.call(0x1003).inspect}" # => 7
33 | puts
34 |
35 | puts "Growing memory..."
36 | memory.grow(1)
37 | puts " new size: #{memory.size}" # => 3
38 | puts
39 |
40 | puts "Creating stand-alone memory..."
41 | memory = Wasmtime::Memory.new(store, min_size: 5, max_size: 5) # size in pages
42 | puts " size: #{memory.size}" # => 5
43 | puts
44 | puts "Growing beyond limit fails..."
45 | begin
46 | memory.grow(1)
47 | rescue Wasmtime::Error => error
48 | puts " exception: #{error.inspect}"
49 | end
50 |
--------------------------------------------------------------------------------
/examples/memory.wat:
--------------------------------------------------------------------------------
1 | (module
2 | (memory (export "memory") 2 3)
3 |
4 | (func (export "size") (result i32) (memory.size))
5 | (func (export "load") (param i32) (result i32)
6 | (i32.load8_s (local.get 0))
7 | )
8 | (func (export "store") (param i32 i32)
9 | (i32.store8 (local.get 0) (local.get 1))
10 | )
11 |
12 | (data (i32.const 0x1000) "\01\02\03\04")
13 | )
14 |
--------------------------------------------------------------------------------
/examples/multi.rb:
--------------------------------------------------------------------------------
1 | require "wasmtime"
2 |
3 | engine = Wasmtime::Engine.new
4 | mod = Wasmtime::Module.from_file(engine, "examples/multi.wat")
5 | store = Wasmtime::Store.new(engine)
6 |
7 | # Host call with multiple params and results
8 | callback = Wasmtime::Func.new(store, [:i32, :i64], [:i64, :i32]) do |_caller, a, b|
9 | # Return an array with 2 elements for 2 results
10 | [b + 1, a + 1]
11 | end
12 |
13 | instance = Wasmtime::Instance.new(store, mod, [callback])
14 |
15 | g = instance.export("g").to_func
16 |
17 | puts "Calling export 'g'..."
18 | result = g.call(1, 3) # => [2, 4]
19 | puts " g result: #{result.inspect}"
20 | puts
21 |
22 | round_trip_many = instance.export("round_trip_many").to_func
23 |
24 | puts "Calling export 'round_trip_many'..."
25 | result = round_trip_many.call(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) # => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
26 | puts " round_trip_many result: #{result.inspect}"
27 |
--------------------------------------------------------------------------------
/examples/multi.wat:
--------------------------------------------------------------------------------
1 | (module
2 | (func $f (import "" "f") (param i32 i64) (result i64 i32))
3 |
4 | (func $g (export "g") (param i32 i64) (result i64 i32)
5 | (call $f (local.get 0) (local.get 1))
6 | )
7 |
8 | (func $round_trip_many
9 | (export "round_trip_many")
10 | (param i64 i64 i64 i64 i64 i64 i64 i64 i64 i64)
11 | (result i64 i64 i64 i64 i64 i64 i64 i64 i64 i64)
12 |
13 | local.get 0
14 | local.get 1
15 | local.get 2
16 | local.get 3
17 | local.get 4
18 | local.get 5
19 | local.get 6
20 | local.get 7
21 | local.get 8
22 | local.get 9)
23 | )
24 |
--------------------------------------------------------------------------------
/examples/rust-crate/.gitignore:
--------------------------------------------------------------------------------
1 | Cargo.lock
2 |
--------------------------------------------------------------------------------
/examples/rust-crate/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "rust-crate"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | doctest = false
8 |
9 | [dependencies]
10 | wasmtime-rb = { path = "../../ext", features = ["ruby-api"] }
11 |
12 | [dev-dependencies]
13 | magnus = { version = "0.7.1", features = ["embed"] } # Only need embed feature for tests
14 | wasmtime-rb = { path = "../../ext", features = ["embed"] } # Only need embed feature for tests
15 |
--------------------------------------------------------------------------------
/examples/rust-crate/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub fn create_an_engine() -> wasmtime_rb::Engine {
2 | wasmtime_rb::Engine::new(&[]).unwrap()
3 | }
4 |
5 | #[cfg(test)]
6 | mod tests {
7 | use super::*;
8 |
9 | #[test]
10 | fn it_works() {
11 | let _cleanup = unsafe { magnus::embed::init() };
12 | let engine = create_an_engine();
13 |
14 | assert!(engine.get().precompile_module(b"(module)").is_ok())
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/wasi.rb:
--------------------------------------------------------------------------------
1 | require "wasmtime"
2 |
3 | engine = Wasmtime::Engine.new
4 | mod = Wasmtime::Module.from_file(engine, "spec/fixtures/wasi-debug.wasm")
5 |
6 | linker = Wasmtime::Linker.new(engine, wasi: true)
7 |
8 | wasi_ctx = Wasmtime::WasiCtxBuilder.new
9 | .set_stdin_string("hi!")
10 | .inherit_stdout
11 | .inherit_stderr
12 | .set_argv(ARGV)
13 | .set_env(ENV)
14 | .build
15 | store = Wasmtime::Store.new(engine, wasi_ctx: wasi_ctx)
16 |
17 | instance = linker.instantiate(store, mod)
18 | instance.invoke("_start")
19 |
--------------------------------------------------------------------------------
/ext/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "wasmtime-rb"
3 | version = "9.0.4"
4 | edition = "2021"
5 | authors = ["The Wasmtime Project Developers"]
6 | license = "Apache-2.0"
7 | publish = false
8 | build = "build.rs"
9 |
10 | [lints.rust]
11 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(ruby_gte_3_0)'] }
12 |
13 | [features]
14 | default = ["tokio", "all-arch", "winch"]
15 | embed = ["magnus/embed"]
16 | tokio = ["dep:tokio", "dep:async-timer"]
17 | all-arch = ["wasmtime/all-arch"]
18 | ruby-api = []
19 | winch = ["wasmtime/winch"]
20 |
21 | [dependencies]
22 | lazy_static = "1.5.0"
23 | magnus = { version = "0.7", features = ["rb-sys"] }
24 | rb-sys = { version = "*", default-features = false, features = [
25 | "stable-api-compiled-fallback",
26 | ] }
27 | wasmtime = { version = "=33.0.0", features = ["memory-protection-keys"] }
28 | wasmtime-wasi = "=33.0.0"
29 | wasi-common = "=33.0.0"
30 | cap-std = "3.4.0"
31 | wat = "1.227.1"
32 | tokio = { version = "1.40.0", features = [
33 | "rt",
34 | "rt-multi-thread",
35 | "time",
36 | "net",
37 | ], optional = true }
38 | async-timer = { version = "1.0.0-beta.15", features = [
39 | "tokio1",
40 | ], optional = true }
41 | static_assertions = "1.1.0"
42 | wasmtime-environ = "=33.0.0"
43 | deterministic-wasi-ctx = { version = "=1.2.1", features = ["wasi-common"] }
44 |
45 | [build-dependencies]
46 | rb-sys-env = "0.2.2"
47 |
--------------------------------------------------------------------------------
/ext/build.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | env,
3 | error::Error,
4 | fs::{create_dir_all, File},
5 | io::{Read, Write},
6 | path::PathBuf,
7 | };
8 |
9 | fn main() -> Result<(), Box> {
10 | // Propogate linking from rb-sys for usage in the wasmtime-rb Rust crate
11 | let _ = rb_sys_env::activate()?;
12 |
13 | bundle_ruby_file("lib/wasmtime/error.rb")?;
14 | bundle_ruby_file("lib/wasmtime/component.rb")?;
15 |
16 | Ok(())
17 | }
18 |
19 | fn bundle_ruby_file(filename: &str) -> Result<(), Box> {
20 | let out_dir = PathBuf::from(env::var("OUT_DIR")?).join("bundled");
21 | let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?);
22 | let file = manifest_dir.join("..").join(filename);
23 | println!("cargo:rerun-if-changed={}", file.display());
24 | let out_path = file.file_name().unwrap();
25 | let out_path = out_path.to_string_lossy().replace(".rb", ".rs");
26 | let out_path = out_dir.join(out_path);
27 | create_dir_all(out_dir)?;
28 | let mut out = File::create(out_path)?;
29 |
30 | let contents = {
31 | let mut file = File::open(file)?;
32 | let mut contents = String::new();
33 | file.read_to_string(&mut contents)?;
34 | contents
35 | };
36 |
37 | let template = r##"
38 | // This file is generated by build.rs
39 |
40 | use magnus::eval;
41 |
42 | pub fn init() -> Result<(), magnus::Error> {
43 | let _: magnus::Value = eval!(
44 | r#"
45 | __FILE_CONTENTS__
46 | "#
47 | )?;
48 |
49 | Ok(())
50 | }
51 |
52 | "##;
53 |
54 | let contents = template.replace("__FILE_CONTENTS__", &contents);
55 |
56 | out.write_all(contents.as_bytes())?;
57 |
58 | Ok(())
59 | }
60 |
--------------------------------------------------------------------------------
/ext/extconf.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "mkmf"
4 | require "rb_sys/mkmf"
5 |
6 | create_rust_makefile("wasmtime/wasmtime_rb") do |ext|
7 | ext.extra_cargo_args += ["--crate-type", "cdylib"]
8 | ext.extra_cargo_args += ["--package", "wasmtime-rb"]
9 | ext.extra_rustflags = ["--cfg=rustix_use_libc"]
10 | end
11 |
--------------------------------------------------------------------------------
/ext/src/helpers/macros.rs:
--------------------------------------------------------------------------------
1 | /// A macro to define a new `Id` const for a given string.
2 | #[macro_export]
3 | macro_rules! define_rb_intern {
4 | ($($name:ident => $id:expr,)*) => {
5 | $(
6 | lazy_static::lazy_static! {
7 | /// Define a Ruby internal `Id`. Equivalent to `rb_intern("$name")`
8 | pub static ref $name: $crate::helpers::StaticId = $crate::helpers::StaticId::intern_str($id);
9 | }
10 | )*
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/ext/src/helpers/mod.rs:
--------------------------------------------------------------------------------
1 | mod macros;
2 | mod nogvl;
3 | mod output_limited_buffer;
4 | mod static_id;
5 | mod symbol_enum;
6 | mod tmplock;
7 |
8 | pub use nogvl::nogvl;
9 | pub use output_limited_buffer::OutputLimitedBuffer;
10 | pub use static_id::StaticId;
11 | pub use symbol_enum::SymbolEnum;
12 | pub use tmplock::Tmplock;
13 |
--------------------------------------------------------------------------------
/ext/src/helpers/nogvl.rs:
--------------------------------------------------------------------------------
1 | use std::{ffi::c_void, mem::MaybeUninit, ptr::null_mut};
2 |
3 | use rb_sys::rb_thread_call_without_gvl;
4 |
5 | unsafe extern "C" fn call_without_gvl(arg: *mut c_void) -> *mut c_void
6 | where
7 | F: FnMut() -> R,
8 | R: Sized,
9 | {
10 | let arg = arg as *mut (&mut F, &mut MaybeUninit);
11 | let (func, result) = unsafe { &mut *arg };
12 | result.write(func());
13 |
14 | null_mut()
15 | }
16 |
17 | pub fn nogvl(mut func: F) -> R
18 | where
19 | F: FnMut() -> R,
20 | R: Sized,
21 | {
22 | let result = MaybeUninit::uninit();
23 | let arg_ptr = &(&mut func, &result) as *const _ as *mut c_void;
24 |
25 | unsafe {
26 | rb_thread_call_without_gvl(Some(call_without_gvl::), arg_ptr, None, null_mut());
27 | result.assume_init()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/ext/src/helpers/output_limited_buffer.rs:
--------------------------------------------------------------------------------
1 | use magnus::{
2 | value::{InnerValue, Opaque, ReprValue},
3 | RString, Ruby,
4 | };
5 | use std::io;
6 | use std::io::ErrorKind;
7 |
8 | /// A buffer that limits the number of bytes that can be written to it.
9 | /// If the buffer is full, it will truncate the data.
10 | /// Is used in the buffer implementations of stdout and stderr in `WasiCtx` and `WasiCtxBuilder`.
11 | pub struct OutputLimitedBuffer {
12 | buffer: Opaque,
13 | /// The maximum number of bytes that can be written to the output stream buffer.
14 | capacity: usize,
15 | }
16 |
17 | impl OutputLimitedBuffer {
18 | #[must_use]
19 | pub fn new(buffer: Opaque, capacity: usize) -> Self {
20 | Self { buffer, capacity }
21 | }
22 | }
23 |
24 | impl io::Write for OutputLimitedBuffer {
25 | fn write(&mut self, buf: &[u8]) -> std::io::Result {
26 | // Append a buffer to the string and truncate when hitting the capacity.
27 | // We return the input buffer size regardless of whether we truncated or not to avoid a panic.
28 | let ruby = Ruby::get().unwrap();
29 |
30 | let mut inner_buffer = self.buffer.get_inner_with(&ruby);
31 |
32 | // Handling frozen case here is necessary because magnus does not check if a string is frozen before writing to it.
33 | let is_frozen = inner_buffer.as_value().is_frozen();
34 | if is_frozen {
35 | return Err(io::Error::new(
36 | ErrorKind::WriteZero,
37 | "Cannot write to a frozen buffer.",
38 | ));
39 | }
40 |
41 | if buf.is_empty() {
42 | return Ok(0);
43 | }
44 |
45 | if inner_buffer
46 | .len()
47 | .checked_add(buf.len())
48 | .is_some_and(|val| val < self.capacity)
49 | {
50 | let amount_written = inner_buffer.write(buf)?;
51 | if amount_written < buf.len() {
52 | return Ok(amount_written);
53 | }
54 | } else {
55 | let portion = self.capacity - inner_buffer.len();
56 | let amount_written = inner_buffer.write(&buf[0..portion])?;
57 | if amount_written < portion {
58 | return Ok(amount_written);
59 | }
60 | };
61 |
62 | Ok(buf.len())
63 | }
64 |
65 | fn flush(&mut self) -> io::Result<()> {
66 | let ruby = Ruby::get().unwrap();
67 |
68 | self.buffer.get_inner_with(&ruby).flush()
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/ext/src/helpers/static_id.rs:
--------------------------------------------------------------------------------
1 | use magnus::rb_sys::{AsRawId, FromRawId};
2 | use magnus::{
3 | value::{Id, IntoId},
4 | Ruby, Symbol,
5 | };
6 | use std::convert::TryInto;
7 | use std::num::NonZeroUsize;
8 |
9 | /// A static `Id` that can be used to refer to a Ruby ID.
10 | ///
11 | /// Use `define_rb_intern!` to define it so that it will be cached in a global variable.
12 | ///
13 | /// Magnus' `Id` can't be used for this purpose since it is not `Sync`, so cannot
14 | /// be used as a global variable with `lazy_static` in `define_rb_intern!`.
15 | /// See [this commit on the Magnus repo][commit].
16 | ///
17 | /// [commit]: https://github.com/matsadler/magnus/commit/1a1c1ee874e15b0b222f7aae68bb9b5360072e57
18 | #[derive(Clone, Copy)]
19 | #[repr(transparent)]
20 | pub struct StaticId(NonZeroUsize);
21 |
22 | impl StaticId {
23 | // Use `define_rb_intern!` instead, which uses this function.
24 | pub fn intern_str(id: &'static str) -> Self {
25 | let id: Id = magnus::StaticSymbol::new(id).into();
26 |
27 | // SAFETY: Ruby will never return a `0` ID.
28 | StaticId(unsafe { NonZeroUsize::new_unchecked(id.as_raw() as _) })
29 | }
30 | }
31 |
32 | impl IntoId for StaticId {
33 | fn into_id_with(self, _: &Ruby) -> Id {
34 | // SAFEFY: This is safe because we know that the `Id` is something
35 | // returned from ruby.
36 | unsafe { Id::from_raw(self.0.get().try_into().expect("ID to be a usize")) }
37 | }
38 | }
39 |
40 | impl From for Symbol {
41 | fn from(static_id: StaticId) -> Self {
42 | let id: Id = static_id.into_id();
43 | id.into()
44 | }
45 | }
46 |
47 | impl std::cmp::PartialEq for StaticId {
48 | fn eq(&self, other: &Id) -> bool {
49 | other.as_raw() as usize == self.0.get()
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/ext/src/helpers/symbol_enum.rs:
--------------------------------------------------------------------------------
1 | use super::static_id::StaticId;
2 | use magnus::{exception::arg_error, prelude::*, Error, Symbol, TryConvert, Value};
3 | use std::fmt::Display;
4 |
5 | /// Represents an enum as a set of Symbols (using `StaticId`).
6 | /// Each symbol maps to a value of the enum's type.
7 | pub struct SymbolEnum<'a, T: Clone> {
8 | what: &'a str,
9 | mapping: Mapping,
10 | }
11 |
12 | impl<'a, T: Clone> SymbolEnum<'a, T> {
13 | pub fn new(what: &'a str, mapping: Vec<(StaticId, T)>) -> Self {
14 | Self {
15 | what,
16 | mapping: Mapping(mapping),
17 | }
18 | }
19 |
20 | /// Map a Magnus `Value` to the entry in the enum.
21 | /// Returns an `ArgumentError` with a message enumerating all valid symbols
22 | /// when `needle` isn't a valid symbol.
23 | pub fn get(&self, needle: Value) -> Result {
24 | let needle = Symbol::try_convert(needle).map_err(|_| self.error(needle))?;
25 | let id = magnus::value::Id::from(needle);
26 |
27 | self.mapping
28 | .0
29 | .iter()
30 | .find(|(haystack, _)| *haystack == id)
31 | .map(|found| found.1.clone())
32 | .ok_or_else(|| self.error(needle.as_value()))
33 | }
34 |
35 | pub fn error(&self, value: Value) -> Error {
36 | Error::new(
37 | arg_error(),
38 | format!(
39 | "invalid {}, expected one of {}, got {:?}",
40 | self.what, self.mapping, value
41 | ),
42 | )
43 | }
44 | }
45 |
46 | struct Mapping(Vec<(StaticId, T)>);
47 |
48 | /// Mimicks `Array#inpsect`'s output with all valid symbols.
49 | /// E.g.: `[:s1, :s2, :s3]`
50 | impl Display for Mapping {
51 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 | write!(f, "[")?;
53 |
54 | if let Some(((last, _), elems)) = self.0.split_last() {
55 | for (id, _) in elems.iter() {
56 | write!(f, ":{}, ", Symbol::from(*id).name().unwrap())?;
57 | }
58 | write!(f, ":{}", Symbol::from(*last).name().unwrap())?;
59 | }
60 |
61 | write!(f, "]")?;
62 | Ok(())
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/ext/src/helpers/tmplock.rs:
--------------------------------------------------------------------------------
1 | use magnus::{
2 | rb_sys::{protect, AsRawValue},
3 | RString,
4 | };
5 |
6 | pub trait Tmplock {
7 | fn as_locked_slice(&self) -> Result<(&[u8], TmplockGuard), magnus::Error>;
8 | fn as_locked_str(&self) -> Result<(&str, TmplockGuard), magnus::Error>;
9 | }
10 |
11 | #[derive(Debug)]
12 | #[repr(transparent)]
13 | pub struct TmplockGuard {
14 | raw: rb_sys::VALUE,
15 | }
16 |
17 | impl Drop for TmplockGuard {
18 | fn drop(&mut self) {
19 | let result = unsafe { protect(|| rb_sys::rb_str_unlocktmp(self.raw)) };
20 | debug_assert!(
21 | result.is_ok(),
22 | "failed to unlock tmplock for unknown reason"
23 | );
24 | }
25 | }
26 |
27 | impl Tmplock for RString {
28 | fn as_locked_slice(&self) -> Result<(&[u8], TmplockGuard), magnus::Error> {
29 | let raw = self.as_raw();
30 | let slice = unsafe { self.as_slice() };
31 | let raw = protect(|| unsafe { rb_sys::rb_str_locktmp(raw) })?;
32 | let guard = TmplockGuard { raw };
33 |
34 | Ok((slice, guard))
35 | }
36 |
37 | fn as_locked_str(&self) -> Result<(&str, TmplockGuard), magnus::Error> {
38 | let str_result = unsafe { self.as_str()? };
39 | let raw = self.as_raw();
40 | let raw = protect(|| unsafe { rb_sys::rb_str_locktmp(raw) })?;
41 | let guard = TmplockGuard { raw };
42 |
43 | Ok((str_result, guard))
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/ext/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![allow(clippy::doc_lazy_continuation)]
2 | use magnus::{Error, Ruby};
3 | mod helpers;
4 | mod ruby_api;
5 |
6 | #[cfg(feature = "ruby-api")]
7 | pub use ruby_api::*;
8 |
9 | #[cfg(not(feature = "ruby-api"))]
10 | pub(crate) use ruby_api::*;
11 |
12 | #[cfg(not(feature = "ruby-api"))] // Let the upstream crate handle this
13 | rb_sys::set_global_tracking_allocator!();
14 |
15 | #[magnus::init]
16 | pub fn init(ruby: &Ruby) -> Result<(), Error> {
17 | #[cfg(ruby_gte_3_0)]
18 | unsafe {
19 | rb_sys::rb_ext_ractor_safe(true);
20 | }
21 | ruby_api::init(ruby)
22 | }
23 |
--------------------------------------------------------------------------------
/ext/src/ruby_api/caller.rs:
--------------------------------------------------------------------------------
1 | use super::{convert::WrapWasmtimeType, externals::Extern, root, store::StoreData};
2 | use crate::error;
3 | use magnus::{class, method, typed_data::Obj, Error, Module as _, RString, Value};
4 | use std::cell::UnsafeCell;
5 | use wasmtime::{AsContext, AsContextMut, Caller as CallerImpl, StoreContext, StoreContextMut};
6 |
7 | /// A handle to a [`wasmtime::Caller`] that's only valid during a Func execution.
8 | /// [`UnsafeCell`] wraps the wasmtime::Caller because the Value's lifetime can't
9 | /// be tied to the Caller: the Value is handed back to Ruby and we can't control
10 | /// whether the user keeps a handle to it or not.
11 | #[derive(Debug)]
12 | pub struct CallerHandle<'a> {
13 | caller: UnsafeCell