Merge remote-tracking branch 'origin/master' into cranelift-master-rewrite
This commit is contained in:
10
.dependabot/config.yml
Normal file
10
.dependabot/config.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
# See https://dependabot.com/docs/config-file/ for a reference.
|
||||
version: 1
|
||||
update_configs:
|
||||
- package_manager: "rust:cargo"
|
||||
directory: "/"
|
||||
update_schedule: "live"
|
||||
version_requirement_updates: "auto"
|
||||
allowed_updates:
|
||||
- match:
|
||||
update_type: "security"
|
||||
9
.github/actions/binary-compatible-builds/README.md
vendored
Normal file
9
.github/actions/binary-compatible-builds/README.md
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# binary-compatible-builds
|
||||
|
||||
A small (ish) action which is intended to be used and will configure builds of
|
||||
Rust projects to be "more binary compatible". On Windows and macOS this
|
||||
involves setting a few env vars, and on Linux this involves spinning up a CentOS
|
||||
6 container which is running in the background.
|
||||
|
||||
All subsequent build commands need to be wrapped in `$CENTOS` to optionally run
|
||||
on `$CENTOS` on Linux to ensure builds happen inside the container.
|
||||
6
.github/actions/binary-compatible-builds/action.yml
vendored
Normal file
6
.github/actions/binary-compatible-builds/action.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
name: 'Set up a CentOS 6 container to build releases in'
|
||||
description: 'Set up a CentOS 6 container to build releases in'
|
||||
|
||||
runs:
|
||||
using: node12
|
||||
main: 'main.js'
|
||||
69
.github/actions/binary-compatible-builds/main.js
vendored
Executable file
69
.github/actions/binary-compatible-builds/main.js
vendored
Executable file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const child_process = require('child_process');
|
||||
const stdio = { stdio: 'inherit' };
|
||||
|
||||
// On OSX all we need to do is configure our deployment target as old as
|
||||
// possible. For now 10.9 is the limit.
|
||||
if (process.platform == 'darwin') {
|
||||
console.log("::set-env name=MACOSX_DEPLOYMENT_TARGET::10.9");
|
||||
console.log("::set-env name=python::python3");
|
||||
return;
|
||||
}
|
||||
|
||||
// On Windows we build against the static CRT to reduce dll dependencies
|
||||
if (process.platform == 'win32') {
|
||||
console.log("::set-env name=RUSTFLAGS::-Ctarget-feature=+crt-static");
|
||||
console.log("::set-env name=python::python");
|
||||
return;
|
||||
}
|
||||
|
||||
// ... and on Linux we do fancy things with containers. We'll spawn an old
|
||||
// CentOS container in the background with a super old glibc, and then we'll run
|
||||
// commands in there with the `$CENTOS` env var.
|
||||
|
||||
if (process.env.CENTOS !== undefined) {
|
||||
const args = ['exec', '-w', process.cwd(), '-i', 'centos'];
|
||||
for (const arg of process.argv.slice(2)) {
|
||||
args.push(arg);
|
||||
}
|
||||
child_process.execFileSync('docker', args, stdio);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add our rust mount onto PATH, but also add some stuff to PATH from
|
||||
// the packages that we install.
|
||||
let path = process.env.PATH;
|
||||
path = `${path}:/rust/bin`;
|
||||
path = `/opt/rh/devtoolset-8/root/usr/bin:${path}`;
|
||||
path = `/opt/rh/rh-python36/root/usr/bin:${path}`;
|
||||
|
||||
// Spawn a container daemonized in the background which we'll connect to via
|
||||
// `docker exec`. This'll have access to the current directory.
|
||||
child_process.execFileSync('docker', [
|
||||
'run',
|
||||
'-di',
|
||||
'--name', 'centos',
|
||||
'-v', `${process.cwd()}:${process.cwd()}`,
|
||||
'-v', `${child_process.execSync('rustc --print sysroot').toString().trim()}:/rust:ro`,
|
||||
'--env', `PATH=${path}`,
|
||||
'centos:6',
|
||||
], stdio);
|
||||
|
||||
// Use ourselves to run future commands
|
||||
console.log(`::set-env name=CENTOS::${__filename}`)
|
||||
|
||||
// See https://edwards.sdsu.edu/research/c11-on-centos-6/ for where these
|
||||
const exec = s => {
|
||||
child_process.execSync(`docker exec centos ${s}`, stdio);
|
||||
};
|
||||
exec('yum install -y centos-release-scl cmake xz epel-release');
|
||||
exec('yum install -y rh-python36 patchelf unzip');
|
||||
exec('yum install -y devtoolset-8-gcc devtoolset-8-binutils devtoolset-8-gcc-c++');
|
||||
exec('yum install -y git');
|
||||
|
||||
// Delete `libstdc++.so` to force gcc to link against `libstdc++.a` instead.
|
||||
// This is a hack and not the right way to do this, but it ends up doing the
|
||||
// right thing for now.
|
||||
exec('rm -f /opt/rh/devtoolset-8/root/usr/lib/gcc/x86_64-redhat-linux/8/libstdc++.so');
|
||||
console.log("::set-env name=python::python3");
|
||||
3
.github/actions/define-dwarfdump-env/README.md
vendored
Normal file
3
.github/actions/define-dwarfdump-env/README.md
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# define-dwarfdump-env
|
||||
|
||||
Defines `DWARFDUMP` path executable.
|
||||
6
.github/actions/define-dwarfdump-env/action.yml
vendored
Normal file
6
.github/actions/define-dwarfdump-env/action.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
name: 'Set up a DWARFDUMP env'
|
||||
description: 'Set up a DWARFDUMP env (see tests/debug/dump.rs)'
|
||||
|
||||
runs:
|
||||
using: node12
|
||||
main: 'main.js'
|
||||
11
.github/actions/define-dwarfdump-env/main.js
vendored
Executable file
11
.github/actions/define-dwarfdump-env/main.js
vendored
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// On OSX pointing to brew's LLVM location.
|
||||
if (process.platform == 'darwin') {
|
||||
console.log("::set-env name=DWARFDUMP::/usr/local/opt/llvm/bin/llvm-dwarfdump");
|
||||
}
|
||||
|
||||
// On Linux pointing to specific version
|
||||
if (process.platform == 'linux') {
|
||||
console.log("::set-env name=DWARFDUMP::/usr/bin/llvm-dwarfdump-9");
|
||||
}
|
||||
8
.github/actions/github-release/Dockerfile
vendored
Normal file
8
.github/actions/github-release/Dockerfile
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
FROM node:slim
|
||||
|
||||
COPY . /action
|
||||
WORKDIR /action
|
||||
|
||||
RUN npm install --production
|
||||
|
||||
ENTRYPOINT ["node", "/action/main.js"]
|
||||
18
.github/actions/github-release/README.md
vendored
Normal file
18
.github/actions/github-release/README.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# github-release
|
||||
|
||||
An action used to publish GitHub releases for `wasmtime`.
|
||||
|
||||
As of the time of this writing there's a few actions floating around which
|
||||
perform github releases but they all tend to have their set of drawbacks.
|
||||
Additionally nothing handles deleting releases which we need for our rolling
|
||||
`dev` release.
|
||||
|
||||
To handle all this this action rolls-its-own implementation using the
|
||||
actions/toolkit repository and packages published there. These run in a Docker
|
||||
container and take various inputs to orchestrate the release from the build.
|
||||
|
||||
More comments can be found in `main.js`.
|
||||
|
||||
Testing this is really hard. If you want to try though run `npm install` and
|
||||
then `node main.js`. You'll have to configure a bunch of env vars though to get
|
||||
anything reasonably working.
|
||||
15
.github/actions/github-release/action.yml
vendored
Normal file
15
.github/actions/github-release/action.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
name: 'wasmtime github releases'
|
||||
description: 'wasmtime github releases'
|
||||
inputs:
|
||||
token:
|
||||
description: ''
|
||||
required: true
|
||||
name:
|
||||
description: ''
|
||||
required: true
|
||||
files:
|
||||
description: ''
|
||||
required: true
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'Dockerfile'
|
||||
117
.github/actions/github-release/main.js
vendored
Normal file
117
.github/actions/github-release/main.js
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
const core = require('@actions/core');
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const github = require('@actions/github');
|
||||
const glob = require('glob');
|
||||
|
||||
function sleep(milliseconds) {
|
||||
return new Promise(resolve => setTimeout(resolve, milliseconds))
|
||||
}
|
||||
|
||||
async function runOnce() {
|
||||
// Load all our inputs and env vars. Note that `getInput` reads from `INPUT_*`
|
||||
const files = core.getInput('files');
|
||||
const name = core.getInput('name');
|
||||
const token = core.getInput('token');
|
||||
const slug = process.env.GITHUB_REPOSITORY;
|
||||
const owner = slug.split('/')[0];
|
||||
const repo = slug.split('/')[1];
|
||||
const sha = process.env.GITHUB_SHA;
|
||||
|
||||
core.info(`files: ${files}`);
|
||||
core.info(`name: ${name}`);
|
||||
core.info(`token: ${token}`);
|
||||
|
||||
const octokit = new github.GitHub(token);
|
||||
|
||||
// Delete the previous release since we can't overwrite one. This may happen
|
||||
// due to retrying an upload or it may happen because we're doing the dev
|
||||
// release.
|
||||
const releases = await octokit.paginate("GET /repos/:owner/:repo/releases", { owner, repo });
|
||||
for (const release of releases) {
|
||||
if (release.tag_name !== name) {
|
||||
continue;
|
||||
}
|
||||
const release_id = release.id;
|
||||
core.info(`deleting release ${release_id}`);
|
||||
await octokit.repos.deleteRelease({ owner, repo, release_id });
|
||||
}
|
||||
|
||||
// We also need to update the `dev` tag while we're at it on the `dev` branch.
|
||||
if (name == 'dev') {
|
||||
try {
|
||||
core.info(`updating dev tag`);
|
||||
await octokit.git.updateRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: 'tags/dev',
|
||||
sha,
|
||||
force: true,
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("ERROR: ", JSON.stringify(e, null, 2));
|
||||
core.info(`creating dev tag`);
|
||||
await octokit.git.createTag({
|
||||
owner,
|
||||
repo,
|
||||
tag: 'dev',
|
||||
message: 'dev release',
|
||||
object: sha,
|
||||
type: 'commit',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Creates an official GitHub release for this `tag`, and if this is `dev`
|
||||
// then we know that from the previous block this should be a fresh release.
|
||||
core.info(`creating a release`);
|
||||
const release = await octokit.repos.createRelease({
|
||||
owner,
|
||||
repo,
|
||||
tag_name: name,
|
||||
prerelease: name === 'dev',
|
||||
});
|
||||
|
||||
// Upload all the relevant assets for this release as just general blobs.
|
||||
for (const file of glob.sync(files)) {
|
||||
const size = fs.statSync(file).size;
|
||||
core.info(`upload ${file}`);
|
||||
await octokit.repos.uploadReleaseAsset({
|
||||
data: fs.createReadStream(file),
|
||||
headers: { 'content-length': size, 'content-type': 'application/octet-stream' },
|
||||
name: path.basename(file),
|
||||
url: release.data.upload_url,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function run() {
|
||||
const retries = 10;
|
||||
for (let i = 0; i < retries; i++) {
|
||||
try {
|
||||
await runOnce();
|
||||
break;
|
||||
} catch (e) {
|
||||
if (i === retries - 1)
|
||||
throw e;
|
||||
logError(e);
|
||||
console.log("RETRYING after 10s");
|
||||
await sleep(10000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function logError(e) {
|
||||
console.log("ERROR: ", e.message);
|
||||
try {
|
||||
console.log(JSON.stringify(e, null, 2));
|
||||
} catch (e) {
|
||||
// ignore json errors for now
|
||||
}
|
||||
console.log(e.stack);
|
||||
}
|
||||
|
||||
run().catch(err => {
|
||||
logError(err);
|
||||
core.setFailed(err.message);
|
||||
});
|
||||
10
.github/actions/github-release/package.json
vendored
Normal file
10
.github/actions/github-release/package.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "wasmtime-github-release",
|
||||
"version": "0.0.0",
|
||||
"main": "main.js",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.0.0",
|
||||
"@actions/github": "^1.0.0",
|
||||
"glob": "^7.1.5"
|
||||
}
|
||||
}
|
||||
18
.github/actions/install-rust/README.md
vendored
Normal file
18
.github/actions/install-rust/README.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# install-rust
|
||||
|
||||
A small github action to install `rustup` and a Rust toolchain. This is
|
||||
generally expressed inline, but it was repeated enough in this repository it
|
||||
seemed worthwhile to extract.
|
||||
|
||||
Some gotchas:
|
||||
|
||||
* Can't `--self-update` on Windows due to permission errors (a bug in Github
|
||||
Actions)
|
||||
* `rustup` isn't installed on macOS (a bug in Github Actions)
|
||||
|
||||
When the above are fixed we should delete this action and just use this inline:
|
||||
|
||||
```yml
|
||||
- run: rustup update $toolchain && rustup default $toolchain
|
||||
shell: bash
|
||||
```
|
||||
12
.github/actions/install-rust/action.yml
vendored
Normal file
12
.github/actions/install-rust/action.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: 'Install Rust toolchain'
|
||||
description: 'Install both `rustup` and a Rust toolchain'
|
||||
|
||||
inputs:
|
||||
toolchain:
|
||||
description: 'Default toolchan to install'
|
||||
required: false
|
||||
default: 'stable'
|
||||
|
||||
runs:
|
||||
using: node12
|
||||
main: 'main.js'
|
||||
13
.github/actions/install-rust/main.js
vendored
Normal file
13
.github/actions/install-rust/main.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
const child_process = require('child_process');
|
||||
const toolchain = process.env.INPUT_TOOLCHAIN;
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
child_process.execSync(`curl https://sh.rustup.rs | sh -s -- -y --default-toolchain=none --profile=minimal`);
|
||||
const bindir = `${process.env.HOME}/.cargo/bin`;
|
||||
console.log(`::add-path::${bindir}`);
|
||||
process.env.PATH = `${process.env.PATH}:${bindir}`;
|
||||
}
|
||||
|
||||
child_process.execFileSync('rustup', ['set', 'profile', 'minimal']);
|
||||
child_process.execFileSync('rustup', ['update', toolchain, '--no-self-update']);
|
||||
child_process.execFileSync('rustup', ['default', toolchain]);
|
||||
546
.github/workflows/main.yml
vendored
Normal file
546
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,546 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
tags-ignore: [dev]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
# Check Code style quickly by running `rustfmt` over all code
|
||||
rustfmt:
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/actions/install-rust
|
||||
- run: cargo fmt --all -- --check
|
||||
|
||||
# Build `mdBook` documentation for `wasmtime`, and upload it as a temporary
|
||||
# build artifact
|
||||
doc_book:
|
||||
name: Doc - build the book
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
- run: |
|
||||
set -e
|
||||
curl -L https://github.com/rust-lang-nursery/mdBook/releases/download/v0.3.1/mdbook-v0.3.1-x86_64-unknown-linux-gnu.tar.gz | tar xzf -
|
||||
echo ::add-path::`pwd`
|
||||
- run: (cd docs && mdbook build)
|
||||
- run: cargo build -p wasmtime
|
||||
- run: (cd docs && mdbook test -L ../target/debug/deps)
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: doc-book
|
||||
path: docs/book
|
||||
|
||||
# Build rustdoc API documentation for `wasmtime*` crates. Note that we don't
|
||||
# want to document all our transitive dependencies, hence `--no-deps`. This is
|
||||
# a temporary build artifact we upload to consume later.
|
||||
doc_api:
|
||||
name: Doc - build the API documentation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/actions/install-rust
|
||||
with:
|
||||
toolchain: nightly
|
||||
- run: cargo doc --no-deps --all --exclude wasmtime-cli --exclude test-programs
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: doc-api
|
||||
path: target/doc
|
||||
|
||||
# Download our libFuzzer corpus and make sure that we can still handle all the
|
||||
# inputs.
|
||||
fuzz_corpora:
|
||||
name: Fuzz Corpora
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
repository: bytecodealliance/wasmtime-libfuzzer-corpus
|
||||
path: ./wasmtime/fuzz/corpus
|
||||
ref: refs/heads/master
|
||||
- uses: ./.github/actions/install-rust
|
||||
with:
|
||||
toolchain: nightly
|
||||
- run: cargo install cargo-fuzz --vers "^0.7"
|
||||
- run: cargo fetch
|
||||
working-directory: ./fuzz
|
||||
- run: cargo fuzz build --release --debug-assertions
|
||||
# Our corpora are too large to run in full on every pull request, they just
|
||||
# take too long. Instead, we sample some of them and make sure that running
|
||||
# our fuzzers over the sampled inputs still works OK.
|
||||
- run: |
|
||||
find fuzz/corpus/compile -type f \
|
||||
| shuf \
|
||||
| head -n 3000 \
|
||||
| xargs cargo fuzz run compile --release --debug-assertions
|
||||
- run: |
|
||||
find fuzz/corpus/instantiate -type f \
|
||||
| shuf \
|
||||
| head -n 2000 \
|
||||
| xargs cargo fuzz run instantiate --release --debug-assertions
|
||||
- run: |
|
||||
find fuzz/corpus/instantiate_translated -type f \
|
||||
| shuf \
|
||||
| head -n 1000 \
|
||||
| xargs cargo fuzz run instantiate_translated --release --debug-assertions
|
||||
- run: |
|
||||
find fuzz/corpus/api_calls -type f \
|
||||
| shuf \
|
||||
| head -n 100 \
|
||||
| xargs cargo fuzz run api_calls --release --debug-assertions
|
||||
- run: |
|
||||
find fuzz/corpus/differential -type f \
|
||||
| shuf \
|
||||
| head -n 100 \
|
||||
| xargs cargo fuzz run differential --release --debug-assertions
|
||||
|
||||
# Install wasm32-unknown-emscripten target, and ensure `crates/wasi-common`
|
||||
# compiles to Emscripten.
|
||||
# TODO enable once rust-lang/rust#66308 is fixed
|
||||
# emscripten:
|
||||
# name: Emscripten
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v1
|
||||
# with:
|
||||
# submodules: true
|
||||
# - uses: ./.github/actions/install-rust
|
||||
# - run: rustup target add wasm32-unknown-emscripten
|
||||
# - run: cargo build --target wasm32-unknown-emscripten -p wasi-common
|
||||
|
||||
# Perform all tests (debug mode) for `wasmtime`. This runs stable/beta/nightly
|
||||
# channels of Rust as well as macOS/Linux/Windows.
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
build: [stable, beta, nightly, windows, macos]
|
||||
include:
|
||||
- build: stable
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
- build: beta
|
||||
os: ubuntu-latest
|
||||
rust: beta
|
||||
- build: nightly
|
||||
os: ubuntu-latest
|
||||
rust: nightly
|
||||
- build: macos
|
||||
os: macos-latest
|
||||
rust: stable
|
||||
- build: windows
|
||||
os: windows-latest
|
||||
rust: stable
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/actions/install-rust
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
- uses: ./.github/actions/define-dwarfdump-env
|
||||
|
||||
- name: Install libclang
|
||||
# Note: libclang is pre-installed on the macOS and linux images.
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
Invoke-WebRequest http://releases.llvm.org/9.0.0/LLVM-9.0.0-win64.exe -OutFile llvm-installer.exe
|
||||
7z x llvm-installer.exe -oC:\llvm-binary
|
||||
Write-Host ::set-env name=LIBCLANG_PATH::C:\llvm-binary\bin\libclang.dll
|
||||
Write-Host ::add-path::C:\llvm-binary\bin
|
||||
|
||||
- name: Query Clang Version
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
Get-Command clang.exe
|
||||
clang.exe --version
|
||||
|
||||
# Install wasm32-wasi target in order to build wasi-common's integration
|
||||
# tests
|
||||
- run: rustup target add wasm32-wasi
|
||||
|
||||
- run: cargo fetch --locked
|
||||
- run: cargo fetch --locked --manifest-path crates/test-programs/wasi-tests/Cargo.toml
|
||||
|
||||
# Build some various feature combinations
|
||||
- run: cargo build --manifest-path crates/api/Cargo.toml --no-default-features
|
||||
- run: cargo build --manifest-path crates/api/Cargo.toml --features wat
|
||||
- run: cargo build --manifest-path crates/api/Cargo.toml --features lightbeam
|
||||
if: matrix.rust == 'nightly'
|
||||
|
||||
# Build and test all features except for lightbeam
|
||||
- run: cargo test --features test_programs --all --exclude lightbeam --exclude wasmtime-c-api -- --nocapture
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
RUSTFLAGS: "-D warnings"
|
||||
|
||||
# Build and test lightbeam if we're using the nightly toolchain. Note that
|
||||
# Lightbeam tests fail right now, but we don't want to block on that.
|
||||
- run: cargo build --package lightbeam
|
||||
if: matrix.rust == 'nightly'
|
||||
- run: cargo test --package lightbeam -- --nocapture
|
||||
if: matrix.rust == 'nightly'
|
||||
continue-on-error: true
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
|
||||
# Build and test c-api examples. Skipping testing on Windows due to
|
||||
# GNU make dependency when executing the wasm-c-api examples.
|
||||
- run: cargo build --package wasmtime-c-api
|
||||
if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest'
|
||||
- run: cargo test --package wasmtime-c-api -- --nocapture --test-threads 1
|
||||
if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest'
|
||||
continue-on-error: true
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
|
||||
# Builds a Python wheel (package) for Windows/Mac/Linux. Note that we're
|
||||
# careful to create binary-compatible releases here to old releases of
|
||||
# Windows/Mac/Linux. This will also build wheels for Python 3.6, 3.7 and 3.8.
|
||||
wheels:
|
||||
name: Python Wheel
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/actions/install-rust
|
||||
with:
|
||||
toolchain: nightly-2020-01-06
|
||||
- uses: ./.github/actions/binary-compatible-builds
|
||||
- run: mkdir crates/misc/py/wheelhouse
|
||||
shell: bash
|
||||
|
||||
# Install Python & dependencies needed for our `setup.py` scripts
|
||||
- name: Setup Python 3.6
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: '3.6'
|
||||
architecture: x64
|
||||
- run: $CENTOS pip3 install setuptools wheel setuptools-rust
|
||||
shell: bash
|
||||
- run: (cd crates/misc/py && $CENTOS $python setup.py bdist_wheel)
|
||||
shell: bash
|
||||
|
||||
# Clear the build directory between building different wheels for different
|
||||
# Python versions to ensure that we don't package dynamic libraries twice by
|
||||
# accident.
|
||||
- run: $CENTOS rm -rf crates/misc/py/build
|
||||
shell: bash
|
||||
|
||||
# Set up Python 3.7 (and build it on Linux), reinstall dependencies, then
|
||||
# rebuild our wheels
|
||||
- name: Setup Python 3.7
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: '3.7'
|
||||
architecture: x64
|
||||
if: matrix.os != 'ubuntu-latest'
|
||||
- name: Build Python 3.7
|
||||
run: $CENTOS sh ci/setup_centos6_python3.sh 3.7.3
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
- run: $CENTOS pip3 install setuptools wheel setuptools-rust auditwheel
|
||||
shell: bash
|
||||
- run: (cd crates/misc/py && $CENTOS $python setup.py bdist_wheel)
|
||||
shell: bash
|
||||
|
||||
# Clear the build directory between building different wheels for different
|
||||
# Python versions to ensure that we don't package dynamic libraries twice by
|
||||
# accident.
|
||||
- run: $CENTOS rm -rf crates/misc/py/build
|
||||
shell: bash
|
||||
|
||||
# Set up Python 3.8 (and build it on Linux), reinstall dependencies, then
|
||||
# rebuild our wheels
|
||||
- name: Setup Python 3.8
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: '3.8'
|
||||
architecture: x64
|
||||
if: matrix.os != 'ubuntu-latest'
|
||||
- name: Build Python 3.8
|
||||
run: $CENTOS sh ci/setup_centos6_python3.sh 3.8.0
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
- run: $CENTOS pip3 install setuptools wheel setuptools-rust auditwheel
|
||||
shell: bash
|
||||
- run: (cd crates/misc/py && $CENTOS $python setup.py bdist_wheel)
|
||||
shell: bash
|
||||
|
||||
# Move `dist/*.whl` into `wheelhouse/` so we can deploy them, but on Linux we
|
||||
# need to run an `auditwheel` command as well to turn these into "manylinux"
|
||||
# wheels to run across a number of distributions.
|
||||
- run: cp crates/misc/py/dist/*.whl crates/misc/py/wheelhouse/
|
||||
shell: bash
|
||||
if: matrix.os != 'ubuntu-latest'
|
||||
- run: |
|
||||
set -e
|
||||
cd crates/misc/py
|
||||
for whl in dist/*.whl; do
|
||||
$CENTOS auditwheel repair "$whl" -w wheelhouse/
|
||||
done
|
||||
shell: bash
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
|
||||
# Upload this for the publishing stage of pipelines
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: wheels-${{ matrix.os }}
|
||||
path: crates/misc/py/wheelhouse
|
||||
|
||||
# Perform release builds of `wasmtime` and `libwasmtime.so`. Builds on
|
||||
# Windows/Mac/Linux, and artifacts are uploaded after the build is finished.
|
||||
# Note that we also run tests here to test exactly what we're deploying.
|
||||
build:
|
||||
name: Build wasmtime
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/actions/install-rust
|
||||
- uses: ./.github/actions/binary-compatible-builds
|
||||
|
||||
# Install wasm32-wasi target in order to build wasi-common's integration
|
||||
# tests
|
||||
- run: rustup target add wasm32-wasi
|
||||
|
||||
# Build `wasmtime` and executables
|
||||
- run: $CENTOS cargo build --release --bin wasmtime --bin wasm2obj
|
||||
shell: bash
|
||||
# Build `libwasmtime.so`
|
||||
- run: $CENTOS cargo build --release --manifest-path crates/c-api/Cargo.toml
|
||||
shell: bash
|
||||
# Test what we just built
|
||||
- run: $CENTOS cargo test --features test_programs --release --all --exclude lightbeam --exclude wasmtime --exclude wasmtime-c-api --exclude wasmtime-fuzzing -- --skip test_debug_dwarf_
|
||||
shell: bash
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
|
||||
# Postprocess the macOS dylib a bit to have a more reasonable `LC_ID_DYLIB`
|
||||
# directive than the default one that comes out of the linker when typically
|
||||
# doing `cargo build`. For more info see #984
|
||||
- run: install_name_tool -id "@rpath/libwasmtime.dylib" target/release/libwasmtime.dylib
|
||||
if: matrix.os == 'macos-latest'
|
||||
|
||||
# ... and now perform some goop to move all the relevant artifacts into
|
||||
# something that we'll upload from this action.
|
||||
|
||||
- run: mkdir dist
|
||||
shell: bash
|
||||
|
||||
# Move binaries to dist folder
|
||||
- run: cp target/release/{wasmtime,wasm2obj} dist
|
||||
if: matrix.os != 'windows-latest'
|
||||
- run: cp target/release/{wasmtime,wasm2obj}.exe dist
|
||||
shell: bash
|
||||
if: matrix.os == 'windows-latest'
|
||||
|
||||
# Move libwasmtime dylib to dist folder
|
||||
- run: cp target/release/libwasmtime.{so,a} dist
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
- run: cp target/release/libwasmtime.{dylib,a} dist
|
||||
if: matrix.os == 'macos-latest'
|
||||
- run: cp target/release/wasmtime.{dll,lib} dist
|
||||
shell: bash
|
||||
if: matrix.os == 'windows-latest'
|
||||
|
||||
# Make a Windows MSI installer if we're on Windows
|
||||
- run: |
|
||||
export WT_VERSION=`cat Cargo.toml | sed -n 's/^version = "\([^"]*\)".*/\1/p'`
|
||||
"$WIX/bin/candle" -arch x64 -out target/wasmtime.wixobj ci/wasmtime.wxs
|
||||
"$WIX/bin/light" -out dist/installer.msi target/wasmtime.wixobj -ext WixUtilExtension
|
||||
rm dist/installer.wixpdb
|
||||
shell: bash
|
||||
if: matrix.os == 'windows-latest'
|
||||
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: bins-${{ matrix.os }}
|
||||
path: dist
|
||||
|
||||
# Build and test the .NET bindings
|
||||
dotnet:
|
||||
name: Test Wasmtime for .NET bindings
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
build: [linux-debug, linux-release, macos-debug, macos-release, windows-debug, windows-release]
|
||||
include:
|
||||
- build: linux-debug
|
||||
os: ubuntu-latest
|
||||
config: debug
|
||||
- build: linux-release
|
||||
os: ubuntu-latest
|
||||
config: release
|
||||
- build: macos-debug
|
||||
os: macos-latest
|
||||
config: debug
|
||||
- build: macos-release
|
||||
os: macos-latest
|
||||
config: release
|
||||
- build: windows-debug
|
||||
os: windows-latest
|
||||
config: debug
|
||||
- build: windows-release
|
||||
os: windows-latest
|
||||
config: release
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/actions/install-rust
|
||||
- uses: ./.github/actions/binary-compatible-builds
|
||||
- run: rustup target add wasm32-wasi
|
||||
- uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '3.0.101'
|
||||
- name: Test
|
||||
run: |
|
||||
cd crates/misc/dotnet/tests
|
||||
dotnet test -c ${{ matrix.config }}
|
||||
- name: Create package
|
||||
run: |
|
||||
cd crates/misc/dotnet/src
|
||||
dotnet pack -c ${{ matrix.config }}
|
||||
if: matrix.os == 'macos-latest' # Currently the pack target only supports macOS
|
||||
|
||||
# Consumes all published artifacts from all the previous build steps, creates
|
||||
# a bunch of tarballs for all of them, and then publishes the tarballs
|
||||
# themselves as an artifact (for inspection) and then optionally creates
|
||||
# github releases and/or tags for pushes.
|
||||
publish:
|
||||
name: Publish
|
||||
needs: [doc_book, doc_api, wheels, build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
- run: rustup update stable && rustup default stable
|
||||
|
||||
# Download all the artifacts that we'll be publishing. Should keep an eye on
|
||||
# the `download-artifact` repository to see if we can ever get something
|
||||
# like "download all artifacts" or "download this list of artifacts"
|
||||
- name: Download book
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: doc-book
|
||||
- name: Download API docs
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: doc-api
|
||||
- name: Download macOS Wheel
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: wheels-macos-latest
|
||||
- name: Download macOS binaries
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: bins-macos-latest
|
||||
- name: Download Linux Wheel
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: wheels-ubuntu-latest
|
||||
- name: Download Linux binaries
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: bins-ubuntu-latest
|
||||
- name: Download Windows Wheel
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: wheels-windows-latest
|
||||
- name: Download Windows binaries
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: bins-windows-latest
|
||||
|
||||
- name: Assemble gh-pages
|
||||
run: |
|
||||
mv doc-book gh-pages
|
||||
mv doc-api gh-pages/api
|
||||
|
||||
# If this is a push to the master branch push to the `gh-pages` using a
|
||||
# deploy key. Note that a deploy key is necessary for now because otherwise
|
||||
# using the default token for github actions doesn't actually trigger a page
|
||||
# rebuild.
|
||||
- name: Push to gh-pages
|
||||
run: curl -LsSf https://git.io/fhJ8n | rustc - && (cd gh-pages && ../rust_out)
|
||||
env:
|
||||
GITHUB_DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
|
||||
BUILD_REPOSITORY_ID: ${{ github.repository }}
|
||||
BUILD_SOURCEVERSION: ${{ github.sha }}
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
|
||||
- name: Calculate tag name
|
||||
run: |
|
||||
name=dev
|
||||
if [[ $GITHUB_REF == refs/tags* ]]; then
|
||||
name=${GITHUB_REF:10}
|
||||
fi
|
||||
echo ::set-output name=val::$name
|
||||
echo ::set-env name=TAG::$name
|
||||
id: tagname
|
||||
|
||||
# Assemble all the build artifacts into tarballs and zip archives.
|
||||
- name: Assemble tarballs
|
||||
run: |
|
||||
./ci/build-tarballs.sh x86_64-linux ubuntu-latest
|
||||
./ci/build-tarballs.sh x86_64-windows windows-latest .exe
|
||||
./ci/build-tarballs.sh x86_64-macos macos-latest
|
||||
|
||||
# Upload all assembled tarballs as an artifact of the github action run, so
|
||||
# that way even PRs can inspect the output.
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: tarballs
|
||||
path: dist
|
||||
|
||||
# The action 'pypa/gh-action-pypi-publish' will try to upload all files in the
|
||||
# dist/ folder. This folder also contains non-package files, and therefore the
|
||||
# action fails.
|
||||
#
|
||||
# To prevent the action from failing all .whl files are copied into a new
|
||||
# directory.
|
||||
- run: |
|
||||
mkdir -p tmp/whl
|
||||
find dist/ -name '*.whl' -type f -exec cp '{}' tmp/whl -v \;
|
||||
|
||||
- name: Publish Python wheels on Pypi
|
||||
uses: pypa/gh-action-pypi-publish@37e305e7413032d8422456179fee28fac7d25187
|
||||
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.pypi_password }}
|
||||
packages_dir: tmp/whl
|
||||
|
||||
# ... and if this was an actual push (tag or `master`) then we publish a
|
||||
# new release. This'll automatically publish a tag release or update `dev`
|
||||
# with this `sha`
|
||||
- name: Publish Release
|
||||
uses: ./.github/actions/github-release
|
||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags'))
|
||||
with:
|
||||
files: "dist/*"
|
||||
name: ${{ steps.tagname.outputs.val }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
*.bk
|
||||
*.swp
|
||||
*.swo
|
||||
*.swx
|
||||
tags
|
||||
target
|
||||
.*.rustfmt
|
||||
cranelift.dbg*
|
||||
rusty-tags.*
|
||||
*~
|
||||
\#*\#
|
||||
docs/book
|
||||
.vscode/
|
||||
9
.gitmodules
vendored
Normal file
9
.gitmodules
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
[submodule "spec_testsuite"]
|
||||
path = tests/spec_testsuite
|
||||
url = https://github.com/WebAssembly/testsuite
|
||||
[submodule "crates/c-api/examples/wasm-c-api"]
|
||||
path = crates/c-api/examples/wasm-c-api
|
||||
url = https://github.com/WebAssembly/wasm-c-api
|
||||
[submodule "crates/wasi-common/WASI"]
|
||||
path = crates/wasi-common/wig/WASI
|
||||
url = https://github.com/WebAssembly/WASI
|
||||
1
.rustfmt.toml
Normal file
1
.rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
||||
# This file tells tools we use rustfmt. We use the default settings.
|
||||
49
CODE_OF_CONDUCT.md
Normal file
49
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
*Note*: this Code of Conduct pertains to individuals' behavior. Please also see the [Organizational Code of Conduct][OCoC].
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the Bytecode Alliance CoC team at [report@bytecodealliance.org](mailto:report@bytecodealliance.org). The CoC team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The CoC team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the Bytecode Alliance's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[OCoC]: https://github.com/bytecodealliance/wasmtime/blob/master/ORG_CODE_OF_CONDUCT.md
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[version]: https://www.contributor-covenant.org/version/1/4/
|
||||
13
CONTRIBUTING.md
Normal file
13
CONTRIBUTING.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Contributing to Wasmtime
|
||||
|
||||
Wasmtime is a [Bytecode Alliance] project, and follows the Bytecode Alliance's [Code of Conduct] and [Organizational Code of Conduct].
|
||||
|
||||
Wasmtime follows the same development style as Cranelift, so check out
|
||||
[Cranelift's CONTRIBUTING.md]. Of course, for Wasmtime-specific issues, please
|
||||
use the [Wasmtime issue tracker].
|
||||
|
||||
[Bytecode Alliance]: https://bytecodealliance.org/
|
||||
[Code of Conduct]: CODE_OF_CONDUCT.md
|
||||
[Organizational Code of Conduct]: ORG_CODE_OF_CONDUCT.md
|
||||
[Cranelift's CONTRIBUTING.md]: https://github.com/bytecodealliance/cranelift/blob/master/CONTRIBUTING.md
|
||||
[Wasmtime issue tracker]: https://github.com/bytecodealliance/wasmtime/issues/new
|
||||
2376
Cargo.lock
generated
Normal file
2376
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
83
Cargo.toml
Normal file
83
Cargo.toml
Normal file
@@ -0,0 +1,83 @@
|
||||
[package]
|
||||
name = "wasmtime-cli"
|
||||
version = "0.12.0"
|
||||
authors = ["The Wasmtime Project Developers"]
|
||||
description = "Command-line interface for Wasmtime"
|
||||
license = "Apache-2.0 WITH LLVM-exception"
|
||||
documentation = "https://cranelift.readthedocs.io/"
|
||||
categories = ["wasm"]
|
||||
keywords = ["webassembly", "wasm"]
|
||||
repository = "https://github.com/bytecodealliance/wasmtime"
|
||||
readme = "README.md"
|
||||
edition = "2018"
|
||||
default-run = "wasmtime"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[[bin]]
|
||||
name = "wasmtime"
|
||||
path = "src/bin/wasmtime.rs"
|
||||
doc = false
|
||||
|
||||
[dependencies]
|
||||
# Enable all supported architectures by default.
|
||||
wasmtime = { path = "crates/api" }
|
||||
wasmtime-debug = { path = "crates/debug" }
|
||||
wasmtime-environ = { path = "crates/environ" }
|
||||
wasmtime-interface-types = { path = "crates/interface-types" }
|
||||
wasmtime-jit = { path = "crates/jit" }
|
||||
wasmtime-obj = { path = "crates/obj" }
|
||||
wasmtime-profiling = { path = "crates/profiling" }
|
||||
wasmtime-wast = { path = "crates/wast" }
|
||||
wasmtime-wasi = { path = "crates/wasi" }
|
||||
wasi-common = { path = "crates/wasi-common" }
|
||||
structopt = { version = "0.3.5", features = ["color", "suggestions"] }
|
||||
faerie = "0.14.0"
|
||||
anyhow = "1.0.19"
|
||||
target-lexicon = { version = "0.10.0", default-features = false }
|
||||
pretty_env_logger = "0.3.0"
|
||||
file-per-thread-logger = "0.1.1"
|
||||
wat = "1.0.10"
|
||||
libc = "0.2.60"
|
||||
rayon = "1.2.1"
|
||||
wasm-webidl-bindings = "0.8"
|
||||
|
||||
[dev-dependencies]
|
||||
wasmtime-runtime = { path = "crates/runtime" }
|
||||
more-asserts = "0.2.1"
|
||||
# This feature requires the wasm32-wasi target be installed. It enables
|
||||
# wasm32-wasi integration tests. To enable, run
|
||||
# `cargo test --features test-programs`.
|
||||
test-programs = { path = "crates/test-programs" }
|
||||
tempfile = "3.1.0"
|
||||
filecheck = "0.4.0"
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.19"
|
||||
|
||||
[profile.release.build-override]
|
||||
opt-level = 0
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/fuzzing",
|
||||
"crates/misc/rust",
|
||||
"crates/misc/py",
|
||||
"crates/c-api",
|
||||
"fuzz",
|
||||
]
|
||||
|
||||
[features]
|
||||
lightbeam = [
|
||||
"wasmtime-environ/lightbeam",
|
||||
"wasmtime-jit/lightbeam",
|
||||
"wasmtime-wast/lightbeam",
|
||||
"wasmtime/lightbeam",
|
||||
]
|
||||
jitdump = ["wasmtime-profiling/jitdump"]
|
||||
test_programs = ["test-programs/test_programs"]
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "actively-developed" }
|
||||
220
LICENSE
Normal file
220
LICENSE
Normal file
@@ -0,0 +1,220 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
--- LLVM Exceptions to the Apache 2.0 License ----
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into an Object form of such source code, you
|
||||
may redistribute such embedded portions in such Object form without complying
|
||||
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
|
||||
|
||||
In addition, if you combine or link compiled forms of this Software with
|
||||
software that is licensed under the GPLv2 ("Combined Software") and if a
|
||||
court of competent jurisdiction determines that the patent provision (Section
|
||||
3), the indemnity provision (Section 9) or other Section of the License
|
||||
conflicts with the conditions of the GPLv2, you may retroactively and
|
||||
prospectively choose to deem waived or otherwise exclude such Section(s) of
|
||||
the License, but only in their entirety and only with respect to the Combined
|
||||
Software.
|
||||
|
||||
143
ORG_CODE_OF_CONDUCT.md
Normal file
143
ORG_CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# Bytecode Alliance Organizational Code of Conduct (OCoC)
|
||||
|
||||
*Note*: this Code of Conduct pertains to organizations' behavior. Please also see the [Individual Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
|
||||
## Preamble
|
||||
|
||||
The Bytecode Alliance (BA) welcomes involvement from organizations,
|
||||
including commercial organizations. This document is an
|
||||
*organizational* code of conduct, intended particularly to provide
|
||||
guidance to commercial organizations. It is distinct from the
|
||||
[Individual Code of Conduct (ICoC)](CODE_OF_CONDUCT.md), and does not
|
||||
replace the ICoC. This OCoC applies to any group of people acting in
|
||||
concert as a BA member or as a participant in BA activities, whether
|
||||
or not that group is formally incorporated in some jurisdiction.
|
||||
|
||||
The code of conduct described below is not a set of rigid rules, and
|
||||
we did not write it to encompass every conceivable scenario that might
|
||||
arise. For example, it is theoretically possible there would be times
|
||||
when asserting patents is in the best interest of the BA community as
|
||||
a whole. In such instances, consult with the BA, strive for
|
||||
consensus, and interpret these rules with an intent that is generous
|
||||
to the community the BA serves.
|
||||
|
||||
While we may revise these guidelines from time to time based on
|
||||
real-world experience, overall they are based on a simple principle:
|
||||
|
||||
*Bytecode Alliance members should observe the distinction between
|
||||
public community functions and private functions — especially
|
||||
commercial ones — and should ensure that the latter support, or at
|
||||
least do not harm, the former.*
|
||||
|
||||
## Guidelines
|
||||
|
||||
* **Do not cause confusion about Wasm standards or interoperability.**
|
||||
|
||||
Having an interoperable WebAssembly core is a high priority for
|
||||
the BA, and members should strive to preserve that core. It is fine
|
||||
to develop additional non-standard features or APIs, but they
|
||||
should always be clearly distinguished from the core interoperable
|
||||
Wasm.
|
||||
|
||||
Treat the WebAssembly name and any BA-associated names with
|
||||
respect, and follow BA trademark and branding guidelines. If you
|
||||
distribute a customized version of software originally produced by
|
||||
the BA, or if you build a product or service using BA-derived
|
||||
software, use names that clearly distinguish your work from the
|
||||
original. (You should still provide proper attribution to the
|
||||
original, of course, wherever such attribution would normally be
|
||||
given.)
|
||||
|
||||
Further, do not use the WebAssembly name or BA-associated names in
|
||||
other public namespaces in ways that could cause confusion, e.g.,
|
||||
in company names, names of commercial service offerings, domain
|
||||
names, publicly-visible social media accounts or online service
|
||||
accounts, etc. It may sometimes be reasonable, however, to
|
||||
register such a name in a new namespace and then immediately donate
|
||||
control of that account to the BA, because that would help the project
|
||||
maintain its identity.
|
||||
|
||||
For further guidance, see the BA Trademark and Branding Policy
|
||||
[TODO: create policy, then insert link].
|
||||
|
||||
* **Do not restrict contributors.** If your company requires
|
||||
employees or contractors to sign non-compete agreements, those
|
||||
agreements must not prevent people from participating in the BA or
|
||||
contributing to related projects.
|
||||
|
||||
This does not mean that all non-compete agreements are incompatible
|
||||
with this code of conduct. For example, a company may restrict an
|
||||
employee's ability to solicit the company's customers. However, an
|
||||
agreement must not block any form of technical or social
|
||||
participation in BA activities, including but not limited to the
|
||||
implementation of particular features.
|
||||
|
||||
The accumulation of experience and expertise in individual persons,
|
||||
who are ultimately free to direct their energy and attention as
|
||||
they decide, is one of the most important drivers of progress in
|
||||
open source projects. A company that limits this freedom may hinder
|
||||
the success of the BA's efforts.
|
||||
|
||||
* **Do not use patents as offensive weapons.** If any BA participant
|
||||
prevents the adoption or development of BA technologies by
|
||||
asserting its patents, that undermines the purpose of the
|
||||
coalition. The collaboration fostered by the BA cannot include
|
||||
members who act to undermine its work.
|
||||
|
||||
* **Practice responsible disclosure** for security vulnerabilities.
|
||||
Use designated, non-public reporting channels to disclose technical
|
||||
vulnerabilities, and give the project a reasonable period to
|
||||
respond, remediate, and patch. [TODO: optionally include the
|
||||
security vulnerability reporting URL here.]
|
||||
|
||||
Vulnerability reporters may patch their company's own offerings, as
|
||||
long as that patching does not significantly delay the reporting of
|
||||
the vulnerability. Vulnerability information should never be used
|
||||
for unilateral commercial advantage. Vendors may legitimately
|
||||
compete on the speed and reliability with which they deploy
|
||||
security fixes, but withholding vulnerability information damages
|
||||
everyone in the long run by risking harm to the BA project's
|
||||
reputation and to the security of all users.
|
||||
|
||||
* **Respect the letter and spirit of open source practice.** While
|
||||
there is not space to list here all possible aspects of standard
|
||||
open source practice, some examples will help show what we mean:
|
||||
|
||||
* Abide by all applicable open source license terms. Do not engage
|
||||
in copyright violation or misattribution of any kind.
|
||||
|
||||
* Do not claim others' ideas or designs as your own.
|
||||
|
||||
* When others engage in publicly visible work (e.g., an upcoming
|
||||
demo that is coordinated in a public issue tracker), do not
|
||||
unilaterally announce early releases or early demonstrations of
|
||||
that work ahead of their schedule in order to secure private
|
||||
advantage (such as marketplace advantage) for yourself.
|
||||
|
||||
The BA reserves the right to determine what constitutes good open
|
||||
source practices and to take action as it deems appropriate to
|
||||
encourage, and if necessary enforce, such practices.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of organizational behavior in violation of the OCoC may
|
||||
be reported by contacting the Bytecode Alliance CoC team at
|
||||
[report@bytecodealliance.org](mailto:report@bytecodealliance.org). The
|
||||
CoC team will review and investigate all complaints, and will respond
|
||||
in a way that it deems appropriate to the circumstances. The CoC team
|
||||
is obligated to maintain confidentiality with regard to the reporter of
|
||||
an incident. Further details of specific enforcement policies may be
|
||||
posted separately.
|
||||
|
||||
When the BA deems an organization in violation of this OCoC, the BA
|
||||
will, at its sole discretion, determine what action to take. The BA
|
||||
will decide what type, degree, and duration of corrective action is
|
||||
needed, if any, before a violating organization can be considered for
|
||||
membership (if it was not already a member) or can have its membership
|
||||
reinstated (if it was a member and the BA canceled its membership due
|
||||
to the violation).
|
||||
|
||||
In practice, the BA's first approach will be to start a conversation,
|
||||
with punitive enforcement used only as a last resort. Violations
|
||||
often turn out to be unintentional and swiftly correctable with all
|
||||
parties acting in good faith.
|
||||
99
README.md
Normal file
99
README.md
Normal file
@@ -0,0 +1,99 @@
|
||||
<div align="center">
|
||||
<h1><code>wasmtime</code></h1>
|
||||
|
||||
<p>
|
||||
<strong>A standalone runtime for
|
||||
<a href="https://webassembly.org/">WebAssembly</a></strong>
|
||||
</p>
|
||||
|
||||
<strong>A <a href="https://bytecodealliance.org/">Bytecode Alliance</a> project</strong>
|
||||
|
||||
<p>
|
||||
<a href="https://github.com/bytecodealliance/wasmtime/actions?query=workflow%3ACI"><img src="https://github.com/bytecodealliance/wasmtime/workflows/CI/badge.svg" alt="build status" /></a>
|
||||
<a href="https://bytecodealliance.zulipchat.com/#narrow/stream/217126-wasmtime"><img src="https://img.shields.io/badge/zulip-join_chat-brightgreen.svg" alt="zulip chat" /></a>
|
||||
<img src="https://img.shields.io/badge/rustc-1.37+-green.svg" alt="min rustc" />
|
||||
</p>
|
||||
|
||||
<h3>
|
||||
<a href="https://bytecodealliance.github.io/wasmtime/">Guide</a>
|
||||
<span> | </span>
|
||||
<a href="https://bytecodealliance.github.io/wasmtime/contributing.html">Contributing</a>
|
||||
<span> | </span>
|
||||
<a href="https://wasmtime.dev/">Website</a>
|
||||
<span> | </span>
|
||||
<a href="https://bytecodealliance.zulipchat.com/#narrow/stream/217126-wasmtime">Chat</a>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
## Installation
|
||||
|
||||
The Wasmtime CLI can be installed on Linux and macOS with a small install
|
||||
script:
|
||||
|
||||
```sh
|
||||
$ curl https://wasmtime.dev/install.sh -sSf | bash
|
||||
```
|
||||
|
||||
Windows or otherwise interested users can download installers and
|
||||
binaries directly from the [GitHub
|
||||
Releases](https://github.com/bytecodealliance/wasmtime/releases) page.
|
||||
|
||||
## Example
|
||||
|
||||
If you've got the [Rust compiler
|
||||
installed](https://www.rust-lang.org/tools/install) then you can take some Rust
|
||||
source code:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
and compile/run it with:
|
||||
|
||||
```sh
|
||||
$ rustup target add wasm32-wasi
|
||||
$ rustc hello.rs --target wasm32-wasi
|
||||
$ wasmtime hello.wasm
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
* **Lightweight**. Wasmtime is a standalone runtime for WebAssembly that scales
|
||||
with your needs. It fits on tiny chips as well as makes use of huge servers.
|
||||
Wasmtime can be embedded into almost any application too.
|
||||
|
||||
* **Fast**. Wasmtime is built on the optimizing Cranelift code generator to
|
||||
quickly generate high-quality machine code at runtime.
|
||||
|
||||
* **Configurable**. Whether you need to precompile your wasm ahead of time,
|
||||
generate code blazingly fast with Lightbeam, or interpret it at runtime,
|
||||
Wasmtime has you covered for all your wasm-executing needs.
|
||||
|
||||
* **WASI**. Wasmtime supports a rich set of APIs for interacting with the host
|
||||
environment through the [WASI standard](https://wasi.dev).
|
||||
|
||||
* **Standards Compliant**. Wasmtime passes the [official WebAssembly test
|
||||
suite](https://github.com/WebAssembly/testsuite), implements the [official C
|
||||
API of wasm](https://github.com/WebAssembly/wasm-c-api), and implements
|
||||
[future proposals to WebAssembly](https://github.com/WebAssembly/proposals) as
|
||||
well. Wasmtime developers are intimately engaged with the WebAssembly
|
||||
standards process all along the way too.
|
||||
|
||||
## Documentation
|
||||
|
||||
[📚 Read the Wasmtime guide here! 📚][guide]
|
||||
|
||||
The [wasmtime guide][guide] is the best starting point to learn about what
|
||||
Wasmtime can do for you or help answer your questions about Wasmtime. If you're
|
||||
curious in contributing to Wasmtime, [it can also help you do
|
||||
that][contributing]!.
|
||||
|
||||
[contributing]: https://bytecodealliance.github.io/wasmtime/contributing.html
|
||||
[guide]: https://bytecodealliance.github.io/wasmtime
|
||||
|
||||
---
|
||||
|
||||
It's Wasmtime.
|
||||
50
RELEASES.md
Normal file
50
RELEASES.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Wasmtime Releases
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
## 0.12.0
|
||||
|
||||
Released 2020-02-26.
|
||||
|
||||
### Added
|
||||
|
||||
* Support for the [WebAssembly text annotations proposal][annotations-proposal]
|
||||
has been added.
|
||||
[#998](https://github.com/bytecodealliance/wasmtime/pull/998)
|
||||
|
||||
* An initial C API for instantiating WASI modules has been added.
|
||||
[#977](https://github.com/bytecodealliance/wasmtime/pull/977)
|
||||
|
||||
* A new suite of `Func::getN` functions have been added to the `wasmtime` API to
|
||||
call statically-known function signatures in a highly optimized fashion.
|
||||
[#955](https://github.com/bytecodealliance/wasmtime/pull/955)
|
||||
|
||||
* Initial support for profiling JIT code through perf jitdump has been added.
|
||||
[#360](https://github.com/bytecodealliance/wasmtime/pull/360)
|
||||
|
||||
* More CLI flags corresponding to proposed WebAssembly features have been added.
|
||||
[#917](https://github.com/bytecodealliance/wasmtime/pull/917)
|
||||
|
||||
[annotations-proposal]: https://github.com/webassembly/annotations
|
||||
|
||||
### Changed
|
||||
|
||||
* The `wasmtime` CLI as well as embedding API will optimize WebAssembly code by
|
||||
default now.
|
||||
[#973](https://github.com/bytecodealliance/wasmtime/pull/973)
|
||||
[#988](https://github.com/bytecodealliance/wasmtime/pull/988)
|
||||
|
||||
* The `verifier` pass in Cranelift is now no longer run by default when using
|
||||
the embedding API.
|
||||
[#882](https://github.com/bytecodealliance/wasmtime/pull/882)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Code caching now accurately accounts for optimization levels, ensuring that if
|
||||
you ask for optimized code you're not accidentally handed unoptimized code
|
||||
from the cache.
|
||||
[#974](https://github.com/bytecodealliance/wasmtime/pull/974)
|
||||
|
||||
* Automated releases for tags should be up and running again, along with
|
||||
automatic publication of the `wasmtime` Python package.
|
||||
[#971](https://github.com/bytecodealliance/wasmtime/pull/971)
|
||||
29
SECURITY.md
Normal file
29
SECURITY.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Security Policy
|
||||
|
||||
Building secure foundations for software development is at the core of what we do in the Bytecode Alliance. Contributions of external security researchers are a vital part of that.
|
||||
|
||||
## Scope
|
||||
|
||||
If you believe you've found a security issue in any website, service, or software owned or operated by the Bytecode Alliance, we encourage you to notify us.
|
||||
|
||||
## How to Submit a Report
|
||||
|
||||
To submit a vulnerability report to the Bytecode Alliance, please contact us at [security@bytecodealliance.org](mailto:security@bytecodealliance.org). Your submission will be reviewed and validated by a member of our security team.
|
||||
|
||||
## Safe Harbor
|
||||
|
||||
The Bytecode Alliance supports safe harbor for security researchers who:
|
||||
|
||||
* Make a good faith effort to avoid privacy violations, destruction of data, and interruption or degradation of our services.
|
||||
* Only interact with accounts you own or with explicit permission of the account holder. If you do encounter Personally Identifiable Information (PII) contact us immediately, do not proceed with access, and immediately purge any local information.
|
||||
* Provide us with a reasonable amount of time to resolve vulnerabilities prior to any disclosure to the public or a third-party.
|
||||
|
||||
We will consider activities conducted consistent with this policy to constitute "authorized" conduct and will not pursue civil action or initiate a complaint to law enforcement. We will help to the extent we can if legal action is initiated by a third party against you.
|
||||
|
||||
Please submit a report to us before engaging in conduct that may be inconsistent with or unaddressed by this policy.
|
||||
|
||||
## Preferences
|
||||
|
||||
* Please provide detailed reports with reproducible steps and a clearly defined impact.
|
||||
* Submit one vulnerability per report.
|
||||
* Social engineering (e.g. phishing, vishing, smishing) is prohibited.
|
||||
211
build.rs
Normal file
211
build.rs
Normal file
@@ -0,0 +1,211 @@
|
||||
//! Build program to generate a program which runs all the testsuites.
|
||||
//!
|
||||
//! By generating a separate `#[test]` test for each file, we allow cargo test
|
||||
//! to automatically run the files in parallel.
|
||||
|
||||
use anyhow::Context;
|
||||
use std::env;
|
||||
use std::fmt::Write;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
let out_dir = PathBuf::from(
|
||||
env::var_os("OUT_DIR").expect("The OUT_DIR environment variable must be set"),
|
||||
);
|
||||
let mut out = String::new();
|
||||
|
||||
for strategy in &[
|
||||
"Cranelift",
|
||||
#[cfg(feature = "lightbeam")]
|
||||
"Lightbeam",
|
||||
] {
|
||||
writeln!(out, "#[cfg(test)]")?;
|
||||
writeln!(out, "#[allow(non_snake_case)]")?;
|
||||
writeln!(out, "mod {} {{", strategy)?;
|
||||
|
||||
with_test_module(&mut out, "misc", |out| {
|
||||
test_directory(out, "tests/misc_testsuite", strategy)?;
|
||||
test_directory_module(out, "tests/misc_testsuite/bulk-memory-operations", strategy)?;
|
||||
test_directory_module(out, "tests/misc_testsuite/reference-types", strategy)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
with_test_module(&mut out, "spec", |out| {
|
||||
let spec_tests = test_directory(out, "tests/spec_testsuite", strategy)?;
|
||||
// Skip running spec_testsuite tests if the submodule isn't checked
|
||||
// out.
|
||||
if spec_tests > 0 {
|
||||
test_directory_module(out, "tests/spec_testsuite/proposals/simd", strategy)?;
|
||||
test_directory_module(out, "tests/spec_testsuite/proposals/multi-value", strategy)?;
|
||||
test_directory_module(
|
||||
out,
|
||||
"tests/spec_testsuite/proposals/reference-types",
|
||||
strategy,
|
||||
)?;
|
||||
test_directory_module(
|
||||
out,
|
||||
"tests/spec_testsuite/proposals/bulk-memory-operations",
|
||||
strategy,
|
||||
)?;
|
||||
} else {
|
||||
println!(
|
||||
"cargo:warning=The spec testsuite is disabled. To enable, run `git submodule \
|
||||
update --remote`."
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
writeln!(out, "}}")?;
|
||||
}
|
||||
|
||||
// Write out our auto-generated tests and opportunistically format them with
|
||||
// `rustfmt` if it's installed.
|
||||
let output = out_dir.join("wast_testsuite_tests.rs");
|
||||
fs::write(&output, out)?;
|
||||
drop(Command::new("rustfmt").arg(&output).status());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_directory_module(
|
||||
out: &mut String,
|
||||
path: impl AsRef<Path>,
|
||||
strategy: &str,
|
||||
) -> anyhow::Result<usize> {
|
||||
let path = path.as_ref();
|
||||
let testsuite = &extract_name(path);
|
||||
with_test_module(out, testsuite, |out| test_directory(out, path, strategy))
|
||||
}
|
||||
|
||||
fn test_directory(
|
||||
out: &mut String,
|
||||
path: impl AsRef<Path>,
|
||||
strategy: &str,
|
||||
) -> anyhow::Result<usize> {
|
||||
let path = path.as_ref();
|
||||
let mut dir_entries: Vec<_> = path
|
||||
.read_dir()
|
||||
.context(format!("failed to read {:?}", path))?
|
||||
.map(|r| r.expect("reading testsuite directory entry"))
|
||||
.filter_map(|dir_entry| {
|
||||
let p = dir_entry.path();
|
||||
let ext = p.extension()?;
|
||||
// Only look at wast files.
|
||||
if ext != "wast" {
|
||||
return None;
|
||||
}
|
||||
// Ignore files starting with `.`, which could be editor temporary files
|
||||
if p.file_stem()?.to_str()?.starts_with(".") {
|
||||
return None;
|
||||
}
|
||||
Some(p)
|
||||
})
|
||||
.collect();
|
||||
|
||||
dir_entries.sort();
|
||||
|
||||
let testsuite = &extract_name(path);
|
||||
for entry in dir_entries.iter() {
|
||||
write_testsuite_tests(out, entry, testsuite, strategy)?;
|
||||
}
|
||||
|
||||
Ok(dir_entries.len())
|
||||
}
|
||||
|
||||
/// Extract a valid Rust identifier from the stem of a path.
|
||||
fn extract_name(path: impl AsRef<Path>) -> String {
|
||||
path.as_ref()
|
||||
.file_stem()
|
||||
.expect("filename should have a stem")
|
||||
.to_str()
|
||||
.expect("filename should be representable as a string")
|
||||
.replace("-", "_")
|
||||
.replace("/", "_")
|
||||
}
|
||||
|
||||
fn with_test_module<T>(
|
||||
out: &mut String,
|
||||
testsuite: &str,
|
||||
f: impl FnOnce(&mut String) -> anyhow::Result<T>,
|
||||
) -> anyhow::Result<T> {
|
||||
out.push_str("mod ");
|
||||
out.push_str(testsuite);
|
||||
out.push_str(" {\n");
|
||||
|
||||
let result = f(out)?;
|
||||
|
||||
out.push_str("}\n");
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn write_testsuite_tests(
|
||||
out: &mut String,
|
||||
path: impl AsRef<Path>,
|
||||
testsuite: &str,
|
||||
strategy: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
let path = path.as_ref();
|
||||
let testname = extract_name(path);
|
||||
|
||||
writeln!(out, "#[test]")?;
|
||||
if ignore(testsuite, &testname, strategy) {
|
||||
writeln!(out, "#[ignore]")?;
|
||||
}
|
||||
writeln!(out, "fn r#{}() -> anyhow::Result<()> {{", &testname)?;
|
||||
writeln!(
|
||||
out,
|
||||
"crate::run_wast(r#\"{}\"#, crate::Strategy::{})",
|
||||
path.display(),
|
||||
strategy
|
||||
)?;
|
||||
writeln!(out, "}}")?;
|
||||
writeln!(out)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ignore tests that aren't supported yet.
|
||||
fn ignore(testsuite: &str, testname: &str, strategy: &str) -> bool {
|
||||
match strategy {
|
||||
#[cfg(feature = "lightbeam")]
|
||||
"Lightbeam" => match (testsuite, testname) {
|
||||
("simd", _) => return true,
|
||||
("multi_value", _) => return true,
|
||||
("reference_types", _) => return true,
|
||||
("bulk_memory_operations", _) => return true,
|
||||
// Lightbeam doesn't support float arguments on the stack.
|
||||
("spec_testsuite", "call") => return true,
|
||||
_ => (),
|
||||
},
|
||||
"Cranelift" => match (testsuite, testname) {
|
||||
("simd", "simd_bit_shift") => return true, // FIXME Unsupported feature: proposed SIMD operator I8x16Shl
|
||||
("simd", "simd_conversions") => return true, // FIXME Unsupported feature: proposed SIMD operator I16x8NarrowI32x4S
|
||||
("simd", "simd_f32x4") => return true, // FIXME expected V128(F32x4([CanonicalNan, CanonicalNan, Value(Float32 { bits: 0 }), Value(Float32 { bits: 0 })])), got V128(18428729675200069632)
|
||||
("simd", "simd_f64x2") => return true, // FIXME expected V128(F64x2([Value(Float64 { bits: 9221120237041090560 }), Value(Float64 { bits: 0 })])), got V128(0)
|
||||
("simd", "simd_f64x2_arith") => return true, // FIXME expected V128(F64x2([Value(Float64 { bits: 9221120237041090560 }), Value(Float64 { bits: 13835058055282163712 })])), got V128(255211775190703847615975447847722024960)
|
||||
("simd", "simd_i64x2_arith") => return true, // FIXME Unsupported feature: proposed SIMD operator I64x2Mul
|
||||
("simd", "simd_lane") => return true, // FIXME invalid u8 number: constant out of range: (v8x16.shuffle -1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14...
|
||||
("simd", "simd_load") => return true, // FIXME Unsupported feature: proposed SIMD operator I8x16Shl
|
||||
("simd", "simd_load_extend") => return true, // FIXME Unsupported feature: proposed SIMD operator I16x8Load8x8S { memarg: MemoryImmediate { flags: 0, offset: 0 } }
|
||||
("simd", "simd_load_splat") => return true, // FIXME Unsupported feature: proposed SIMD operator V8x16LoadSplat { memarg: MemoryImmediate { flags: 0, offset: 0 } }
|
||||
("simd", "simd_splat") => return true, // FIXME Unsupported feature: proposed SIMD operator I8x16ShrS
|
||||
|
||||
// Still working on implementing these. See #929.
|
||||
("reference_types", "table_copy_on_imported_tables") => return false,
|
||||
("reference_types", _) => return true,
|
||||
|
||||
// Still working on implementing these. See #928
|
||||
("bulk_memory_operations", "bulk")
|
||||
| ("bulk_memory_operations", "data")
|
||||
| ("bulk_memory_operations", "memory_init")
|
||||
| ("bulk_memory_operations", "imports") => return true,
|
||||
|
||||
_ => {}
|
||||
},
|
||||
_ => panic!("unrecognized strategy"),
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
55
ci/build-tarballs.sh
Executable file
55
ci/build-tarballs.sh
Executable file
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
|
||||
# A small shell script invoked from CI on the final Linux builder which actually
|
||||
# assembles the release artifacts for a particular platform. This will take the
|
||||
# binary artifacts of previous builders and create associated tarballs to
|
||||
# publish to GitHub.
|
||||
#
|
||||
# The first argument of this is the "platform" name to put into the tarball, and
|
||||
# the second argument is the name of the github actions platform which is where
|
||||
# we source binaries from. The final third argument is ".exe" on Windows to
|
||||
# handle executable extensions right.
|
||||
|
||||
set -ex
|
||||
|
||||
platform=$1
|
||||
src=$2
|
||||
exe=$3
|
||||
|
||||
rm -rf tmp
|
||||
mkdir tmp
|
||||
mkdir -p dist
|
||||
|
||||
mktarball() {
|
||||
dir=$1
|
||||
if [ "$exe" = "" ]; then
|
||||
tar cJf dist/$dir.tar.xz -C tmp $dir
|
||||
else
|
||||
(cd tmp && zip -r ../dist/$dir.zip $dir)
|
||||
fi
|
||||
}
|
||||
|
||||
# Create the main tarball of binaries
|
||||
bin_pkgname=wasmtime-$TAG-$platform
|
||||
mkdir tmp/$bin_pkgname
|
||||
cp LICENSE README.md tmp/$bin_pkgname
|
||||
mv bins-$src/{wasmtime,wasm2obj}$exe tmp/$bin_pkgname
|
||||
chmod +x tmp/$bin_pkgname/{wasmtime,wasm2obj}$exe
|
||||
mktarball $bin_pkgname
|
||||
|
||||
if [ "$exe" = ".exe" ]; then
|
||||
mv bins-$src/installer.msi dist/$bin_pkgname.msi
|
||||
fi
|
||||
|
||||
# Create tarball of API libraries
|
||||
api_pkgname=wasmtime-$TAG-$platform-c-api
|
||||
mkdir tmp/$api_pkgname
|
||||
mkdir tmp/$api_pkgname/lib
|
||||
mkdir tmp/$api_pkgname/include
|
||||
cp LICENSE README.md tmp/$api_pkgname
|
||||
mv bins-$src/* tmp/$api_pkgname/lib
|
||||
cp crates/c-api/examples/wasm-c-api/include/wasm.h tmp/$api_pkgname/include
|
||||
mktarball $api_pkgname
|
||||
|
||||
# Move wheels to dist folder
|
||||
mv wheels-$src/* dist
|
||||
34
ci/setup_centos6_python3.sh
Normal file
34
ci/setup_centos6_python3.sh
Normal file
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
VERSION=${1:-3.7.3}
|
||||
|
||||
# Python 3.6 stands in our way -- nuking it
|
||||
yum erase -y rh-python36
|
||||
rm -rf /opt/rh/rh-python36
|
||||
|
||||
yum install -y gcc bzip2-devel libffi-devel zlib-devel
|
||||
|
||||
cd /usr/src/
|
||||
|
||||
# pip3.7 needs new openssl
|
||||
curl -O -L https://github.com/openssl/openssl/archive/OpenSSL_1_1_1c.tar.gz
|
||||
tar -zxvf OpenSSL_1_1_1c.tar.gz
|
||||
cd openssl-OpenSSL_1_1_1c
|
||||
./Configure shared zlib linux-x86_64
|
||||
make -sj4
|
||||
make install
|
||||
cd ..
|
||||
rm -rf openssl-OpenSSL_1_1_1c
|
||||
|
||||
# Fixing libssl.so.1.1: cannot open shared object file
|
||||
echo "/usr/local/lib64" >> /etc/ld.so.conf && ldconfig
|
||||
|
||||
curl -O -L https://www.python.org/ftp/python/${VERSION}/Python-${VERSION}.tgz
|
||||
tar xzf Python-${VERSION}.tgz
|
||||
cd Python-${VERSION}
|
||||
./configure
|
||||
make -sj4
|
||||
make install
|
||||
cd ..
|
||||
rm -rf Python-${VERSION}
|
||||
89
ci/wasmtime.wxs
Normal file
89
ci/wasmtime.wxs
Normal file
@@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
|
||||
<?if $(sys.BUILDARCH)="x64" ?>
|
||||
<?define ArchSuffix=" 64-bit" ?>
|
||||
<?else?>
|
||||
<?define ArchSuffix="" ?>
|
||||
<?endif?>
|
||||
|
||||
<?define ProductName="Wasmtime $(env.WT_VERSION) $(var.ArchSuffix))" ?>
|
||||
|
||||
<?define BaseRegKey="Software\[Manufacturer]\Wasmtime ($(sys.BUILDARCH))\$(env.WT_VERSION)" ?>
|
||||
|
||||
<Product Id="*" UpgradeCode="A00EBA3C-5C90-42DA-8176-3D46447D2211" Version="$(env.WT_VERSION)" Language="1033" Name="Wasmtime" Manufacturer="Crane Station">
|
||||
<Package InstallerVersion="300" Compressed="yes"/>
|
||||
<Media Id="1" Cabinet="wasmtime.cab" EmbedCab="yes" />
|
||||
|
||||
<InstallUISequence>
|
||||
<FindRelatedProducts After="AppSearch" />
|
||||
</InstallUISequence>
|
||||
<InstallExecuteSequence>
|
||||
<FindRelatedProducts After="AppSearch" />
|
||||
<RemoveExistingProducts Before="InstallInitialize" />
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="ProgramFiles64Folder">
|
||||
<Directory Id="INSTALLDIR" Name="Wasmtime">
|
||||
<Directory Id="BINDIR" Name="bin"/>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<!-- Record our install location -->
|
||||
<Component Id="InstallDir" Guid="*">
|
||||
<RegistryKey Root='HKLM' Key='$(var.BaseRegKey)'>
|
||||
<RegistryValue Type='string' Name='InstallDir' Value='[INSTALLDIR]'/>
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
|
||||
<!-- Add install dir to PATH -->
|
||||
<Component Id="PathEnvPerMachine" Guid="*">
|
||||
<!-- <Condition>ALLUSERS=1 OR (ALLUSERS=2 AND Privileged)</Condition> -->
|
||||
<RegistryValue Root="HKMU" Key="$(var.BaseRegKey)" Name="PathEnvPerMachine" Type="string" Value="1" KeyPath="yes" />
|
||||
<!-- [INSTALLDIR] contains trailing backslash -->
|
||||
<Environment Id="PathPerMachine" Name="PATH" Value="[INSTALLDIR]bin" Permanent="no" Part="last" Action="set" System="yes" />
|
||||
</Component>
|
||||
<Component Id="PathEnvPerUser" Guid="*">
|
||||
<!-- <Condition>ALLUSERS="" OR (ALLUSERS=2 AND (NOT Privileged))</Condition> -->
|
||||
<RegistryValue Root="HKMU" Key="$(var.BaseRegKey)" Name="PathEnvPerUser" Type="string" Value="1" KeyPath="yes" />
|
||||
<Environment Id="PathPerUser" Name="PATH" Value="[INSTALLDIR]bin" Permanent="no" Part="last" Action="set" System="no" />
|
||||
</Component>
|
||||
</Directory>
|
||||
|
||||
<DirectoryRef Id="INSTALLDIR">
|
||||
<Component Id="LICENSE" Guid="*">
|
||||
<File Id="LICENSE" Source="LICENSE" KeyPath="yes" Checksum="yes"/>
|
||||
</Component>
|
||||
<Component Id="README" Guid="*">
|
||||
<File Id="README.md" Source="README.md" KeyPath="yes" Checksum="yes"/>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<DirectoryRef Id="BINDIR">
|
||||
<Component Id="wasmtime.exe" Guid="*">
|
||||
<File Id="wasmtime.exe" Source="target\release\wasmtime.exe" KeyPath="yes" Checksum="yes"/>
|
||||
</Component>
|
||||
<Component Id="wasm2obj.exe" Guid="*">
|
||||
<File Id="wasm2obj.exe" Source="target\release\wasm2obj.exe" KeyPath="yes" Checksum="yes"/>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<Feature Id="InstallWasmtime" Title="Wasmtime and Wasm2obj" Level="1">
|
||||
<ComponentRef Id="wasmtime.exe" />
|
||||
<ComponentRef Id="wasm2obj.exe" />
|
||||
<ComponentRef Id="LICENSE" />
|
||||
<ComponentRef Id="README" />
|
||||
<ComponentRef Id="InstallDir" />
|
||||
</Feature>
|
||||
<Feature Id="AddToPath"
|
||||
Title="Add to PATH"
|
||||
Description="Add Wasmtime to PATH environment variable"
|
||||
Level="1"
|
||||
AllowAdvertise="no">
|
||||
<ComponentRef Id="PathEnvPerMachine" />
|
||||
<ComponentRef Id="PathEnvPerUser" />
|
||||
</Feature>
|
||||
<CustomActionRef Id="WixBroadcastEnvironmentChange" />
|
||||
</Product>
|
||||
</Wix>
|
||||
1
clippy.toml
Normal file
1
clippy.toml
Normal file
@@ -0,0 +1 @@
|
||||
doc-valid-idents = ["WebAssembly"]
|
||||
52
crates/api/Cargo.toml
Normal file
52
crates/api/Cargo.toml
Normal file
@@ -0,0 +1,52 @@
|
||||
[package]
|
||||
name = "wasmtime"
|
||||
version = "0.12.0"
|
||||
authors = ["The Wasmtime Project Developers"]
|
||||
description = "High-level API to expose the Wasmtime runtime"
|
||||
license = "Apache-2.0 WITH LLVM-exception"
|
||||
repository = "https://github.com/bytecodealliance/wasmtime"
|
||||
readme = "README.md"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
wasmtime-runtime = { path = "../runtime", version = "0.12.0" }
|
||||
wasmtime-environ = { path = "../environ", version = "0.12.0" }
|
||||
wasmtime-jit = { path = "../jit", version = "0.12.0" }
|
||||
wasmtime-profiling = { path = "../profiling", version = "0.12.0" }
|
||||
wasmparser = "0.51.2"
|
||||
target-lexicon = { version = "0.10.0", default-features = false }
|
||||
anyhow = "1.0.19"
|
||||
region = "2.0.0"
|
||||
libc = "0.2"
|
||||
cfg-if = "0.1.9"
|
||||
backtrace = "0.3.42"
|
||||
rustc-demangle = "0.1.16"
|
||||
lazy_static = "1.4"
|
||||
wat = { version = "1.0.10", optional = true }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = "0.3.7"
|
||||
|
||||
[dev-dependencies]
|
||||
# for wasmtime.rs
|
||||
wasi-common = { path = "../wasi-common", version = "0.12.0" }
|
||||
pretty_env_logger = "0.3.0"
|
||||
rayon = "1.2.1"
|
||||
file-per-thread-logger = "0.1.1"
|
||||
wat = "1.0.10"
|
||||
tempfile = "3.1"
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "actively-developed" }
|
||||
|
||||
[features]
|
||||
default = ['wat']
|
||||
|
||||
# Enables experimental support for the lightbeam codegen backend, an alternative
|
||||
# to cranelift. Requires Nightly Rust currently, and this is not enabled by
|
||||
# default.
|
||||
lightbeam = ["wasmtime-jit/lightbeam"]
|
||||
|
||||
[[test]]
|
||||
name = "host-segfault"
|
||||
harness = false
|
||||
220
crates/api/LICENSE
Normal file
220
crates/api/LICENSE
Normal file
@@ -0,0 +1,220 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
--- LLVM Exceptions to the Apache 2.0 License ----
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into an Object form of such source code, you
|
||||
may redistribute such embedded portions in such Object form without complying
|
||||
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
|
||||
|
||||
In addition, if you combine or link compiled forms of this Software with
|
||||
software that is licensed under the GPLv2 ("Combined Software") and if a
|
||||
court of competent jurisdiction determines that the patent provision (Section
|
||||
3), the indemnity provision (Section 9) or other Section of the License
|
||||
conflicts with the conditions of the GPLv2, you may retroactively and
|
||||
prospectively choose to deem waived or otherwise exclude such Section(s) of
|
||||
the License, but only in their entirety and only with respect to the Combined
|
||||
Software.
|
||||
|
||||
8
crates/api/README.md
Normal file
8
crates/api/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
## Wasmtime Embedding API
|
||||
|
||||
The `wasmtime` crate is an embedding API of the `wasmtime` WebAssembly runtime.
|
||||
This is intended to be used in Rust projects and provides a high-level API of
|
||||
working with WebAssembly modules.
|
||||
|
||||
If you're interested in embedding `wasmtime` in other languages, you may wish to
|
||||
take a look a the [C embedding API](../c-api) instead!
|
||||
60
crates/api/examples/gcd.rs
Normal file
60
crates/api/examples/gcd.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
//! Example of instantiating of the WebAssembly module and
|
||||
//! invoking its exported function.
|
||||
|
||||
use wasmtime::*;
|
||||
|
||||
const WAT: &str = r#"
|
||||
(module
|
||||
(func $gcd (param i32 i32) (result i32)
|
||||
(local i32)
|
||||
block ;; label = @1
|
||||
block ;; label = @2
|
||||
local.get 0
|
||||
br_if 0 (;@2;)
|
||||
local.get 1
|
||||
local.set 2
|
||||
br 1 (;@1;)
|
||||
end
|
||||
loop ;; label = @2
|
||||
local.get 1
|
||||
local.get 0
|
||||
local.tee 2
|
||||
i32.rem_u
|
||||
local.set 0
|
||||
local.get 2
|
||||
local.set 1
|
||||
local.get 0
|
||||
br_if 0 (;@2;)
|
||||
end
|
||||
end
|
||||
local.get 2
|
||||
)
|
||||
(export "gcd" (func $gcd))
|
||||
)
|
||||
"#;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
// Load our WebAssembly (parsed WAT in our case), and then load it into a
|
||||
// `Module` which is attached to a `Store` cache.
|
||||
let store = Store::default();
|
||||
let module = Module::new(&store, WAT)?;
|
||||
|
||||
// Find index of the `gcd` export.
|
||||
let gcd_index = module
|
||||
.exports()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, export)| export.name().to_string() == "gcd")
|
||||
.unwrap()
|
||||
.0;
|
||||
|
||||
// Instantiate the module.
|
||||
let instance = Instance::new(&module, &[])?;
|
||||
|
||||
// Invoke `gcd` export
|
||||
let gcd = instance.exports()[gcd_index].func().expect("gcd");
|
||||
let result = gcd.call(&[Val::from(6i32), Val::from(27i32)])?;
|
||||
|
||||
println!("{:?}", result);
|
||||
Ok(())
|
||||
}
|
||||
50
crates/api/examples/hello.rs
Normal file
50
crates/api/examples/hello.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
//! Translation of hello example
|
||||
|
||||
use anyhow::{ensure, Context as _, Result};
|
||||
use wasmtime::*;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// Configure the initial compilation environment, creating the global
|
||||
// `Store` structure. Note that you can also tweak configuration settings
|
||||
// with a `Config` and an `Engine` if desired.
|
||||
println!("Initializing...");
|
||||
let store = Store::default();
|
||||
|
||||
// Compile the wasm binary into an in-memory instance of a `Module`.
|
||||
println!("Compiling module...");
|
||||
let wat = r#"
|
||||
(module
|
||||
(func $hello (import "" "hello"))
|
||||
(func (export "run") (call $hello))
|
||||
)
|
||||
"#;
|
||||
let module = Module::new(&store, wat).context("> Error compiling module!")?;
|
||||
|
||||
// Here we handle the imports of the module, which in this case is our
|
||||
// `HelloCallback` type and its associated implementation of `Callback.
|
||||
println!("Creating callback...");
|
||||
let hello_func = Func::wrap0(&store, || {
|
||||
println!("Calling back...");
|
||||
println!("> Hello World!");
|
||||
});
|
||||
|
||||
// Once we've got that all set up we can then move to the instantiation
|
||||
// phase, pairing together a compiled module as well as a set of imports.
|
||||
// Note that this is where the wasm `start` function, if any, would run.
|
||||
println!("Instantiating module...");
|
||||
let imports = vec![hello_func.into()];
|
||||
let instance = Instance::new(&module, &imports).context("> Error instantiating module!")?;
|
||||
|
||||
// Next we poke around a bit to extract the `run` function from the module.
|
||||
println!("Extracting export...");
|
||||
let exports = instance.exports();
|
||||
ensure!(!exports.is_empty(), "> Error accessing exports!");
|
||||
let run_func = exports[0].func().context("> Error accessing exports!")?;
|
||||
|
||||
// And last but not least we can call it!
|
||||
println!("Calling export...");
|
||||
run_func.call(&[])?;
|
||||
|
||||
println!("Done.");
|
||||
Ok(())
|
||||
}
|
||||
160
crates/api/examples/memory.rs
Normal file
160
crates/api/examples/memory.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
//! Translation of the memory example
|
||||
|
||||
use anyhow::{bail, ensure, Context as _, Error};
|
||||
use wasmtime::*;
|
||||
|
||||
fn get_export_memory(exports: &[Extern], i: usize) -> Result<Memory, Error> {
|
||||
if exports.len() <= i {
|
||||
bail!("> Error accessing memory export {}!", i);
|
||||
}
|
||||
Ok(exports[i]
|
||||
.memory()
|
||||
.with_context(|| format!("> Error accessing memory export {}!", i))?
|
||||
.clone())
|
||||
}
|
||||
|
||||
fn get_export_func(exports: &[Extern], i: usize) -> Result<Func, Error> {
|
||||
if exports.len() <= i {
|
||||
bail!("> Error accessing function export {}!", i);
|
||||
}
|
||||
Ok(exports[i]
|
||||
.func()
|
||||
.with_context(|| format!("> Error accessing function export {}!", i))?
|
||||
.clone())
|
||||
}
|
||||
|
||||
macro_rules! check {
|
||||
($actual:expr, $expected:expr) => {
|
||||
if $actual != $expected {
|
||||
bail!("> Error on result, expected {}, got {}", $expected, $actual);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! check_ok {
|
||||
($func:expr, $($p:expr),*) => {
|
||||
if let Err(_) = $func.call(&[$($p.into()),*]) {
|
||||
bail!("> Error on result, expected return");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! check_trap {
|
||||
($func:expr, $($p:expr),*) => {
|
||||
if let Ok(_) = $func.call(&[$($p.into()),*]) {
|
||||
bail!("> Error on result, expected trap");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! call {
|
||||
($func:expr, $($p:expr),*) => {
|
||||
match $func.call(&[$($p.into()),*]) {
|
||||
Ok(result) => {
|
||||
let result: i32 = result[0].unwrap_i32();
|
||||
result
|
||||
}
|
||||
Err(_) => { bail!("> Error on result, expected return"); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
// Initialize.
|
||||
println!("Initializing...");
|
||||
let store = Store::default();
|
||||
|
||||
// Load binary.
|
||||
println!("Loading binary...");
|
||||
let wat = r#"
|
||||
(module
|
||||
(memory (export "memory") 2 3)
|
||||
|
||||
(func (export "size") (result i32) (memory.size))
|
||||
(func (export "load") (param i32) (result i32)
|
||||
(i32.load8_s (local.get 0))
|
||||
)
|
||||
(func (export "store") (param i32 i32)
|
||||
(i32.store8 (local.get 0) (local.get 1))
|
||||
)
|
||||
|
||||
(data (i32.const 0x1000) "\01\02\03\04")
|
||||
)
|
||||
"#;
|
||||
|
||||
// Compile.
|
||||
println!("Compiling module...");
|
||||
let module = Module::new(&store, &wat).context("> Error compiling module!")?;
|
||||
|
||||
// Instantiate.
|
||||
println!("Instantiating module...");
|
||||
let instance = Instance::new(&module, &[]).context("> Error instantiating module!")?;
|
||||
|
||||
// Extract export.
|
||||
println!("Extracting export...");
|
||||
let exports = instance.exports();
|
||||
ensure!(!exports.is_empty(), "> Error accessing exports!");
|
||||
let memory = get_export_memory(&exports, 0)?;
|
||||
let size_func = get_export_func(&exports, 1)?;
|
||||
let load_func = get_export_func(&exports, 2)?;
|
||||
let store_func = get_export_func(&exports, 3)?;
|
||||
|
||||
// Check initial memory.
|
||||
println!("Checking memory...");
|
||||
check!(memory.size(), 2u32);
|
||||
check!(memory.data_size(), 0x20000usize);
|
||||
check!(unsafe { memory.data_unchecked_mut()[0] }, 0);
|
||||
check!(unsafe { memory.data_unchecked_mut()[0x1000] }, 1);
|
||||
check!(unsafe { memory.data_unchecked_mut()[0x1003] }, 4);
|
||||
|
||||
check!(call!(size_func,), 2);
|
||||
check!(call!(load_func, 0), 0);
|
||||
check!(call!(load_func, 0x1000), 1);
|
||||
check!(call!(load_func, 0x1003), 4);
|
||||
check!(call!(load_func, 0x1ffff), 0);
|
||||
check_trap!(load_func, 0x20000);
|
||||
|
||||
// Mutate memory.
|
||||
println!("Mutating memory...");
|
||||
unsafe {
|
||||
memory.data_unchecked_mut()[0x1003] = 5;
|
||||
}
|
||||
|
||||
check_ok!(store_func, 0x1002, 6);
|
||||
check_trap!(store_func, 0x20000, 0);
|
||||
|
||||
check!(unsafe { memory.data_unchecked()[0x1002] }, 6);
|
||||
check!(unsafe { memory.data_unchecked()[0x1003] }, 5);
|
||||
check!(call!(load_func, 0x1002), 6);
|
||||
check!(call!(load_func, 0x1003), 5);
|
||||
|
||||
// Grow memory.
|
||||
println!("Growing memory...");
|
||||
memory.grow(1)?;
|
||||
check!(memory.size(), 3u32);
|
||||
check!(memory.data_size(), 0x30000usize);
|
||||
|
||||
check!(call!(load_func, 0x20000), 0);
|
||||
check_ok!(store_func, 0x20000, 0);
|
||||
check_trap!(load_func, 0x30000);
|
||||
check_trap!(store_func, 0x30000, 0);
|
||||
|
||||
memory.grow(1).unwrap_err();
|
||||
memory.grow(0).unwrap();
|
||||
|
||||
// Create stand-alone memory.
|
||||
// TODO(wasm+): Once Wasm allows multiple memories, turn this into import.
|
||||
println!("Creating stand-alone memory...");
|
||||
let memorytype = MemoryType::new(Limits::new(5, Some(5)));
|
||||
let memory2 = Memory::new(&store, memorytype);
|
||||
check!(memory2.size(), 5u32);
|
||||
memory2.grow(1).unwrap_err();
|
||||
memory2.grow(0).unwrap();
|
||||
|
||||
// Shut down.
|
||||
println!("Shutting down...");
|
||||
drop(store);
|
||||
|
||||
println!("Done.");
|
||||
Ok(())
|
||||
}
|
||||
129
crates/api/examples/multi.rs
Normal file
129
crates/api/examples/multi.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
//! Translation of multi example
|
||||
|
||||
use anyhow::{ensure, format_err, Context as _, Result};
|
||||
use std::rc::Rc;
|
||||
use wasmtime::*;
|
||||
|
||||
struct Callback;
|
||||
|
||||
impl Callable for Callback {
|
||||
fn call(&self, args: &[Val], results: &mut [Val]) -> Result<(), Trap> {
|
||||
println!("Calling back...");
|
||||
println!("> {} {}", args[0].unwrap_i32(), args[1].unwrap_i64());
|
||||
|
||||
results[0] = Val::I64(args[1].unwrap_i64() + 1);
|
||||
results[1] = Val::I32(args[0].unwrap_i32() + 1);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const WAT: &str = r#"
|
||||
(module
|
||||
(func $f (import "" "f") (param i32 i64) (result i64 i32))
|
||||
|
||||
(func $g (export "g") (param i32 i64) (result i64 i32)
|
||||
(call $f (local.get 0) (local.get 1))
|
||||
)
|
||||
|
||||
(func $round_trip_many
|
||||
(export "round_trip_many")
|
||||
(param i64 i64 i64 i64 i64 i64 i64 i64 i64 i64)
|
||||
(result i64 i64 i64 i64 i64 i64 i64 i64 i64 i64)
|
||||
local.get 0
|
||||
local.get 1
|
||||
local.get 2
|
||||
local.get 3
|
||||
local.get 4
|
||||
local.get 5
|
||||
local.get 6
|
||||
local.get 7
|
||||
local.get 8
|
||||
local.get 9)
|
||||
)
|
||||
"#;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// Initialize.
|
||||
println!("Initializing...");
|
||||
let engine = Engine::new(Config::new().wasm_multi_value(true));
|
||||
let store = Store::new(&engine);
|
||||
|
||||
// Compile.
|
||||
println!("Compiling module...");
|
||||
let module = Module::new(&store, WAT).context("Error compiling module!")?;
|
||||
|
||||
// Create external print functions.
|
||||
println!("Creating callback...");
|
||||
let callback_type = FuncType::new(
|
||||
Box::new([ValType::I32, ValType::I64]),
|
||||
Box::new([ValType::I64, ValType::I32]),
|
||||
);
|
||||
let callback_func = Func::new(&store, callback_type, Rc::new(Callback));
|
||||
|
||||
// Instantiate.
|
||||
println!("Instantiating module...");
|
||||
let imports = vec![callback_func.into()];
|
||||
let instance =
|
||||
Instance::new(&module, imports.as_slice()).context("Error instantiating module!")?;
|
||||
|
||||
// Extract exports.
|
||||
println!("Extracting export...");
|
||||
let exports = instance.exports();
|
||||
ensure!(!exports.is_empty(), "Error accessing exports!");
|
||||
let g = exports[0].func().context("> Error accessing export $g!")?;
|
||||
let round_trip_many = exports[1]
|
||||
.func()
|
||||
.context("> Error accessing export $round_trip_many")?;
|
||||
|
||||
// Call `$g`.
|
||||
println!("Calling export \"g\"...");
|
||||
let args = vec![Val::I32(1), Val::I64(3)];
|
||||
let results = g
|
||||
.call(&args)
|
||||
.map_err(|e| format_err!("> Error calling g! {:?}", e))?;
|
||||
|
||||
println!("Printing result...");
|
||||
println!("> {} {}", results[0].unwrap_i64(), results[1].unwrap_i32());
|
||||
|
||||
debug_assert_eq!(results[0].unwrap_i64(), 4);
|
||||
debug_assert_eq!(results[1].unwrap_i32(), 2);
|
||||
|
||||
// Call `$round_trip_many`.
|
||||
println!("Calling export \"round_trip_many\"...");
|
||||
let args = vec![
|
||||
Val::I64(0),
|
||||
Val::I64(1),
|
||||
Val::I64(2),
|
||||
Val::I64(3),
|
||||
Val::I64(4),
|
||||
Val::I64(5),
|
||||
Val::I64(6),
|
||||
Val::I64(7),
|
||||
Val::I64(8),
|
||||
Val::I64(9),
|
||||
];
|
||||
let results = round_trip_many
|
||||
.call(&args)
|
||||
.map_err(|e| format_err!("> Error calling round_trip_many! {:?}", e))?;
|
||||
|
||||
println!("Printing result...");
|
||||
print!(">");
|
||||
for r in results.iter() {
|
||||
print!(" {}", r.unwrap_i64());
|
||||
}
|
||||
println!();
|
||||
|
||||
debug_assert_eq!(results.len(), 10);
|
||||
debug_assert!(args
|
||||
.iter()
|
||||
.zip(results.iter())
|
||||
.all(|(a, r)| a.i64() == r.i64()));
|
||||
|
||||
// Shut down.
|
||||
println!("Shutting down...");
|
||||
drop(store);
|
||||
|
||||
// All done.
|
||||
println!("Done.");
|
||||
Ok(())
|
||||
}
|
||||
225
crates/api/src/callable.rs
Normal file
225
crates/api/src/callable.rs
Normal file
@@ -0,0 +1,225 @@
|
||||
use crate::runtime::Store;
|
||||
use crate::trampoline::generate_func_export;
|
||||
use crate::trap::Trap;
|
||||
use crate::types::FuncType;
|
||||
use crate::values::Val;
|
||||
use std::ptr;
|
||||
use std::rc::Rc;
|
||||
use wasmtime_environ::ir;
|
||||
use wasmtime_runtime::{Export, InstanceHandle};
|
||||
|
||||
/// A trait representing a function that can be imported and called from inside
|
||||
/// WebAssembly.
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use wasmtime::Val;
|
||||
///
|
||||
/// struct TimesTwo;
|
||||
///
|
||||
/// impl wasmtime::Callable for TimesTwo {
|
||||
/// fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), wasmtime::Trap> {
|
||||
/// let mut value = params[0].unwrap_i32();
|
||||
/// value *= 2;
|
||||
/// results[0] = value.into();
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # fn main () -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// // Simple module that imports our host function ("times_two") and re-exports
|
||||
/// // it as "run".
|
||||
/// let wat = r#"
|
||||
/// (module
|
||||
/// (func $times_two (import "" "times_two") (param i32) (result i32))
|
||||
/// (func
|
||||
/// (export "run")
|
||||
/// (param i32)
|
||||
/// (result i32)
|
||||
/// (local.get 0)
|
||||
/// (call $times_two))
|
||||
/// )
|
||||
/// "#;
|
||||
///
|
||||
/// // Initialise environment and our module.
|
||||
/// let store = wasmtime::Store::default();
|
||||
/// let module = wasmtime::Module::new(&store, wat)?;
|
||||
///
|
||||
/// // Define the type of the function we're going to call.
|
||||
/// let times_two_type = wasmtime::FuncType::new(
|
||||
/// // Parameters
|
||||
/// Box::new([wasmtime::ValType::I32]),
|
||||
/// // Results
|
||||
/// Box::new([wasmtime::ValType::I32])
|
||||
/// );
|
||||
///
|
||||
/// // Build a reference to the "times_two" function that can be used.
|
||||
/// let times_two_function =
|
||||
/// wasmtime::Func::new(&store, times_two_type, std::rc::Rc::new(TimesTwo));
|
||||
///
|
||||
/// // Create module instance that imports our function
|
||||
/// let instance = wasmtime::Instance::new(
|
||||
/// &module,
|
||||
/// &[times_two_function.into()]
|
||||
/// )?;
|
||||
///
|
||||
/// // Get "run" function from the exports.
|
||||
/// let run_function = instance.exports()[0].func().unwrap();
|
||||
///
|
||||
/// // Borrow and call "run". Returning any error message from Wasm as a string.
|
||||
/// let original = 5i32;
|
||||
/// let results = run_function
|
||||
/// .call(&[original.into()])
|
||||
/// .map_err(|trap| trap.to_string())?;
|
||||
///
|
||||
/// // Compare that the results returned matches what we expect.
|
||||
/// assert_eq!(original * 2, results[0].unwrap_i32());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub trait Callable {
|
||||
/// What is called when the function is invoked in WebAssembly.
|
||||
/// `params` is an immutable list of parameters provided to the function.
|
||||
/// `results` is mutable list of results to be potentially set by your
|
||||
/// function. Produces a `Trap` if the function encounters any errors.
|
||||
fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), Trap>;
|
||||
}
|
||||
|
||||
pub(crate) trait WrappedCallable {
|
||||
fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), Trap>;
|
||||
fn signature(&self) -> &ir::Signature {
|
||||
match self.wasmtime_export() {
|
||||
Export::Function { signature, .. } => signature,
|
||||
_ => panic!("unexpected export type in Callable"),
|
||||
}
|
||||
}
|
||||
fn wasmtime_handle(&self) -> &InstanceHandle;
|
||||
fn wasmtime_export(&self) -> &Export;
|
||||
}
|
||||
|
||||
pub(crate) struct WasmtimeFn {
|
||||
store: Store,
|
||||
instance: InstanceHandle,
|
||||
export: Export,
|
||||
}
|
||||
|
||||
impl WasmtimeFn {
|
||||
pub fn new(store: &Store, instance: InstanceHandle, export: Export) -> WasmtimeFn {
|
||||
WasmtimeFn {
|
||||
store: store.clone(),
|
||||
instance,
|
||||
export,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WrappedCallable for WasmtimeFn {
|
||||
fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), Trap> {
|
||||
use std::cmp::max;
|
||||
use std::mem;
|
||||
|
||||
let (vmctx, body, signature) = match self.wasmtime_export() {
|
||||
Export::Function {
|
||||
vmctx,
|
||||
address,
|
||||
signature,
|
||||
} => (*vmctx, *address, signature.clone()),
|
||||
_ => panic!("unexpected export type in Callable"),
|
||||
};
|
||||
if signature.params.len() - 2 != params.len() {
|
||||
return Err(Trap::new(format!(
|
||||
"expected {} arguments, got {}",
|
||||
signature.params.len() - 2,
|
||||
params.len()
|
||||
)));
|
||||
}
|
||||
if signature.returns.len() != results.len() {
|
||||
return Err(Trap::new(format!(
|
||||
"expected {} results, got {}",
|
||||
signature.returns.len(),
|
||||
results.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let value_size = mem::size_of::<u128>();
|
||||
let mut values_vec = vec![0; max(params.len(), results.len())];
|
||||
|
||||
// Store the argument values into `values_vec`.
|
||||
let param_tys = signature.params.iter().skip(2);
|
||||
for ((arg, slot), ty) in params.iter().zip(&mut values_vec).zip(param_tys) {
|
||||
if arg.ty().get_wasmtime_type() != Some(ty.value_type) {
|
||||
return Err(Trap::new("argument type mismatch"));
|
||||
}
|
||||
unsafe {
|
||||
arg.write_value_to(slot);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the trampoline to call for this function.
|
||||
let exec_code_buf = self
|
||||
.store
|
||||
.compiler_mut()
|
||||
.get_published_trampoline(&signature, value_size)
|
||||
.map_err(|e| Trap::new(format!("trampoline error: {:?}", e)))?;
|
||||
|
||||
// Call the trampoline.
|
||||
if let Err(error) = unsafe {
|
||||
wasmtime_runtime::wasmtime_call_trampoline(
|
||||
vmctx,
|
||||
ptr::null_mut(),
|
||||
exec_code_buf,
|
||||
body,
|
||||
values_vec.as_mut_ptr() as *mut u8,
|
||||
)
|
||||
} {
|
||||
return Err(Trap::from_jit(error));
|
||||
}
|
||||
|
||||
// Load the return values out of `values_vec`.
|
||||
for (index, abi_param) in signature.returns.iter().enumerate() {
|
||||
unsafe {
|
||||
let ptr = values_vec.as_ptr().add(index);
|
||||
|
||||
results[index] = Val::read_value_from(ptr, abi_param.value_type);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn wasmtime_handle(&self) -> &InstanceHandle {
|
||||
&self.instance
|
||||
}
|
||||
fn wasmtime_export(&self) -> &Export {
|
||||
&self.export
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NativeCallable {
|
||||
callable: Rc<dyn Callable + 'static>,
|
||||
instance: InstanceHandle,
|
||||
export: Export,
|
||||
}
|
||||
|
||||
impl NativeCallable {
|
||||
pub(crate) fn new(callable: Rc<dyn Callable + 'static>, ft: &FuncType, store: &Store) -> Self {
|
||||
let (instance, export) =
|
||||
generate_func_export(ft, &callable, store).expect("generated func");
|
||||
NativeCallable {
|
||||
callable,
|
||||
instance,
|
||||
export,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WrappedCallable for NativeCallable {
|
||||
fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), Trap> {
|
||||
self.callable.call(params, results)
|
||||
}
|
||||
fn wasmtime_handle(&self) -> &InstanceHandle {
|
||||
&self.instance
|
||||
}
|
||||
fn wasmtime_export(&self) -> &Export {
|
||||
&self.export
|
||||
}
|
||||
}
|
||||
643
crates/api/src/externals.rs
Normal file
643
crates/api/src/externals.rs
Normal file
@@ -0,0 +1,643 @@
|
||||
use crate::trampoline::{generate_global_export, generate_memory_export, generate_table_export};
|
||||
use crate::values::{from_checked_anyfunc, into_checked_anyfunc, Val};
|
||||
use crate::Mutability;
|
||||
use crate::{ExternType, GlobalType, MemoryType, TableType, ValType};
|
||||
use crate::{Func, Store};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use std::slice;
|
||||
use wasmtime_environ::{ir, wasm};
|
||||
use wasmtime_runtime::{self as runtime, InstanceHandle};
|
||||
|
||||
// Externals
|
||||
|
||||
/// An external item to a WebAssembly module, or a list of what can possibly be
|
||||
/// exported from a wasm module.
|
||||
///
|
||||
/// This is both returned from [`Instance::exports`](crate::Instance::exports)
|
||||
/// as well as required by [`Instance::new`](crate::Instance::new). In other
|
||||
/// words, this is the type of extracted values from an instantiated module, and
|
||||
/// it's also used to provide imported values when instantiating a module.
|
||||
#[derive(Clone)]
|
||||
pub enum Extern {
|
||||
/// A WebAssembly `func` which can be called.
|
||||
Func(Func),
|
||||
/// A WebAssembly `global` which acts like a `Cell<T>` of sorts, supporting
|
||||
/// `get` and `set` operations.
|
||||
Global(Global),
|
||||
/// A WebAssembly `table` which is an array of `Val` types.
|
||||
Table(Table),
|
||||
/// A WebAssembly linear memory.
|
||||
Memory(Memory),
|
||||
}
|
||||
|
||||
impl Extern {
|
||||
/// Returns the underlying `Func`, if this external is a function.
|
||||
///
|
||||
/// Returns `None` if this is not a function.
|
||||
pub fn func(&self) -> Option<&Func> {
|
||||
match self {
|
||||
Extern::Func(func) => Some(func),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the underlying `Global`, if this external is a global.
|
||||
///
|
||||
/// Returns `None` if this is not a global.
|
||||
pub fn global(&self) -> Option<&Global> {
|
||||
match self {
|
||||
Extern::Global(global) => Some(global),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the underlying `Table`, if this external is a table.
|
||||
///
|
||||
/// Returns `None` if this is not a table.
|
||||
pub fn table(&self) -> Option<&Table> {
|
||||
match self {
|
||||
Extern::Table(table) => Some(table),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the underlying `Memory`, if this external is a memory.
|
||||
///
|
||||
/// Returns `None` if this is not a memory.
|
||||
pub fn memory(&self) -> Option<&Memory> {
|
||||
match self {
|
||||
Extern::Memory(memory) => Some(memory),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the type associated with this `Extern`.
|
||||
pub fn ty(&self) -> ExternType {
|
||||
match self {
|
||||
Extern::Func(ft) => ExternType::Func(ft.ty().clone()),
|
||||
Extern::Memory(ft) => ExternType::Memory(ft.ty().clone()),
|
||||
Extern::Table(tt) => ExternType::Table(tt.ty().clone()),
|
||||
Extern::Global(gt) => ExternType::Global(gt.ty().clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_wasmtime_export(&self) -> wasmtime_runtime::Export {
|
||||
match self {
|
||||
Extern::Func(f) => f.wasmtime_export().clone(),
|
||||
Extern::Global(g) => g.wasmtime_export().clone(),
|
||||
Extern::Memory(m) => m.wasmtime_export().clone(),
|
||||
Extern::Table(t) => t.wasmtime_export().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_wasmtime_export(
|
||||
store: &Store,
|
||||
instance_handle: InstanceHandle,
|
||||
export: wasmtime_runtime::Export,
|
||||
) -> Extern {
|
||||
match export {
|
||||
wasmtime_runtime::Export::Function { .. } => {
|
||||
Extern::Func(Func::from_wasmtime_function(export, store, instance_handle))
|
||||
}
|
||||
wasmtime_runtime::Export::Memory { .. } => {
|
||||
Extern::Memory(Memory::from_wasmtime_memory(export, store, instance_handle))
|
||||
}
|
||||
wasmtime_runtime::Export::Global { .. } => {
|
||||
Extern::Global(Global::from_wasmtime_global(export, store, instance_handle))
|
||||
}
|
||||
wasmtime_runtime::Export::Table { .. } => {
|
||||
Extern::Table(Table::from_wasmtime_table(export, store, instance_handle))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Func> for Extern {
|
||||
fn from(r: Func) -> Self {
|
||||
Extern::Func(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Global> for Extern {
|
||||
fn from(r: Global) -> Self {
|
||||
Extern::Global(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Memory> for Extern {
|
||||
fn from(r: Memory) -> Self {
|
||||
Extern::Memory(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Table> for Extern {
|
||||
fn from(r: Table) -> Self {
|
||||
Extern::Table(r)
|
||||
}
|
||||
}
|
||||
|
||||
/// A WebAssembly `global` value which can be read and written to.
|
||||
///
|
||||
/// A `global` in WebAssembly is sort of like a global variable within an
|
||||
/// [`Instance`](crate::Instance). The `global.get` and `global.set`
|
||||
/// instructions will modify and read global values in a wasm module. Globals
|
||||
/// can either be imported or exported from wasm modules.
|
||||
///
|
||||
/// If you're familiar with Rust already you can think of a `Global` as a sort
|
||||
/// of `Rc<Cell<Val>>`, more or less.
|
||||
///
|
||||
/// # `Global` and `Clone`
|
||||
///
|
||||
/// Globals are internally reference counted so you can `clone` a `Global`. The
|
||||
/// cloning process only performs a shallow clone, so two cloned `Global`
|
||||
/// instances are equivalent in their functionality.
|
||||
#[derive(Clone)]
|
||||
pub struct Global {
|
||||
_store: Store,
|
||||
ty: GlobalType,
|
||||
wasmtime_export: wasmtime_runtime::Export,
|
||||
wasmtime_handle: InstanceHandle,
|
||||
}
|
||||
|
||||
impl Global {
|
||||
/// Creates a new WebAssembly `global` value with the provide type `ty` and
|
||||
/// initial value `val`.
|
||||
///
|
||||
/// The `store` argument provided is used as a general global cache for
|
||||
/// information, and otherwise the `ty` and `val` arguments are used to
|
||||
/// initialize the global.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the `ty` provided does not match the type of the
|
||||
/// value `val`.
|
||||
pub fn new(store: &Store, ty: GlobalType, val: Val) -> Result<Global> {
|
||||
if val.ty() != *ty.content() {
|
||||
bail!("value provided does not match the type of this global");
|
||||
}
|
||||
let (wasmtime_handle, wasmtime_export) = generate_global_export(store, &ty, val)?;
|
||||
Ok(Global {
|
||||
_store: store.clone(),
|
||||
ty,
|
||||
wasmtime_export,
|
||||
wasmtime_handle,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the underlying type of this `global`.
|
||||
pub fn ty(&self) -> &GlobalType {
|
||||
&self.ty
|
||||
}
|
||||
|
||||
fn wasmtime_global_definition(&self) -> *mut wasmtime_runtime::VMGlobalDefinition {
|
||||
match self.wasmtime_export {
|
||||
wasmtime_runtime::Export::Global { definition, .. } => definition,
|
||||
_ => panic!("global definition not found"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current [`Val`] of this global.
|
||||
pub fn get(&self) -> Val {
|
||||
let definition = unsafe { &mut *self.wasmtime_global_definition() };
|
||||
unsafe {
|
||||
match self.ty().content() {
|
||||
ValType::I32 => Val::from(*definition.as_i32()),
|
||||
ValType::I64 => Val::from(*definition.as_i64()),
|
||||
ValType::F32 => Val::F32(*definition.as_u32()),
|
||||
ValType::F64 => Val::F64(*definition.as_u64()),
|
||||
_ => unimplemented!("Global::get for {:?}", self.ty().content()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to set the current value of this global to [`Val`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if this global has a different type than `Val`, or if
|
||||
/// it's not a mutable global.
|
||||
pub fn set(&self, val: Val) -> Result<()> {
|
||||
if self.ty().mutability() != Mutability::Var {
|
||||
bail!("immutable global cannot be set");
|
||||
}
|
||||
if val.ty() != *self.ty().content() {
|
||||
bail!(
|
||||
"global of type {:?} cannot be set to {:?}",
|
||||
self.ty().content(),
|
||||
val.ty()
|
||||
);
|
||||
}
|
||||
let definition = unsafe { &mut *self.wasmtime_global_definition() };
|
||||
unsafe {
|
||||
match val {
|
||||
Val::I32(i) => *definition.as_i32_mut() = i,
|
||||
Val::I64(i) => *definition.as_i64_mut() = i,
|
||||
Val::F32(f) => *definition.as_u32_mut() = f,
|
||||
Val::F64(f) => *definition.as_u64_mut() = f,
|
||||
_ => unimplemented!("Global::set for {:?}", val.ty()),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn wasmtime_export(&self) -> &wasmtime_runtime::Export {
|
||||
&self.wasmtime_export
|
||||
}
|
||||
|
||||
pub(crate) fn from_wasmtime_global(
|
||||
export: wasmtime_runtime::Export,
|
||||
store: &Store,
|
||||
wasmtime_handle: InstanceHandle,
|
||||
) -> Global {
|
||||
let global = if let wasmtime_runtime::Export::Global { ref global, .. } = export {
|
||||
global
|
||||
} else {
|
||||
panic!("wasmtime export is not global")
|
||||
};
|
||||
// The original export is coming from wasmtime_runtime itself we should
|
||||
// support all the types coming out of it, so assert such here.
|
||||
let ty = GlobalType::from_wasmtime_global(&global)
|
||||
.expect("core wasm global type should be supported");
|
||||
Global {
|
||||
_store: store.clone(),
|
||||
ty: ty,
|
||||
wasmtime_export: export,
|
||||
wasmtime_handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A WebAssembly `table`, or an array of values.
|
||||
///
|
||||
/// Like [`Memory`] a table is an indexed array of values, but unlike [`Memory`]
|
||||
/// it's an array of WebAssembly values rather than bytes. One of the most
|
||||
/// common usages of a table is a function table for wasm modules, where each
|
||||
/// element has the `Func` type.
|
||||
///
|
||||
/// Tables, like globals, are not threadsafe and can only be used on one thread.
|
||||
/// Tables can be grown in size and each element can be read/written.
|
||||
///
|
||||
/// # `Table` and `Clone`
|
||||
///
|
||||
/// Tables are internally reference counted so you can `clone` a `Table`. The
|
||||
/// cloning process only performs a shallow clone, so two cloned `Table`
|
||||
/// instances are equivalent in their functionality.
|
||||
#[derive(Clone)]
|
||||
pub struct Table {
|
||||
store: Store,
|
||||
ty: TableType,
|
||||
wasmtime_handle: InstanceHandle,
|
||||
wasmtime_export: wasmtime_runtime::Export,
|
||||
}
|
||||
|
||||
fn set_table_item(
|
||||
handle: &mut InstanceHandle,
|
||||
table_index: wasm::DefinedTableIndex,
|
||||
item_index: u32,
|
||||
item: wasmtime_runtime::VMCallerCheckedAnyfunc,
|
||||
) -> Result<()> {
|
||||
handle
|
||||
.table_set(table_index, item_index, item)
|
||||
.map_err(|()| anyhow!("table element index out of bounds"))
|
||||
}
|
||||
|
||||
impl Table {
|
||||
/// Creates a new `Table` with the given parameters.
|
||||
///
|
||||
/// * `store` - a global cache to store information in
|
||||
/// * `ty` - the type of this table, containing both the element type as
|
||||
/// well as the initial size and maximum size, if any.
|
||||
/// * `init` - the initial value to fill all table entries with, if the
|
||||
/// table starts with an initial size.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if `init` does not match the element type of the table.
|
||||
pub fn new(store: &Store, ty: TableType, init: Val) -> Result<Table> {
|
||||
let item = into_checked_anyfunc(init, store)?;
|
||||
let (mut wasmtime_handle, wasmtime_export) = generate_table_export(store, &ty)?;
|
||||
|
||||
// Initialize entries with the init value.
|
||||
match wasmtime_export {
|
||||
wasmtime_runtime::Export::Table { definition, .. } => {
|
||||
let index = wasmtime_handle.table_index(unsafe { &*definition });
|
||||
let len = unsafe { (*definition).current_elements };
|
||||
for i in 0..len {
|
||||
set_table_item(&mut wasmtime_handle, index, i, item.clone())?;
|
||||
}
|
||||
}
|
||||
_ => unreachable!("export should be a table"),
|
||||
}
|
||||
|
||||
Ok(Table {
|
||||
store: store.clone(),
|
||||
ty,
|
||||
wasmtime_handle,
|
||||
wasmtime_export,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the underlying type of this table, including its element type as
|
||||
/// well as the maximum/minimum lower bounds.
|
||||
pub fn ty(&self) -> &TableType {
|
||||
&self.ty
|
||||
}
|
||||
|
||||
fn wasmtime_table_index(&self) -> wasm::DefinedTableIndex {
|
||||
match self.wasmtime_export {
|
||||
wasmtime_runtime::Export::Table { definition, .. } => {
|
||||
self.wasmtime_handle.table_index(unsafe { &*definition })
|
||||
}
|
||||
_ => panic!("global definition not found"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the table element value at `index`.
|
||||
///
|
||||
/// Returns `None` if `index` is out of bounds.
|
||||
pub fn get(&self, index: u32) -> Option<Val> {
|
||||
let table_index = self.wasmtime_table_index();
|
||||
let item = self.wasmtime_handle.table_get(table_index, index)?;
|
||||
Some(from_checked_anyfunc(item, &self.store))
|
||||
}
|
||||
|
||||
/// Writes the `val` provided into `index` within this table.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if `index` is out of bounds or if `val` does not have
|
||||
/// the right type to be stored in this table.
|
||||
pub fn set(&self, index: u32, val: Val) -> Result<()> {
|
||||
let table_index = self.wasmtime_table_index();
|
||||
let mut wasmtime_handle = self.wasmtime_handle.clone();
|
||||
let item = into_checked_anyfunc(val, &self.store)?;
|
||||
set_table_item(&mut wasmtime_handle, table_index, index, item)
|
||||
}
|
||||
|
||||
/// Returns the current size of this table.
|
||||
pub fn size(&self) -> u32 {
|
||||
match self.wasmtime_export {
|
||||
wasmtime_runtime::Export::Table { definition, .. } => unsafe {
|
||||
(*definition).current_elements
|
||||
},
|
||||
_ => panic!("global definition not found"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Grows the size of this table by `delta` more elements, initialization
|
||||
/// all new elements to `init`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the table cannot be grown by `delta`, for example
|
||||
/// if it would cause the table to exceed its maximum size. Also returns an
|
||||
/// error if `init` is not of the right type.
|
||||
pub fn grow(&self, delta: u32, init: Val) -> Result<u32> {
|
||||
let index = self.wasmtime_table_index();
|
||||
let item = into_checked_anyfunc(init, &self.store)?;
|
||||
if let Some(len) = self.wasmtime_handle.clone().table_grow(index, delta) {
|
||||
let mut wasmtime_handle = self.wasmtime_handle.clone();
|
||||
for i in 0..delta {
|
||||
let i = len - (delta - i);
|
||||
set_table_item(&mut wasmtime_handle, index, i, item.clone())?;
|
||||
}
|
||||
Ok(len)
|
||||
} else {
|
||||
bail!("failed to grow table by `{}`", delta)
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy `len` elements from `src_table[src_index..]` into
|
||||
/// `dst_table[dst_index..]`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the range is out of bounds of either the source or
|
||||
/// destination tables.
|
||||
pub fn copy(
|
||||
dst_table: &Table,
|
||||
dst_index: u32,
|
||||
src_table: &Table,
|
||||
src_index: u32,
|
||||
len: u32,
|
||||
) -> Result<()> {
|
||||
// NB: We must use the `dst_table`'s `wasmtime_handle` for the
|
||||
// `dst_table_index` and vice versa for `src_table` since each table can
|
||||
// come from different modules.
|
||||
|
||||
let dst_table_index = dst_table.wasmtime_table_index();
|
||||
let dst_table = dst_table.wasmtime_handle.get_defined_table(dst_table_index);
|
||||
|
||||
let src_table_index = src_table.wasmtime_table_index();
|
||||
let src_table = src_table.wasmtime_handle.get_defined_table(src_table_index);
|
||||
|
||||
runtime::Table::copy(
|
||||
dst_table,
|
||||
src_table,
|
||||
dst_index,
|
||||
src_index,
|
||||
len,
|
||||
ir::SourceLoc::default(),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn wasmtime_export(&self) -> &wasmtime_runtime::Export {
|
||||
&self.wasmtime_export
|
||||
}
|
||||
|
||||
pub(crate) fn from_wasmtime_table(
|
||||
export: wasmtime_runtime::Export,
|
||||
store: &Store,
|
||||
instance_handle: wasmtime_runtime::InstanceHandle,
|
||||
) -> Table {
|
||||
let table = if let wasmtime_runtime::Export::Table { ref table, .. } = export {
|
||||
table
|
||||
} else {
|
||||
panic!("wasmtime export is not table")
|
||||
};
|
||||
let ty = TableType::from_wasmtime_table(&table.table);
|
||||
Table {
|
||||
store: store.clone(),
|
||||
ty: ty,
|
||||
wasmtime_handle: instance_handle,
|
||||
wasmtime_export: export,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A WebAssembly linear memory.
|
||||
///
|
||||
/// WebAssembly memories represent a contiguous array of bytes that have a size
|
||||
/// that is always a multiple of the WebAssembly page size, currently 64
|
||||
/// kilobytes.
|
||||
///
|
||||
/// WebAssembly memory is used for global data, statics in C/C++/Rust, shadow
|
||||
/// stack memory, etc. Accessing wasm memory is generally quite fast!
|
||||
///
|
||||
/// # `Memory` and `Clone`
|
||||
///
|
||||
/// Memories are internally reference counted so you can `clone` a `Memory`. The
|
||||
/// cloning process only performs a shallow clone, so two cloned `Memory`
|
||||
/// instances are equivalent in their functionality.
|
||||
///
|
||||
/// # `Memory` and threads
|
||||
///
|
||||
/// It is intended that `Memory` is safe to share between threads. At this time
|
||||
/// this is not implemented in `wasmtime`, however. This is planned to be
|
||||
/// implemented though!
|
||||
#[derive(Clone)]
|
||||
pub struct Memory {
|
||||
_store: Store,
|
||||
ty: MemoryType,
|
||||
wasmtime_handle: InstanceHandle,
|
||||
wasmtime_export: wasmtime_runtime::Export,
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
/// Creates a new WebAssembly memory given the configuration of `ty`.
|
||||
///
|
||||
/// The `store` argument is a general location for cache information, and
|
||||
/// otherwise the memory will immediately be allocated according to the
|
||||
/// type's configuration. All WebAssembly memory is initialized to zero.
|
||||
pub fn new(store: &Store, ty: MemoryType) -> Memory {
|
||||
let (wasmtime_handle, wasmtime_export) =
|
||||
generate_memory_export(store, &ty).expect("generated memory");
|
||||
Memory {
|
||||
_store: store.clone(),
|
||||
ty,
|
||||
wasmtime_handle,
|
||||
wasmtime_export,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the underlying type of this memory.
|
||||
pub fn ty(&self) -> &MemoryType {
|
||||
&self.ty
|
||||
}
|
||||
|
||||
fn wasmtime_memory_definition(&self) -> *mut wasmtime_runtime::VMMemoryDefinition {
|
||||
match self.wasmtime_export {
|
||||
wasmtime_runtime::Export::Memory { definition, .. } => definition,
|
||||
_ => panic!("memory definition not found"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns this memory as a slice view that can be read natively in Rust.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This is an unsafe operation because there is no guarantee that the
|
||||
/// following operations do not happen concurrently while the slice is in
|
||||
/// use:
|
||||
///
|
||||
/// * Data could be modified by calling into a wasm module.
|
||||
/// * Memory could be relocated through growth by calling into a wasm
|
||||
/// module.
|
||||
/// * When threads are supported, non-atomic reads will race with other
|
||||
/// writes.
|
||||
///
|
||||
/// Extreme care need be taken when the data of a `Memory` is read. The
|
||||
/// above invariants all need to be upheld at a bare minimum, and in
|
||||
/// general you'll need to ensure that while you're looking at slice you're
|
||||
/// the only one who can possibly look at the slice and read/write it.
|
||||
///
|
||||
/// Be sure to keep in mind that `Memory` is reference counted, meaning
|
||||
/// that there may be other users of this `Memory` instance elsewhere in
|
||||
/// your program. Additionally `Memory` can be shared and used in any number
|
||||
/// of wasm instances, so calling any wasm code should be considered
|
||||
/// dangerous while you're holding a slice of memory.
|
||||
pub unsafe fn data_unchecked(&self) -> &[u8] {
|
||||
self.data_unchecked_mut()
|
||||
}
|
||||
|
||||
/// Returns this memory as a slice view that can be read and written
|
||||
/// natively in Rust.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// All of the same safety caveats of [`Memory::data_unchecked`] apply
|
||||
/// here, doubly so because this is returning a mutable slice! As a
|
||||
/// double-extra reminder, remember that `Memory` is reference counted, so
|
||||
/// you can very easily acquire two mutable slices by simply calling this
|
||||
/// function twice. Extreme caution should be used when using this method,
|
||||
/// and in general you probably want to result to unsafe accessors and the
|
||||
/// `data` methods below.
|
||||
pub unsafe fn data_unchecked_mut(&self) -> &mut [u8] {
|
||||
let definition = &*self.wasmtime_memory_definition();
|
||||
slice::from_raw_parts_mut(definition.base, definition.current_length)
|
||||
}
|
||||
|
||||
/// Returns the base pointer, in the host's address space, that the memory
|
||||
/// is located at.
|
||||
///
|
||||
/// When reading and manipulating memory be sure to read up on the caveats
|
||||
/// of [`Memory::data_unchecked`] to make sure that you can safely
|
||||
/// read/write the memory.
|
||||
pub fn data_ptr(&self) -> *mut u8 {
|
||||
unsafe { (*self.wasmtime_memory_definition()).base }
|
||||
}
|
||||
|
||||
/// Returns the byte length of this memory.
|
||||
///
|
||||
/// The returned value will be a multiple of the wasm page size, 64k.
|
||||
pub fn data_size(&self) -> usize {
|
||||
unsafe { (*self.wasmtime_memory_definition()).current_length }
|
||||
}
|
||||
|
||||
/// Returns the size, in pages, of this wasm memory.
|
||||
pub fn size(&self) -> u32 {
|
||||
(self.data_size() / wasmtime_environ::WASM_PAGE_SIZE as usize) as u32
|
||||
}
|
||||
|
||||
/// Grows this WebAssembly memory by `delta` pages.
|
||||
///
|
||||
/// This will attempt to add `delta` more pages of memory on to the end of
|
||||
/// this `Memory` instance. If successful this may relocate the memory and
|
||||
/// cause [`Memory::data_ptr`] to return a new value. Additionally previous
|
||||
/// slices into this memory may no longer be valid.
|
||||
///
|
||||
/// On success returns the number of pages this memory previously had
|
||||
/// before the growth succeeded.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if memory could not be grown, for example if it exceeds
|
||||
/// the maximum limits of this memory.
|
||||
pub fn grow(&self, delta: u32) -> Result<u32> {
|
||||
match self.wasmtime_export {
|
||||
wasmtime_runtime::Export::Memory { definition, .. } => {
|
||||
let definition = unsafe { &(*definition) };
|
||||
let index = self.wasmtime_handle.memory_index(definition);
|
||||
self.wasmtime_handle
|
||||
.clone()
|
||||
.memory_grow(index, delta)
|
||||
.ok_or_else(|| anyhow!("failed to grow memory"))
|
||||
}
|
||||
_ => panic!("memory definition not found"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn wasmtime_export(&self) -> &wasmtime_runtime::Export {
|
||||
&self.wasmtime_export
|
||||
}
|
||||
|
||||
pub(crate) fn from_wasmtime_memory(
|
||||
export: wasmtime_runtime::Export,
|
||||
store: &Store,
|
||||
instance_handle: wasmtime_runtime::InstanceHandle,
|
||||
) -> Memory {
|
||||
let memory = if let wasmtime_runtime::Export::Memory { ref memory, .. } = export {
|
||||
memory
|
||||
} else {
|
||||
panic!("wasmtime export is not memory")
|
||||
};
|
||||
let ty = MemoryType::from_wasmtime_memory(&memory.memory);
|
||||
Memory {
|
||||
_store: store.clone(),
|
||||
ty: ty,
|
||||
wasmtime_handle: instance_handle,
|
||||
wasmtime_export: export,
|
||||
}
|
||||
}
|
||||
}
|
||||
183
crates/api/src/frame_info.rs
Normal file
183
crates/api/src/frame_info.rs
Normal file
@@ -0,0 +1,183 @@
|
||||
use crate::module::Names;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use wasmtime_environ::entity::EntityRef;
|
||||
use wasmtime_environ::wasm::FuncIndex;
|
||||
use wasmtime_jit::CompiledModule;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
/// This is a global cache of backtrace frame information for all active
|
||||
///
|
||||
/// This global cache is used during `Trap` creation to symbolicate frames.
|
||||
/// This is populated on module compilation, and it is cleared out whenever
|
||||
/// all references to a module are dropped.
|
||||
pub static ref FRAME_INFO: GlobalFrameInfo = GlobalFrameInfo::default();
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GlobalFrameInfo {
|
||||
/// An internal map that keeps track of backtrace frame information for
|
||||
/// each module.
|
||||
///
|
||||
/// This map is morally a map of ranges to a map of information for that
|
||||
/// module. Each module is expected to reside in a disjoint section of
|
||||
/// contiguous memory. No modules can overlap.
|
||||
///
|
||||
/// The key of this map is the highest address in the module and the value
|
||||
/// is the module's information, which also contains the start address.
|
||||
ranges: RwLock<BTreeMap<usize, ModuleFrameInfo>>,
|
||||
}
|
||||
|
||||
/// An RAII structure used to unregister a module's frame information when the
|
||||
/// module is destroyed.
|
||||
pub struct GlobalFrameInfoRegistration {
|
||||
/// The key that will be removed from the global `ranges` map when this is
|
||||
/// dropped.
|
||||
key: usize,
|
||||
}
|
||||
|
||||
struct ModuleFrameInfo {
|
||||
start: usize,
|
||||
functions: BTreeMap<usize, (usize, FuncIndex)>,
|
||||
names: Arc<Names>,
|
||||
}
|
||||
|
||||
impl GlobalFrameInfo {
|
||||
/// Registers a new compiled module's frame information.
|
||||
///
|
||||
/// This function will register the `names` information for all of the
|
||||
/// compiled functions within `module`. If the `module` has no functions
|
||||
/// then `None` will be returned. Otherwise the returned object, when
|
||||
/// dropped, will be used to unregister all name information from this map.
|
||||
pub fn register(
|
||||
&self,
|
||||
names: &Arc<Names>,
|
||||
module: &CompiledModule,
|
||||
) -> Option<GlobalFrameInfoRegistration> {
|
||||
let mut min = usize::max_value();
|
||||
let mut max = 0;
|
||||
let mut functions = BTreeMap::new();
|
||||
for (i, allocated) in module.finished_functions() {
|
||||
let (start, end) = unsafe {
|
||||
let ptr = (**allocated).as_ptr();
|
||||
let len = (**allocated).len();
|
||||
(ptr as usize, ptr as usize + len)
|
||||
};
|
||||
if start < min {
|
||||
min = start;
|
||||
}
|
||||
if end > max {
|
||||
max = end;
|
||||
}
|
||||
let func_index = module.module().local.func_index(i);
|
||||
assert!(functions.insert(end, (start, func_index)).is_none());
|
||||
}
|
||||
if functions.len() == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut ranges = self.ranges.write().unwrap();
|
||||
// First up assert that our chunk of jit functions doesn't collide with
|
||||
// any other known chunks of jit functions...
|
||||
if let Some((_, prev)) = ranges.range(max..).next() {
|
||||
assert!(prev.start > max);
|
||||
}
|
||||
if let Some((prev_end, _)) = ranges.range(..=min).next_back() {
|
||||
assert!(*prev_end < min);
|
||||
}
|
||||
|
||||
// ... then insert our range and assert nothing was there previously
|
||||
let prev = ranges.insert(
|
||||
max,
|
||||
ModuleFrameInfo {
|
||||
start: min,
|
||||
functions,
|
||||
names: names.clone(),
|
||||
},
|
||||
);
|
||||
assert!(prev.is_none());
|
||||
Some(GlobalFrameInfoRegistration { key: max })
|
||||
}
|
||||
|
||||
/// Fetches information about a program counter in a backtrace.
|
||||
///
|
||||
/// Returns an object if this `pc` is known to some previously registered
|
||||
/// module, or returns `None` if no information can be found.
|
||||
pub fn lookup(&self, pc: usize) -> Option<FrameInfo> {
|
||||
let ranges = self.ranges.read().ok()?;
|
||||
let (end, info) = ranges.range(pc..).next()?;
|
||||
if pc < info.start || *end < pc {
|
||||
return None;
|
||||
}
|
||||
let (end, (start, func_index)) = info.functions.range(pc..).next()?;
|
||||
if pc < *start || *end < pc {
|
||||
return None;
|
||||
}
|
||||
Some(FrameInfo {
|
||||
module_name: info.names.module_name.clone(),
|
||||
func_index: func_index.index() as u32,
|
||||
func_name: info.names.module.func_names.get(func_index).cloned(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for GlobalFrameInfoRegistration {
|
||||
fn drop(&mut self) {
|
||||
if let Ok(mut map) = FRAME_INFO.ranges.write() {
|
||||
map.remove(&self.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Description of a frame in a backtrace for a [`Trap`].
|
||||
///
|
||||
/// Whenever a WebAssembly trap occurs an instance of [`Trap`] is created. Each
|
||||
/// [`Trap`] has a backtrace of the WebAssembly frames that led to the trap, and
|
||||
/// each frame is described by this structure.
|
||||
#[derive(Debug)]
|
||||
pub struct FrameInfo {
|
||||
module_name: Option<String>,
|
||||
func_index: u32,
|
||||
func_name: Option<String>,
|
||||
}
|
||||
|
||||
impl FrameInfo {
|
||||
/// Returns the WebAssembly function index for this frame.
|
||||
///
|
||||
/// This function index is the index in the function index space of the
|
||||
/// WebAssembly module that this frame comes from.
|
||||
pub fn func_index(&self) -> u32 {
|
||||
self.func_index
|
||||
}
|
||||
|
||||
/// Returns the identifer of the module that this frame is for.
|
||||
///
|
||||
/// Module identifiers are present in the `name` section of a WebAssembly
|
||||
/// binary, but this may not return the exact item in the `name` section.
|
||||
/// Module names can be overwritten at construction time or perhaps inferred
|
||||
/// from file names. The primary purpose of this function is to assist in
|
||||
/// debugging and therefore may be tweaked over time.
|
||||
///
|
||||
/// This function returns `None` when no name can be found or inferred.
|
||||
pub fn module_name(&self) -> Option<&str> {
|
||||
self.module_name.as_deref()
|
||||
}
|
||||
|
||||
/// Returns a descriptive name of the function for this frame, if one is
|
||||
/// available.
|
||||
///
|
||||
/// The name of this function may come from the `name` section of the
|
||||
/// WebAssembly binary, or wasmtime may try to infer a better name for it if
|
||||
/// not available, for example the name of the export if it's exported.
|
||||
///
|
||||
/// This return value is primarily used for debugging and human-readable
|
||||
/// purposes for things like traps. Note that the exact return value may be
|
||||
/// tweaked over time here and isn't guaranteed to be something in
|
||||
/// particular about a wasm module due to its primary purpose of assisting
|
||||
/// in debugging.
|
||||
///
|
||||
/// This function returns `None` when no name could be inferred.
|
||||
pub fn func_name(&self) -> Option<&str> {
|
||||
self.func_name.as_deref()
|
||||
}
|
||||
}
|
||||
621
crates/api/src/func.rs
Normal file
621
crates/api/src/func.rs
Normal file
@@ -0,0 +1,621 @@
|
||||
use crate::callable::{NativeCallable, WasmtimeFn, WrappedCallable};
|
||||
use crate::{Callable, FuncType, Store, Trap, Val, ValType};
|
||||
use anyhow::{ensure, Context as _};
|
||||
use std::fmt;
|
||||
use std::mem;
|
||||
use std::panic::{self, AssertUnwindSafe};
|
||||
use std::ptr;
|
||||
use std::rc::Rc;
|
||||
use wasmtime_runtime::{InstanceHandle, VMContext, VMFunctionBody};
|
||||
|
||||
/// A WebAssembly function which can be called.
|
||||
///
|
||||
/// This type can represent a number of callable items, such as:
|
||||
///
|
||||
/// * An exported function from a WebAssembly module.
|
||||
/// * A user-defined function used to satisfy an import.
|
||||
///
|
||||
/// These types of callable items are all wrapped up in this `Func` and can be
|
||||
/// used to both instantiate an [`Instance`](crate::Instance) as well as be
|
||||
/// extracted from an [`Instance`](crate::Instance).
|
||||
///
|
||||
/// # `Func` and `Clone`
|
||||
///
|
||||
/// Functions are internally reference counted so you can `clone` a `Func`. The
|
||||
/// cloning process only performs a shallow clone, so two cloned `Func`
|
||||
/// instances are equivalent in their functionality.
|
||||
#[derive(Clone)]
|
||||
pub struct Func {
|
||||
_store: Store,
|
||||
callable: Rc<dyn WrappedCallable + 'static>,
|
||||
ty: FuncType,
|
||||
}
|
||||
|
||||
macro_rules! wrappers {
|
||||
($(
|
||||
$(#[$doc:meta])*
|
||||
($name:ident $(,$args:ident)*)
|
||||
)*) => ($(
|
||||
$(#[$doc])*
|
||||
pub fn $name<F, $($args,)* R>(store: &Store, func: F) -> Func
|
||||
where
|
||||
F: Fn($($args),*) -> R + 'static,
|
||||
$($args: WasmTy,)*
|
||||
R: WasmRet,
|
||||
{
|
||||
#[allow(non_snake_case)]
|
||||
unsafe extern "C" fn shim<F, $($args,)* R>(
|
||||
vmctx: *mut VMContext,
|
||||
_caller_vmctx: *mut VMContext,
|
||||
$($args: $args::Abi,)*
|
||||
) -> R::Abi
|
||||
where
|
||||
F: Fn($($args),*) -> R + 'static,
|
||||
$($args: WasmTy,)*
|
||||
R: WasmRet,
|
||||
{
|
||||
let ret = {
|
||||
let instance = InstanceHandle::from_vmctx(vmctx);
|
||||
let func = instance.host_state().downcast_ref::<F>().expect("state");
|
||||
panic::catch_unwind(AssertUnwindSafe(|| {
|
||||
func($($args::from_abi(_caller_vmctx, $args)),*)
|
||||
}))
|
||||
};
|
||||
match ret {
|
||||
Ok(ret) => ret.into_abi(),
|
||||
Err(panic) => wasmtime_runtime::resume_panic(panic),
|
||||
}
|
||||
}
|
||||
|
||||
let mut _args = Vec::new();
|
||||
$($args::push(&mut _args);)*
|
||||
let mut ret = Vec::new();
|
||||
R::push(&mut ret);
|
||||
let ty = FuncType::new(_args.into(), ret.into());
|
||||
unsafe {
|
||||
let (instance, export) = crate::trampoline::generate_raw_func_export(
|
||||
&ty,
|
||||
std::slice::from_raw_parts_mut(
|
||||
shim::<F, $($args,)* R> as *mut _,
|
||||
0,
|
||||
),
|
||||
store,
|
||||
Box::new(func),
|
||||
)
|
||||
.expect("failed to generate export");
|
||||
let callable = Rc::new(WasmtimeFn::new(store, instance, export));
|
||||
Func::from_wrapped(store, ty, callable)
|
||||
}
|
||||
}
|
||||
)*)
|
||||
}
|
||||
|
||||
macro_rules! getters {
|
||||
($(
|
||||
$(#[$doc:meta])*
|
||||
($name:ident $(,$args:ident)*)
|
||||
)*) => ($(
|
||||
$(#[$doc])*
|
||||
#[allow(non_snake_case)]
|
||||
pub fn $name<$($args,)* R>(&self)
|
||||
-> anyhow::Result<impl Fn($($args,)*) -> Result<R, Trap>>
|
||||
where
|
||||
$($args: WasmTy,)*
|
||||
R: WasmTy,
|
||||
{
|
||||
// Verify all the paramers match the expected parameters, and that
|
||||
// there are no extra parameters...
|
||||
let mut params = self.ty().params().iter().cloned();
|
||||
let n = 0;
|
||||
$(
|
||||
let n = n + 1;
|
||||
$args::matches(&mut params)
|
||||
.with_context(|| format!("Type mismatch in argument {}", n))?;
|
||||
)*
|
||||
ensure!(params.next().is_none(), "Type mismatch: too many arguments (expected {})", n);
|
||||
|
||||
// ... then do the same for the results...
|
||||
let mut results = self.ty().results().iter().cloned();
|
||||
R::matches(&mut results)
|
||||
.context("Type mismatch in return type")?;
|
||||
ensure!(results.next().is_none(), "Type mismatch: too many return values (expected 1)");
|
||||
|
||||
// ... and then once we've passed the typechecks we can hand out our
|
||||
// object since our `transmute` below should be safe!
|
||||
let (address, vmctx) = match self.wasmtime_export() {
|
||||
wasmtime_runtime::Export::Function { address, vmctx, signature: _} => {
|
||||
(*address, *vmctx)
|
||||
}
|
||||
_ => panic!("expected function export"),
|
||||
};
|
||||
Ok(move |$($args: $args),*| -> Result<R, Trap> {
|
||||
unsafe {
|
||||
let f = mem::transmute::<
|
||||
*const VMFunctionBody,
|
||||
unsafe extern "C" fn(
|
||||
*mut VMContext,
|
||||
*mut VMContext,
|
||||
$($args::Abi,)*
|
||||
) -> R::Abi,
|
||||
>(address);
|
||||
let mut ret = None;
|
||||
$(let $args = $args.into_abi();)*
|
||||
wasmtime_runtime::catch_traps(vmctx, || {
|
||||
ret = Some(f(vmctx, ptr::null_mut(), $($args,)*));
|
||||
}).map_err(Trap::from_jit)?;
|
||||
Ok(R::from_abi(vmctx, ret.unwrap()))
|
||||
}
|
||||
})
|
||||
}
|
||||
)*)
|
||||
}
|
||||
|
||||
impl Func {
|
||||
/// Creates a new `Func` with the given arguments, typically to create a
|
||||
/// user-defined function to pass as an import to a module.
|
||||
///
|
||||
/// * `store` - a cache of data where information is stored, typically
|
||||
/// shared with a [`Module`](crate::Module).
|
||||
///
|
||||
/// * `ty` - the signature of this function, used to indicate what the
|
||||
/// inputs and outputs are, which must be WebAssembly types.
|
||||
///
|
||||
/// * `callable` - a type implementing the [`Callable`] trait which
|
||||
/// is the implementation of this `Func` value.
|
||||
///
|
||||
/// Note that the implementation of `callable` must adhere to the `ty`
|
||||
/// signature given, error or traps may occur if it does not respect the
|
||||
/// `ty` signature.
|
||||
pub fn new(store: &Store, ty: FuncType, callable: Rc<dyn Callable + 'static>) -> Self {
|
||||
let callable = Rc::new(NativeCallable::new(callable, &ty, &store));
|
||||
Func::from_wrapped(store, ty, callable)
|
||||
}
|
||||
|
||||
wrappers! {
|
||||
/// Creates a new `Func` from the given Rust closure, which takes 0
|
||||
/// arguments.
|
||||
///
|
||||
/// For more information about this function, see [`Func::wrap1`].
|
||||
(wrap0)
|
||||
|
||||
/// Creates a new `Func` from the given Rust closure, which takes 1
|
||||
/// argument.
|
||||
///
|
||||
/// This function will create a new `Func` which, when called, will
|
||||
/// execute the given Rust closure. Unlike [`Func::new`] the target
|
||||
/// function being called is known statically so the type signature can
|
||||
/// be inferred. Rust types will map to WebAssembly types as follows:
|
||||
///
|
||||
///
|
||||
/// | Rust Argument Type | WebAssembly Type |
|
||||
/// |--------------------|------------------|
|
||||
/// | `i32` | `i32` |
|
||||
/// | `i64` | `i64` |
|
||||
/// | `f32` | `f32` |
|
||||
/// | `f64` | `f64` |
|
||||
/// | (not supported) | `v128` |
|
||||
/// | (not supported) | `anyref` |
|
||||
///
|
||||
/// Any of the Rust types can be returned from the closure as well, in
|
||||
/// addition to some extra types
|
||||
///
|
||||
/// | Rust Return Type | WebAssembly Return Type | Meaning |
|
||||
/// |-------------------|-------------------------|-------------------|
|
||||
/// | `()` | nothing | no return value |
|
||||
/// | `Result<T, Trap>` | `T` | function may trap |
|
||||
///
|
||||
/// Note that when using this API (and the related `wrap*` family of
|
||||
/// functions), the intention is to create as thin of a layer as
|
||||
/// possible for when WebAssembly calls the function provided. With
|
||||
/// sufficient inlining and optimization the WebAssembly will call
|
||||
/// straight into `func` provided, with no extra fluff entailed.
|
||||
(wrap1, A1)
|
||||
|
||||
/// Creates a new `Func` from the given Rust closure, which takes 2
|
||||
/// arguments.
|
||||
///
|
||||
/// For more information about this function, see [`Func::wrap1`].
|
||||
(wrap2, A1, A2)
|
||||
|
||||
/// Creates a new `Func` from the given Rust closure, which takes 3
|
||||
/// arguments.
|
||||
///
|
||||
/// For more information about this function, see [`Func::wrap1`].
|
||||
(wrap3, A1, A2, A3)
|
||||
|
||||
/// Creates a new `Func` from the given Rust closure, which takes 4
|
||||
/// arguments.
|
||||
///
|
||||
/// For more information about this function, see [`Func::wrap1`].
|
||||
(wrap4, A1, A2, A3, A4)
|
||||
|
||||
/// Creates a new `Func` from the given Rust closure, which takes 5
|
||||
/// arguments.
|
||||
///
|
||||
/// For more information about this function, see [`Func::wrap1`].
|
||||
(wrap5, A1, A2, A3, A4, A5)
|
||||
|
||||
/// Creates a new `Func` from the given Rust closure, which takes 6
|
||||
/// arguments.
|
||||
///
|
||||
/// For more information about this function, see [`Func::wrap1`].
|
||||
(wrap6, A1, A2, A3, A4, A5, A6)
|
||||
|
||||
/// Creates a new `Func` from the given Rust closure, which takes 7
|
||||
/// arguments.
|
||||
///
|
||||
/// For more information about this function, see [`Func::wrap1`].
|
||||
(wrap7, A1, A2, A3, A4, A5, A6, A7)
|
||||
|
||||
/// Creates a new `Func` from the given Rust closure, which takes 8
|
||||
/// arguments.
|
||||
///
|
||||
/// For more information about this function, see [`Func::wrap1`].
|
||||
(wrap8, A1, A2, A3, A4, A5, A6, A7, A8)
|
||||
|
||||
/// Creates a new `Func` from the given Rust closure, which takes 9
|
||||
/// arguments.
|
||||
///
|
||||
/// For more information about this function, see [`Func::wrap1`].
|
||||
(wrap9, A1, A2, A3, A4, A5, A6, A7, A8, A9)
|
||||
|
||||
/// Creates a new `Func` from the given Rust closure, which takes 10
|
||||
/// arguments.
|
||||
///
|
||||
/// For more information about this function, see [`Func::wrap1`].
|
||||
(wrap10, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10)
|
||||
}
|
||||
|
||||
fn from_wrapped(
|
||||
store: &Store,
|
||||
ty: FuncType,
|
||||
callable: Rc<dyn WrappedCallable + 'static>,
|
||||
) -> Func {
|
||||
Func {
|
||||
_store: store.clone(),
|
||||
callable,
|
||||
ty,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the underlying wasm type that this `Func` has.
|
||||
pub fn ty(&self) -> &FuncType {
|
||||
&self.ty
|
||||
}
|
||||
|
||||
/// Returns the number of parameters that this function takes.
|
||||
pub fn param_arity(&self) -> usize {
|
||||
self.ty.params().len()
|
||||
}
|
||||
|
||||
/// Returns the number of results this function produces.
|
||||
pub fn result_arity(&self) -> usize {
|
||||
self.ty.results().len()
|
||||
}
|
||||
|
||||
/// Invokes this function with the `params` given, returning the results and
|
||||
/// any trap, if one occurs.
|
||||
///
|
||||
/// The `params` here must match the type signature of this `Func`, or a
|
||||
/// trap will occur. If a trap occurs while executing this function, then a
|
||||
/// trap will also be returned.
|
||||
///
|
||||
/// This function should not panic unless the underlying function itself
|
||||
/// initiates a panic.
|
||||
pub fn call(&self, params: &[Val]) -> Result<Box<[Val]>, Trap> {
|
||||
let mut results = vec![Val::null(); self.result_arity()];
|
||||
self.callable.call(params, &mut results)?;
|
||||
Ok(results.into_boxed_slice())
|
||||
}
|
||||
|
||||
pub(crate) fn wasmtime_export(&self) -> &wasmtime_runtime::Export {
|
||||
self.callable.wasmtime_export()
|
||||
}
|
||||
|
||||
pub(crate) fn from_wasmtime_function(
|
||||
export: wasmtime_runtime::Export,
|
||||
store: &Store,
|
||||
instance_handle: InstanceHandle,
|
||||
) -> Self {
|
||||
// This is only called with `Export::Function`, and since it's coming
|
||||
// from wasmtime_runtime itself we should support all the types coming
|
||||
// out of it, so assert such here.
|
||||
let ty = if let wasmtime_runtime::Export::Function { signature, .. } = &export {
|
||||
FuncType::from_wasmtime_signature(signature.clone())
|
||||
.expect("core wasm signature should be supported")
|
||||
} else {
|
||||
panic!("expected function export")
|
||||
};
|
||||
let callable = WasmtimeFn::new(store, instance_handle, export);
|
||||
Func::from_wrapped(store, ty, Rc::new(callable))
|
||||
}
|
||||
|
||||
getters! {
|
||||
/// Extracts a natively-callable object from this `Func`, if the
|
||||
/// signature matches.
|
||||
///
|
||||
/// See the [`Func::get1`] method for more documentation.
|
||||
(get0)
|
||||
|
||||
/// Extracts a natively-callable object from this `Func`, if the
|
||||
/// signature matches.
|
||||
///
|
||||
/// This function serves as an optimized version of the [`Func::call`]
|
||||
/// method if the type signature of a function is statically known to
|
||||
/// the program. This method is faster than `call` on a few metrics:
|
||||
///
|
||||
/// * Runtime type-checking only happens once, when this method is
|
||||
/// called.
|
||||
/// * The result values, if any, aren't boxed into a vector.
|
||||
/// * Arguments and return values don't go through boxing and unboxing.
|
||||
/// * No trampolines are used to transfer control flow to/from JIT code,
|
||||
/// instead this function jumps directly into JIT code.
|
||||
///
|
||||
/// For more information about which Rust types match up to which wasm
|
||||
/// types, see the documentation on [`Func::wrap1`].
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// This function will return `None` if the type signature asserted
|
||||
/// statically does not match the runtime type signature. `Some`,
|
||||
/// however, will be returned if the underlying function takes one
|
||||
/// parameter of type `A` and returns the parameter `R`. Currently `R`
|
||||
/// can either be `()` (no return values) or one wasm type. At this time
|
||||
/// a multi-value return isn't supported.
|
||||
///
|
||||
/// The returned closure will always return a `Result<R, Trap>` and an
|
||||
/// `Err` is returned if a trap happens while the wasm is executing.
|
||||
(get1, A1)
|
||||
|
||||
/// Extracts a natively-callable object from this `Func`, if the
|
||||
/// signature matches.
|
||||
///
|
||||
/// See the [`Func::get1`] method for more documentation.
|
||||
(get2, A1, A2)
|
||||
|
||||
/// Extracts a natively-callable object from this `Func`, if the
|
||||
/// signature matches.
|
||||
///
|
||||
/// See the [`Func::get1`] method for more documentation.
|
||||
(get3, A1, A2, A3)
|
||||
|
||||
/// Extracts a natively-callable object from this `Func`, if the
|
||||
/// signature matches.
|
||||
///
|
||||
/// See the [`Func::get1`] method for more documentation.
|
||||
(get4, A1, A2, A3, A4)
|
||||
|
||||
/// Extracts a natively-callable object from this `Func`, if the
|
||||
/// signature matches.
|
||||
///
|
||||
/// See the [`Func::get1`] method for more documentation.
|
||||
(get5, A1, A2, A3, A4, A5)
|
||||
|
||||
/// Extracts a natively-callable object from this `Func`, if the
|
||||
/// signature matches.
|
||||
///
|
||||
/// See the [`Func::get1`] method for more documentation.
|
||||
(get6, A1, A2, A3, A4, A5, A6)
|
||||
|
||||
/// Extracts a natively-callable object from this `Func`, if the
|
||||
/// signature matches.
|
||||
///
|
||||
/// See the [`Func::get1`] method for more documentation.
|
||||
(get7, A1, A2, A3, A4, A5, A6, A7)
|
||||
|
||||
/// Extracts a natively-callable object from this `Func`, if the
|
||||
/// signature matches.
|
||||
///
|
||||
/// See the [`Func::get1`] method for more documentation.
|
||||
(get8, A1, A2, A3, A4, A5, A6, A7, A8)
|
||||
|
||||
/// Extracts a natively-callable object from this `Func`, if the
|
||||
/// signature matches.
|
||||
///
|
||||
/// See the [`Func::get1`] method for more documentation.
|
||||
(get9, A1, A2, A3, A4, A5, A6, A7, A8, A9)
|
||||
|
||||
/// Extracts a natively-callable object from this `Func`, if the
|
||||
/// signature matches.
|
||||
///
|
||||
/// See the [`Func::get1`] method for more documentation.
|
||||
(get10, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Func {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Func")
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait implemented for types which can be arguments to closures passed to
|
||||
/// [`Func::wrap1`] and friends.
|
||||
///
|
||||
/// This trait should not be implemented by user types. This trait may change at
|
||||
/// any time internally. The types which implement this trait, however, are
|
||||
/// stable over time.
|
||||
///
|
||||
/// For more information see [`Func::wrap1`]
|
||||
pub trait WasmTy {
|
||||
#[doc(hidden)]
|
||||
type Abi: Copy;
|
||||
#[doc(hidden)]
|
||||
fn push(dst: &mut Vec<ValType>);
|
||||
#[doc(hidden)]
|
||||
fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()>;
|
||||
#[doc(hidden)]
|
||||
fn from_abi(vmctx: *mut VMContext, abi: Self::Abi) -> Self;
|
||||
#[doc(hidden)]
|
||||
fn into_abi(self) -> Self::Abi;
|
||||
}
|
||||
|
||||
impl WasmTy for () {
|
||||
type Abi = ();
|
||||
fn push(_dst: &mut Vec<ValType>) {}
|
||||
fn matches(_tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
#[inline]
|
||||
fn from_abi(_vmctx: *mut VMContext, abi: Self::Abi) -> Self {
|
||||
abi
|
||||
}
|
||||
#[inline]
|
||||
fn into_abi(self) -> Self::Abi {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl WasmTy for i32 {
|
||||
type Abi = Self;
|
||||
fn push(dst: &mut Vec<ValType>) {
|
||||
dst.push(ValType::I32);
|
||||
}
|
||||
fn matches(mut tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
|
||||
let next = tys.next();
|
||||
ensure!(
|
||||
next == Some(ValType::I32),
|
||||
"Type mismatch, expected i32, got {:?}",
|
||||
next
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
#[inline]
|
||||
fn from_abi(_vmctx: *mut VMContext, abi: Self::Abi) -> Self {
|
||||
abi
|
||||
}
|
||||
#[inline]
|
||||
fn into_abi(self) -> Self::Abi {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl WasmTy for i64 {
|
||||
type Abi = Self;
|
||||
fn push(dst: &mut Vec<ValType>) {
|
||||
dst.push(ValType::I64);
|
||||
}
|
||||
fn matches(mut tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
|
||||
let next = tys.next();
|
||||
ensure!(
|
||||
next == Some(ValType::I64),
|
||||
"Type mismatch, expected i64, got {:?}",
|
||||
next
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
#[inline]
|
||||
fn from_abi(_vmctx: *mut VMContext, abi: Self::Abi) -> Self {
|
||||
abi
|
||||
}
|
||||
#[inline]
|
||||
fn into_abi(self) -> Self::Abi {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl WasmTy for f32 {
|
||||
type Abi = Self;
|
||||
fn push(dst: &mut Vec<ValType>) {
|
||||
dst.push(ValType::F32);
|
||||
}
|
||||
fn matches(mut tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
|
||||
let next = tys.next();
|
||||
ensure!(
|
||||
next == Some(ValType::F32),
|
||||
"Type mismatch, expected f32, got {:?}",
|
||||
next
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
#[inline]
|
||||
fn from_abi(_vmctx: *mut VMContext, abi: Self::Abi) -> Self {
|
||||
abi
|
||||
}
|
||||
#[inline]
|
||||
fn into_abi(self) -> Self::Abi {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl WasmTy for f64 {
|
||||
type Abi = Self;
|
||||
fn push(dst: &mut Vec<ValType>) {
|
||||
dst.push(ValType::F64);
|
||||
}
|
||||
fn matches(mut tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
|
||||
let next = tys.next();
|
||||
ensure!(
|
||||
next == Some(ValType::F64),
|
||||
"Type mismatch, expected f64, got {:?}",
|
||||
next
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
#[inline]
|
||||
fn from_abi(_vmctx: *mut VMContext, abi: Self::Abi) -> Self {
|
||||
abi
|
||||
}
|
||||
#[inline]
|
||||
fn into_abi(self) -> Self::Abi {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait implemented for types which can be returned from closures passed to
|
||||
/// [`Func::wrap1`] and friends.
|
||||
///
|
||||
/// This trait should not be implemented by user types. This trait may change at
|
||||
/// any time internally. The types which implement this trait, however, are
|
||||
/// stable over time.
|
||||
///
|
||||
/// For more information see [`Func::wrap1`]
|
||||
pub trait WasmRet {
|
||||
#[doc(hidden)]
|
||||
type Abi;
|
||||
#[doc(hidden)]
|
||||
fn push(dst: &mut Vec<ValType>);
|
||||
#[doc(hidden)]
|
||||
fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()>;
|
||||
#[doc(hidden)]
|
||||
fn into_abi(self) -> Self::Abi;
|
||||
}
|
||||
|
||||
impl<T: WasmTy> WasmRet for T {
|
||||
type Abi = T::Abi;
|
||||
fn push(dst: &mut Vec<ValType>) {
|
||||
T::push(dst)
|
||||
}
|
||||
|
||||
fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
|
||||
T::matches(tys)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn into_abi(self) -> Self::Abi {
|
||||
T::into_abi(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: WasmTy> WasmRet for Result<T, Trap> {
|
||||
type Abi = T::Abi;
|
||||
fn push(dst: &mut Vec<ValType>) {
|
||||
T::push(dst)
|
||||
}
|
||||
|
||||
fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
|
||||
T::matches(tys)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn into_abi(self) -> Self::Abi {
|
||||
match self {
|
||||
Ok(val) => return val.into_abi(),
|
||||
Err(trap) => handle_trap(trap),
|
||||
}
|
||||
|
||||
fn handle_trap(trap: Trap) -> ! {
|
||||
unsafe { wasmtime_runtime::raise_user_trap(Box::new(trap)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
186
crates/api/src/instance.rs
Normal file
186
crates/api/src/instance.rs
Normal file
@@ -0,0 +1,186 @@
|
||||
use crate::externals::Extern;
|
||||
use crate::module::Module;
|
||||
use crate::runtime::{Config, Store};
|
||||
use crate::trap::Trap;
|
||||
use anyhow::{Error, Result};
|
||||
use wasmtime_jit::{CompiledModule, Resolver};
|
||||
use wasmtime_runtime::{Export, InstanceHandle, InstantiationError};
|
||||
|
||||
struct SimpleResolver<'a> {
|
||||
imports: &'a [Extern],
|
||||
}
|
||||
|
||||
impl Resolver for SimpleResolver<'_> {
|
||||
fn resolve(&mut self, idx: u32, _name: &str, _field: &str) -> Option<Export> {
|
||||
self.imports
|
||||
.get(idx as usize)
|
||||
.map(|i| i.get_wasmtime_export())
|
||||
}
|
||||
}
|
||||
|
||||
fn instantiate(
|
||||
config: &Config,
|
||||
compiled_module: &CompiledModule,
|
||||
imports: &[Extern],
|
||||
) -> Result<InstanceHandle, Error> {
|
||||
let mut resolver = SimpleResolver { imports };
|
||||
unsafe {
|
||||
let instance = compiled_module
|
||||
.instantiate(
|
||||
config.validating_config.operator_config.enable_bulk_memory,
|
||||
&mut resolver,
|
||||
)
|
||||
.map_err(|e| -> Error {
|
||||
match e {
|
||||
InstantiationError::StartTrap(trap) | InstantiationError::Trap(trap) => {
|
||||
Trap::from_jit(trap).into()
|
||||
}
|
||||
other => other.into(),
|
||||
}
|
||||
})?;
|
||||
Ok(instance)
|
||||
}
|
||||
}
|
||||
|
||||
/// An instantiated WebAssembly module.
|
||||
///
|
||||
/// This type represents the instantiation of a [`Module`]. Once instantiated
|
||||
/// you can access the [`exports`](Instance::exports) which are of type
|
||||
/// [`Extern`] and provide the ability to call functions, set globals, read
|
||||
/// memory, etc. This is where all the fun stuff happens!
|
||||
///
|
||||
/// An [`Instance`] is created from two inputs, a [`Module`] and a list of
|
||||
/// imports, provided as a list of [`Extern`] values. The [`Module`] is the wasm
|
||||
/// code that was compiled and we're instantiating, and the [`Extern`] imports
|
||||
/// are how we're satisfying the imports of the module provided. On successful
|
||||
/// instantiation an [`Instance`] will automatically invoke the wasm `start`
|
||||
/// function.
|
||||
///
|
||||
/// When interacting with any wasm code you'll want to make an [`Instance`] to
|
||||
/// call any code or execute anything!
|
||||
#[derive(Clone)]
|
||||
pub struct Instance {
|
||||
pub(crate) instance_handle: InstanceHandle,
|
||||
module: Module,
|
||||
exports: Box<[Extern]>,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
/// Creates a new [`Instance`] from the previously compiled [`Module`] and
|
||||
/// list of `imports` specified.
|
||||
///
|
||||
/// This method instantiates the `module` provided with the `imports`,
|
||||
/// following the procedure in the [core specification][inst] to
|
||||
/// instantiate. Instantiation can fail for a number of reasons (many
|
||||
/// specified below), but if successful the `start` function will be
|
||||
/// automatically run (if provided) and then the [`Instance`] will be
|
||||
/// returned.
|
||||
///
|
||||
/// ## Providing Imports
|
||||
///
|
||||
/// The `imports` array here is a bit tricky. The entries in the list of
|
||||
/// `imports` are intended to correspond 1:1 with the list of imports
|
||||
/// returned by [`Module::imports`]. Before calling [`Instance::new`] you'll
|
||||
/// want to inspect the return value of [`Module::imports`] and, for each
|
||||
/// import type, create an [`Extern`] which corresponds to that type.
|
||||
/// These [`Extern`] values are all then collected into a list and passed to
|
||||
/// this function.
|
||||
///
|
||||
/// Note that this function is intentionally relatively low level. It is the
|
||||
/// intention that we'll soon provide a [higher level API][issue] which will
|
||||
/// be much more ergonomic for instantiating modules. If you need the full
|
||||
/// power of customization of imports, though, this is the method for you!
|
||||
///
|
||||
/// ## Errors
|
||||
///
|
||||
/// This function can fail for a number of reasons, including, but not
|
||||
/// limited to:
|
||||
///
|
||||
/// * The number of `imports` provided doesn't match the number of imports
|
||||
/// returned by the `module`'s [`Module::imports`] method.
|
||||
/// * The type of any [`Extern`] doesn't match the corresponding
|
||||
/// [`ExternType`] entry that it maps to.
|
||||
/// * The `start` function in the instance, if present, traps.
|
||||
/// * Module/instance resource limits are exceeded.
|
||||
///
|
||||
/// When instantiation fails it's recommended to inspect the return value to
|
||||
/// see why it failed, or bubble it upwards. If you'd like to specifically
|
||||
/// check for trap errors, you can use `error.downcast::<Trap>()`.
|
||||
///
|
||||
/// [inst]: https://webassembly.github.io/spec/core/exec/modules.html#exec-instantiation
|
||||
/// [issue]: https://github.com/bytecodealliance/wasmtime/issues/727
|
||||
pub fn new(module: &Module, imports: &[Extern]) -> Result<Instance, Error> {
|
||||
let store = module.store();
|
||||
let config = store.engine().config();
|
||||
let instance_handle = instantiate(config, module.compiled_module(), imports)?;
|
||||
|
||||
let exports = {
|
||||
let mut exports = Vec::with_capacity(module.exports().len());
|
||||
for export in module.exports() {
|
||||
let name = export.name().to_string();
|
||||
let export = instance_handle.lookup(&name).expect("export");
|
||||
exports.push(Extern::from_wasmtime_export(
|
||||
store,
|
||||
instance_handle.clone(),
|
||||
export,
|
||||
));
|
||||
}
|
||||
exports.into_boxed_slice()
|
||||
};
|
||||
module.register_frame_info();
|
||||
Ok(Instance {
|
||||
instance_handle,
|
||||
module: module.clone(),
|
||||
exports,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the associated [`Store`] that this `Instance` is compiled into.
|
||||
///
|
||||
/// This is the [`Store`] that generally serves as a sort of global cache
|
||||
/// for various instance-related things.
|
||||
pub fn store(&self) -> &Store {
|
||||
self.module.store()
|
||||
}
|
||||
|
||||
/// Returns the associated [`Module`] that this `Instance` instantiated.
|
||||
///
|
||||
/// The corresponding [`Module`] here is a static version of this `Instance`
|
||||
/// which can be used to learn information such as naming information about
|
||||
/// various functions.
|
||||
pub fn module(&self) -> &Module {
|
||||
&self.module
|
||||
}
|
||||
|
||||
/// Returns the list of exported items from this [`Instance`].
|
||||
///
|
||||
/// Note that the exports here do not have names associated with them,
|
||||
/// they're simply the values that are exported. To learn the value of each
|
||||
/// export you'll need to consult [`Module::exports`]. The list returned
|
||||
/// here maps 1:1 with the list that [`Module::exports`] returns, and
|
||||
/// [`ExportType`] contains the name of each export.
|
||||
pub fn exports(&self) -> &[Extern] {
|
||||
&self.exports
|
||||
}
|
||||
|
||||
/// Looks up an exported [`Extern`] value by name.
|
||||
///
|
||||
/// This method will search the module for an export named `name` and return
|
||||
/// the value, if found.
|
||||
///
|
||||
/// Returns `None` if there was no export named `name`.
|
||||
pub fn get_export(&self, name: &str) -> Option<&Extern> {
|
||||
let (i, _) = self
|
||||
.module
|
||||
.exports()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, e)| e.name() == name)?;
|
||||
Some(&self.exports()[i])
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn handle(&self) -> &InstanceHandle {
|
||||
&self.instance_handle
|
||||
}
|
||||
}
|
||||
44
crates/api/src/lib.rs
Normal file
44
crates/api/src/lib.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
//! Wasmtime's embedding API
|
||||
//!
|
||||
//! This crate contains a high-level API used to interact with WebAssembly
|
||||
//! modules. The API here is intended to mirror the proposed [WebAssembly C
|
||||
//! API](https://github.com/WebAssembly/wasm-c-api), with small extensions here
|
||||
//! and there to implement Rust idioms. This crate also defines the actual C API
|
||||
//! itself for consumption from other languages.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
mod callable;
|
||||
mod externals;
|
||||
mod frame_info;
|
||||
mod func;
|
||||
mod instance;
|
||||
mod module;
|
||||
mod r#ref;
|
||||
mod runtime;
|
||||
mod trampoline;
|
||||
mod trap;
|
||||
mod types;
|
||||
mod values;
|
||||
|
||||
pub use crate::callable::Callable;
|
||||
pub use crate::externals::*;
|
||||
pub use crate::frame_info::FrameInfo;
|
||||
pub use crate::func::{Func, WasmRet, WasmTy};
|
||||
pub use crate::instance::Instance;
|
||||
pub use crate::module::Module;
|
||||
pub use crate::r#ref::{AnyRef, HostInfo, HostRef};
|
||||
pub use crate::runtime::{Config, Engine, OptLevel, Store, Strategy};
|
||||
pub use crate::trap::Trap;
|
||||
pub use crate::types::*;
|
||||
pub use crate::values::*;
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(unix)] {
|
||||
pub mod unix;
|
||||
} else if #[cfg(windows)] {
|
||||
pub mod windows;
|
||||
} else {
|
||||
// ... unknown os!
|
||||
}
|
||||
}
|
||||
442
crates/api/src/module.rs
Normal file
442
crates/api/src/module.rs
Normal file
@@ -0,0 +1,442 @@
|
||||
use crate::frame_info::{GlobalFrameInfoRegistration, FRAME_INFO};
|
||||
use crate::runtime::Store;
|
||||
use crate::types::{
|
||||
ExportType, ExternType, FuncType, GlobalType, ImportType, Limits, MemoryType, Mutability,
|
||||
TableType, ValType,
|
||||
};
|
||||
use anyhow::{bail, Error, Result};
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use wasmparser::{
|
||||
validate, CustomSectionKind, ExternalKind, ImportSectionEntryType, ModuleReader, Name,
|
||||
SectionCode,
|
||||
};
|
||||
use wasmtime_jit::CompiledModule;
|
||||
|
||||
fn into_memory_type(mt: wasmparser::MemoryType) -> Result<MemoryType> {
|
||||
if mt.shared {
|
||||
bail!("shared memories are not supported yet");
|
||||
}
|
||||
Ok(MemoryType::new(Limits::new(
|
||||
mt.limits.initial,
|
||||
mt.limits.maximum,
|
||||
)))
|
||||
}
|
||||
|
||||
fn into_global_type(gt: wasmparser::GlobalType) -> GlobalType {
|
||||
let mutability = if gt.mutable {
|
||||
Mutability::Var
|
||||
} else {
|
||||
Mutability::Const
|
||||
};
|
||||
GlobalType::new(into_valtype(>.content_type), mutability)
|
||||
}
|
||||
|
||||
// `into_valtype` is used for `map` which requires `&T`.
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
fn into_valtype(ty: &wasmparser::Type) -> ValType {
|
||||
use wasmparser::Type::*;
|
||||
match ty {
|
||||
I32 => ValType::I32,
|
||||
I64 => ValType::I64,
|
||||
F32 => ValType::F32,
|
||||
F64 => ValType::F64,
|
||||
V128 => ValType::V128,
|
||||
AnyFunc => ValType::FuncRef,
|
||||
AnyRef => ValType::AnyRef,
|
||||
_ => unimplemented!("types in into_valtype"),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_func_type(mt: wasmparser::FuncType) -> FuncType {
|
||||
assert_eq!(mt.form, wasmparser::Type::Func);
|
||||
let params = mt.params.iter().map(into_valtype).collect::<Vec<_>>();
|
||||
let returns = mt.returns.iter().map(into_valtype).collect::<Vec<_>>();
|
||||
FuncType::new(params.into_boxed_slice(), returns.into_boxed_slice())
|
||||
}
|
||||
|
||||
fn into_table_type(tt: wasmparser::TableType) -> TableType {
|
||||
assert!(
|
||||
tt.element_type == wasmparser::Type::AnyFunc || tt.element_type == wasmparser::Type::AnyRef
|
||||
);
|
||||
let ty = into_valtype(&tt.element_type);
|
||||
let limits = Limits::new(tt.limits.initial, tt.limits.maximum);
|
||||
TableType::new(ty, limits)
|
||||
}
|
||||
|
||||
/// A compiled WebAssembly module, ready to be instantiated.
|
||||
///
|
||||
/// A `Module` is a compiled in-memory representation of an input WebAssembly
|
||||
/// binary. A `Module` is then used to create an [`Instance`](crate::Instance)
|
||||
/// through an instantiation process. You cannot call functions or fetch
|
||||
/// globals, for example, on a `Module` because it's purely a code
|
||||
/// representation. Instead you'll need to create an
|
||||
/// [`Instance`](crate::Instance) to interact with the wasm module.
|
||||
///
|
||||
/// ## Modules and `Clone`
|
||||
///
|
||||
/// Using `clone` on a `Module` is a cheap operation. It will not create an
|
||||
/// entirely new module, but rather just a new reference to the existing module.
|
||||
/// In other words it's a shallow copy, not a deep copy.
|
||||
#[derive(Clone)]
|
||||
pub struct Module {
|
||||
inner: Arc<ModuleInner>,
|
||||
}
|
||||
|
||||
struct ModuleInner {
|
||||
store: Store,
|
||||
imports: Box<[ImportType]>,
|
||||
exports: Box<[ExportType]>,
|
||||
compiled: CompiledModule,
|
||||
frame_info_registration: Mutex<Option<Option<GlobalFrameInfoRegistration>>>,
|
||||
names: Arc<Names>,
|
||||
}
|
||||
|
||||
pub struct Names {
|
||||
pub module: Arc<wasmtime_environ::Module>,
|
||||
pub module_name: Option<String>,
|
||||
}
|
||||
|
||||
impl Module {
|
||||
/// Creates a new WebAssembly `Module` from the given in-memory `bytes`.
|
||||
///
|
||||
/// The `bytes` provided must be in one of two formats:
|
||||
///
|
||||
/// * It can be a [binary-encoded][binary] WebAssembly module. This
|
||||
/// is always supported.
|
||||
/// * It may also be a [text-encoded][text] instance of the WebAssembly
|
||||
/// text format. This is only supported when the `wat` feature of this
|
||||
/// crate is enabled. If this is supplied then the text format will be
|
||||
/// parsed before validation. Note that the `wat` feature is enabled by
|
||||
/// default.
|
||||
///
|
||||
/// The data for the wasm module must be loaded in-memory if it's present
|
||||
/// elsewhere, for example on disk. This requires that the entire binary is
|
||||
/// loaded into memory all at once, this API does not support streaming
|
||||
/// compilation of a module.
|
||||
///
|
||||
/// The WebAssembly binary will be decoded and validated. It will also be
|
||||
/// compiled according to the configuration of the provided `store` and
|
||||
/// cached in this type.
|
||||
///
|
||||
/// The provided `store` is a global cache for compiled resources as well as
|
||||
/// configuration for what wasm features are enabled. It's recommended to
|
||||
/// share a `store` among modules if possible.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function may fail and return an error. Errors may include
|
||||
/// situations such as:
|
||||
///
|
||||
/// * The binary provided could not be decoded because it's not a valid
|
||||
/// WebAssembly binary
|
||||
/// * The WebAssembly binary may not validate (e.g. contains type errors)
|
||||
/// * Implementation-specific limits were exceeded with a valid binary (for
|
||||
/// example too many locals)
|
||||
/// * The wasm binary may use features that are not enabled in the
|
||||
/// configuration of `store`
|
||||
/// * If the `wat` feature is enabled and the input is text, then it may be
|
||||
/// rejected if it fails to parse.
|
||||
///
|
||||
/// The error returned should contain full information about why module
|
||||
/// creation failed if one is returned.
|
||||
///
|
||||
/// [binary]: https://webassembly.github.io/spec/core/binary/index.html
|
||||
/// [text]: https://webassembly.github.io/spec/core/text/index.html
|
||||
pub fn new(store: &Store, bytes: impl AsRef<[u8]>) -> Result<Module> {
|
||||
#[cfg(feature = "wat")]
|
||||
let bytes = wat::parse_bytes(bytes.as_ref())?;
|
||||
Module::from_binary(store, bytes.as_ref())
|
||||
}
|
||||
|
||||
/// Creates a new WebAssembly `Module` from the given in-memory `binary`
|
||||
/// data. The provided `name` will be used in traps/backtrace details.
|
||||
///
|
||||
/// See [`Module::new`] for other details.
|
||||
pub fn new_with_name(store: &Store, bytes: impl AsRef<[u8]>, name: &str) -> Result<Module> {
|
||||
let mut module = Module::new(store, bytes.as_ref())?;
|
||||
let inner = Arc::get_mut(&mut module.inner).unwrap();
|
||||
Arc::get_mut(&mut inner.names).unwrap().module_name = Some(name.to_string());
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
/// Creates a new WebAssembly `Module` from the contents of the given
|
||||
/// `file` on disk.
|
||||
///
|
||||
/// This is a convenience function that will read the `file` provided and
|
||||
/// pass the bytes to the [`Module::new`] function. For more information
|
||||
/// see [`Module::new`]
|
||||
pub fn from_file(store: &Store, file: impl AsRef<Path>) -> Result<Module> {
|
||||
#[cfg(feature = "wat")]
|
||||
let wasm = wat::parse_file(file)?;
|
||||
#[cfg(not(feature = "wat"))]
|
||||
let wasm = std::fs::read(file)?;
|
||||
Module::new(store, &wasm)
|
||||
}
|
||||
|
||||
/// Creates a new WebAssembly `Module` from the given in-memory `binary`
|
||||
/// data.
|
||||
///
|
||||
/// This is similar to [`Module::new`] except that it requires that the
|
||||
/// `binary` input is a WebAssembly binary, the text format is not supported
|
||||
/// by this function. It's generally recommended to use [`Module::new`],
|
||||
/// but if it's required to not support the text format this function can be
|
||||
/// used instead.
|
||||
pub fn from_binary(store: &Store, binary: &[u8]) -> Result<Module> {
|
||||
Module::validate(store, binary)?;
|
||||
// Note that the call to `from_binary_unchecked` here should be ok
|
||||
// because we previously validated the binary, meaning we're guaranteed
|
||||
// to pass a valid binary for `store`.
|
||||
unsafe { Module::from_binary_unchecked(store, binary) }
|
||||
}
|
||||
|
||||
/// Creates a new WebAssembly `Module` from the given in-memory `binary`
|
||||
/// data, skipping validation and asserting that `binary` is a valid
|
||||
/// WebAssembly module.
|
||||
///
|
||||
/// This function is the same as [`Module::new`] except that it skips the
|
||||
/// call to [`Module::validate`] and it does not support the text format of
|
||||
/// WebAssembly. The WebAssembly binary is not validated for
|
||||
/// correctness and it is simply assumed as valid.
|
||||
///
|
||||
/// For more information about creation of a module and the `store` argument
|
||||
/// see the documentation of [`Module::new`].
|
||||
///
|
||||
/// # Unsafety
|
||||
///
|
||||
/// This function is `unsafe` due to the unchecked assumption that the input
|
||||
/// `binary` is valid. If the `binary` is not actually a valid wasm binary it
|
||||
/// may cause invalid machine code to get generated, cause panics, etc.
|
||||
///
|
||||
/// It is only safe to call this method if [`Module::validate`] succeeds on
|
||||
/// the same arguments passed to this function.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function may fail for many of the same reasons as [`Module::new`].
|
||||
/// While this assumes that the binary is valid it still needs to actually
|
||||
/// be somewhat valid for decoding purposes, and the basics of decoding can
|
||||
/// still fail.
|
||||
pub unsafe fn from_binary_unchecked(store: &Store, binary: &[u8]) -> Result<Module> {
|
||||
let mut ret = Module::compile(store, binary)?;
|
||||
ret.read_imports_and_exports(binary)?;
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Validates `binary` input data as a WebAssembly binary given the
|
||||
/// configuration in `store`.
|
||||
///
|
||||
/// This function will perform a speedy validation of the `binary` input
|
||||
/// WebAssembly module (which is in [binary form][binary], the text format
|
||||
/// is not accepted by this function) and return either `Ok` or `Err`
|
||||
/// depending on the results of validation. The `store` argument indicates
|
||||
/// configuration for WebAssembly features, for example, which are used to
|
||||
/// indicate what should be valid and what shouldn't be.
|
||||
///
|
||||
/// Validation automatically happens as part of [`Module::new`], but is a
|
||||
/// requirement for [`Module::new_unchecked`] to be safe.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If validation fails for any reason (type check error, usage of a feature
|
||||
/// that wasn't enabled, etc) then an error with a description of the
|
||||
/// validation issue will be returned.
|
||||
///
|
||||
/// [binary]: https://webassembly.github.io/spec/core/binary/index.html
|
||||
pub fn validate(store: &Store, binary: &[u8]) -> Result<()> {
|
||||
let config = store.engine().config().validating_config.clone();
|
||||
validate(binary, Some(config)).map_err(Error::new)
|
||||
}
|
||||
|
||||
unsafe fn compile(store: &Store, binary: &[u8]) -> Result<Self> {
|
||||
let compiled = CompiledModule::new(
|
||||
&mut store.compiler_mut(),
|
||||
binary,
|
||||
store.engine().config().debug_info,
|
||||
store.engine().config().profiler.as_ref(),
|
||||
)?;
|
||||
|
||||
let names = Arc::new(Names {
|
||||
module_name: None,
|
||||
module: compiled.module().clone(),
|
||||
});
|
||||
Ok(Module {
|
||||
inner: Arc::new(ModuleInner {
|
||||
store: store.clone(),
|
||||
imports: Box::new([]),
|
||||
exports: Box::new([]),
|
||||
names,
|
||||
compiled,
|
||||
frame_info_registration: Mutex::new(None),
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compiled_module(&self) -> &CompiledModule {
|
||||
&self.inner.compiled
|
||||
}
|
||||
|
||||
/// Returns identifier/name that this [`Module`] has. This name
|
||||
/// is used in traps/backtrace details.
|
||||
pub fn name(&self) -> Option<&str> {
|
||||
self.inner.names.module_name.as_deref()
|
||||
}
|
||||
|
||||
/// Returns the list of imports that this [`Module`] has and must be
|
||||
/// satisfied.
|
||||
pub fn imports(&self) -> &[ImportType] {
|
||||
&self.inner.imports
|
||||
}
|
||||
|
||||
/// Returns the list of exports that this [`Module`] has and will be
|
||||
/// available after instantiation.
|
||||
pub fn exports(&self) -> &[ExportType] {
|
||||
&self.inner.exports
|
||||
}
|
||||
|
||||
/// Returns the [`Store`] that this [`Module`] was compiled into.
|
||||
pub fn store(&self) -> &Store {
|
||||
&self.inner.store
|
||||
}
|
||||
|
||||
fn read_imports_and_exports(&mut self, binary: &[u8]) -> Result<()> {
|
||||
let inner = Arc::get_mut(&mut self.inner).unwrap();
|
||||
let mut reader = ModuleReader::new(binary)?;
|
||||
let mut imports = Vec::new();
|
||||
let mut exports = Vec::new();
|
||||
let mut memories = Vec::new();
|
||||
let mut tables = Vec::new();
|
||||
let mut func_sig = Vec::new();
|
||||
let mut sigs = Vec::new();
|
||||
let mut globals = Vec::new();
|
||||
while !reader.eof() {
|
||||
let section = reader.read()?;
|
||||
match section.code {
|
||||
SectionCode::Memory => {
|
||||
let section = section.get_memory_section_reader()?;
|
||||
memories.reserve_exact(section.get_count() as usize);
|
||||
for entry in section {
|
||||
memories.push(into_memory_type(entry?)?);
|
||||
}
|
||||
}
|
||||
SectionCode::Type => {
|
||||
let section = section.get_type_section_reader()?;
|
||||
sigs.reserve_exact(section.get_count() as usize);
|
||||
for entry in section {
|
||||
sigs.push(into_func_type(entry?));
|
||||
}
|
||||
}
|
||||
SectionCode::Function => {
|
||||
let section = section.get_function_section_reader()?;
|
||||
func_sig.reserve_exact(section.get_count() as usize);
|
||||
for entry in section {
|
||||
func_sig.push(entry?);
|
||||
}
|
||||
}
|
||||
SectionCode::Global => {
|
||||
let section = section.get_global_section_reader()?;
|
||||
globals.reserve_exact(section.get_count() as usize);
|
||||
for entry in section {
|
||||
globals.push(into_global_type(entry?.ty));
|
||||
}
|
||||
}
|
||||
SectionCode::Table => {
|
||||
let section = section.get_table_section_reader()?;
|
||||
tables.reserve_exact(section.get_count() as usize);
|
||||
for entry in section {
|
||||
tables.push(into_table_type(entry?))
|
||||
}
|
||||
}
|
||||
SectionCode::Import => {
|
||||
let section = section.get_import_section_reader()?;
|
||||
imports.reserve_exact(section.get_count() as usize);
|
||||
for entry in section {
|
||||
let entry = entry?;
|
||||
let r#type = match entry.ty {
|
||||
ImportSectionEntryType::Function(index) => {
|
||||
func_sig.push(index);
|
||||
let sig = &sigs[index as usize];
|
||||
ExternType::Func(sig.clone())
|
||||
}
|
||||
ImportSectionEntryType::Table(tt) => {
|
||||
let table = into_table_type(tt);
|
||||
tables.push(table.clone());
|
||||
ExternType::Table(table)
|
||||
}
|
||||
ImportSectionEntryType::Memory(mt) => {
|
||||
let memory = into_memory_type(mt)?;
|
||||
memories.push(memory.clone());
|
||||
ExternType::Memory(memory)
|
||||
}
|
||||
ImportSectionEntryType::Global(gt) => {
|
||||
let global = into_global_type(gt);
|
||||
globals.push(global.clone());
|
||||
ExternType::Global(global)
|
||||
}
|
||||
};
|
||||
imports.push(ImportType::new(entry.module, entry.field, r#type));
|
||||
}
|
||||
}
|
||||
SectionCode::Export => {
|
||||
let section = section.get_export_section_reader()?;
|
||||
exports.reserve_exact(section.get_count() as usize);
|
||||
for entry in section {
|
||||
let entry = entry?;
|
||||
let r#type = match entry.kind {
|
||||
ExternalKind::Function => {
|
||||
let sig_index = func_sig[entry.index as usize] as usize;
|
||||
let sig = &sigs[sig_index];
|
||||
ExternType::Func(sig.clone())
|
||||
}
|
||||
ExternalKind::Table => {
|
||||
ExternType::Table(tables[entry.index as usize].clone())
|
||||
}
|
||||
ExternalKind::Memory => {
|
||||
ExternType::Memory(memories[entry.index as usize].clone())
|
||||
}
|
||||
ExternalKind::Global => {
|
||||
ExternType::Global(globals[entry.index as usize].clone())
|
||||
}
|
||||
};
|
||||
exports.push(ExportType::new(entry.field, r#type));
|
||||
}
|
||||
}
|
||||
SectionCode::Custom {
|
||||
kind: CustomSectionKind::Name,
|
||||
..
|
||||
} => {
|
||||
// Read name section. Per spec, ignore invalid custom section.
|
||||
if let Ok(mut reader) = section.get_name_section_reader() {
|
||||
while let Ok(entry) = reader.read() {
|
||||
if let Name::Module(name) = entry {
|
||||
if let Ok(name) = name.get_name() {
|
||||
Arc::get_mut(&mut inner.names).unwrap().module_name =
|
||||
Some(name.to_string());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// skip other sections
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner.imports = imports.into();
|
||||
inner.exports = exports.into();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Register this module's stack frame information into the global scope.
|
||||
///
|
||||
/// This is required to ensure that any traps can be properly symbolicated.
|
||||
pub(crate) fn register_frame_info(&self) {
|
||||
let mut info = self.inner.frame_info_registration.lock().unwrap();
|
||||
if info.is_some() {
|
||||
return;
|
||||
}
|
||||
*info = Some(FRAME_INFO.register(&self.inner.names, &self.inner.compiled));
|
||||
}
|
||||
}
|
||||
243
crates/api/src/ref.rs
Normal file
243
crates/api/src/ref.rs
Normal file
@@ -0,0 +1,243 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::any::Any;
|
||||
use std::cell::{self, RefCell};
|
||||
use std::fmt;
|
||||
use std::rc::{Rc, Weak};
|
||||
|
||||
pub trait HostInfo {
|
||||
fn finalize(&mut self) {}
|
||||
}
|
||||
|
||||
trait InternalRefBase: Any {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn host_info(&self) -> Option<cell::RefMut<Box<dyn HostInfo>>>;
|
||||
fn set_host_info(&self, info: Option<Box<dyn HostInfo>>);
|
||||
fn ptr_eq(&self, other: &dyn InternalRefBase) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct InternalRef(Rc<dyn InternalRefBase>);
|
||||
|
||||
impl InternalRef {
|
||||
pub fn is_ref<T: 'static>(&self) -> bool {
|
||||
let r = self.0.as_any();
|
||||
Any::is::<HostRef<T>>(r)
|
||||
}
|
||||
pub fn get_ref<T: 'static>(&self) -> HostRef<T> {
|
||||
let r = self.0.as_any();
|
||||
r.downcast_ref::<HostRef<T>>()
|
||||
.expect("reference is not T type")
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
struct AnyAndHostInfo {
|
||||
any: Box<dyn Any>,
|
||||
host_info: Option<Box<dyn HostInfo>>,
|
||||
}
|
||||
|
||||
impl Drop for AnyAndHostInfo {
|
||||
fn drop(&mut self) {
|
||||
if let Some(info) = &mut self.host_info {
|
||||
info.finalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OtherRef(Rc<RefCell<AnyAndHostInfo>>);
|
||||
|
||||
/// Represents an opaque reference to any data within WebAssembly.
|
||||
#[derive(Clone)]
|
||||
pub enum AnyRef {
|
||||
/// A reference to no data.
|
||||
Null,
|
||||
/// A reference to data stored internally in `wasmtime`.
|
||||
Ref(InternalRef),
|
||||
/// A reference to data located outside of `wasmtime`.
|
||||
Other(OtherRef),
|
||||
}
|
||||
|
||||
impl AnyRef {
|
||||
/// Creates a new instance of `AnyRef` from `Box<dyn Any>`.
|
||||
pub fn new(data: Box<dyn Any>) -> Self {
|
||||
let info = AnyAndHostInfo {
|
||||
any: data,
|
||||
host_info: None,
|
||||
};
|
||||
AnyRef::Other(OtherRef(Rc::new(RefCell::new(info))))
|
||||
}
|
||||
|
||||
/// Creates a `Null` reference.
|
||||
pub fn null() -> Self {
|
||||
AnyRef::Null
|
||||
}
|
||||
|
||||
/// Returns the data stored in the reference if available.
|
||||
/// # Panics
|
||||
/// Panics if the variant isn't `AnyRef::Other`.
|
||||
pub fn data(&self) -> cell::Ref<Box<dyn Any>> {
|
||||
match self {
|
||||
AnyRef::Other(OtherRef(r)) => cell::Ref::map(r.borrow(), |r| &r.any),
|
||||
_ => panic!("expected AnyRef::Other"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the two `AnyRef<T>`'s point to the same value (not just
|
||||
/// values that compare as equal).
|
||||
pub fn ptr_eq(&self, other: &AnyRef) -> bool {
|
||||
match (self, other) {
|
||||
(AnyRef::Null, AnyRef::Null) => true,
|
||||
(AnyRef::Ref(InternalRef(ref a)), AnyRef::Ref(InternalRef(ref b))) => {
|
||||
a.ptr_eq(b.as_ref())
|
||||
}
|
||||
(AnyRef::Other(OtherRef(ref a)), AnyRef::Other(OtherRef(ref b))) => Rc::ptr_eq(a, b),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the host information if available.
|
||||
/// # Panics
|
||||
/// Panics if `AnyRef` is already borrowed or `AnyRef` is `Null`.
|
||||
pub fn host_info(&self) -> Option<cell::RefMut<Box<dyn HostInfo>>> {
|
||||
match self {
|
||||
AnyRef::Null => panic!("null"),
|
||||
AnyRef::Ref(r) => r.0.host_info(),
|
||||
AnyRef::Other(r) => {
|
||||
let info = cell::RefMut::map(r.0.borrow_mut(), |b| &mut b.host_info);
|
||||
if info.is_none() {
|
||||
return None;
|
||||
}
|
||||
Some(cell::RefMut::map(info, |info| info.as_mut().unwrap()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the host information for an `AnyRef`.
|
||||
/// # Panics
|
||||
/// Panics if `AnyRef` is already borrowed or `AnyRef` is `Null`.
|
||||
pub fn set_host_info(&self, info: Option<Box<dyn HostInfo>>) {
|
||||
match self {
|
||||
AnyRef::Null => panic!("null"),
|
||||
AnyRef::Ref(r) => r.0.set_host_info(info),
|
||||
AnyRef::Other(r) => {
|
||||
r.0.borrow_mut().host_info = info;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for AnyRef {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
AnyRef::Null => write!(f, "null"),
|
||||
AnyRef::Ref(_) => write!(f, "anyref"),
|
||||
AnyRef::Other(_) => write!(f, "other ref"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentBox<T> {
|
||||
content: T,
|
||||
host_info: Option<Box<dyn HostInfo>>,
|
||||
anyref_data: Weak<dyn InternalRefBase>,
|
||||
}
|
||||
|
||||
impl<T> Drop for ContentBox<T> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(info) = &mut self.host_info {
|
||||
info.finalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a piece of data located in the host environment.
|
||||
pub struct HostRef<T>(Rc<RefCell<ContentBox<T>>>);
|
||||
|
||||
impl<T: 'static> HostRef<T> {
|
||||
/// Creates a new `HostRef<T>` from `T`.
|
||||
pub fn new(item: T) -> HostRef<T> {
|
||||
let anyref_data: Weak<HostRef<T>> = Weak::new();
|
||||
let content = ContentBox {
|
||||
content: item,
|
||||
host_info: None,
|
||||
anyref_data,
|
||||
};
|
||||
HostRef(Rc::new(RefCell::new(content)))
|
||||
}
|
||||
|
||||
/// Immutably borrows the wrapped data.
|
||||
/// # Panics
|
||||
/// Panics if the value is currently mutably borrowed.
|
||||
pub fn borrow(&self) -> cell::Ref<T> {
|
||||
cell::Ref::map(self.0.borrow(), |b| &b.content)
|
||||
}
|
||||
|
||||
/// Mutably borrows the wrapped data.
|
||||
/// # Panics
|
||||
/// Panics if the `HostRef<T>` is already borrowed.
|
||||
pub fn borrow_mut(&self) -> cell::RefMut<T> {
|
||||
cell::RefMut::map(self.0.borrow_mut(), |b| &mut b.content)
|
||||
}
|
||||
|
||||
/// Returns true if the two `HostRef<T>`'s point to the same value (not just
|
||||
/// values that compare as equal).
|
||||
pub fn ptr_eq(&self, other: &HostRef<T>) -> bool {
|
||||
Rc::ptr_eq(&self.0, &other.0)
|
||||
}
|
||||
|
||||
/// Returns an opaque reference to the wrapped data in the form of
|
||||
/// an `AnyRef`.
|
||||
/// # Panics
|
||||
/// Panics if `HostRef<T>` is already mutably borrowed.
|
||||
pub fn anyref(&self) -> AnyRef {
|
||||
let r = self.0.borrow_mut().anyref_data.upgrade();
|
||||
if let Some(r) = r {
|
||||
return AnyRef::Ref(InternalRef(r));
|
||||
}
|
||||
let anyref_data: Rc<dyn InternalRefBase> = Rc::new(self.clone());
|
||||
self.0.borrow_mut().anyref_data = Rc::downgrade(&anyref_data);
|
||||
AnyRef::Ref(InternalRef(anyref_data))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> InternalRefBase for HostRef<T> {
|
||||
fn ptr_eq(&self, other: &dyn InternalRefBase) -> bool {
|
||||
if let Some(other) = other.as_any().downcast_ref() {
|
||||
self.ptr_eq(other)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn host_info(&self) -> Option<cell::RefMut<Box<dyn HostInfo>>> {
|
||||
let info = cell::RefMut::map(self.0.borrow_mut(), |b| &mut b.host_info);
|
||||
if info.is_none() {
|
||||
return None;
|
||||
}
|
||||
Some(cell::RefMut::map(info, |info| info.as_mut().unwrap()))
|
||||
}
|
||||
|
||||
fn set_host_info(&self, info: Option<Box<dyn HostInfo>>) {
|
||||
self.0.borrow_mut().host_info = info;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for HostRef<T> {
|
||||
fn clone(&self) -> HostRef<T> {
|
||||
HostRef(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> fmt::Debug for HostRef<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Ref(")?;
|
||||
self.0.borrow().content.fmt(f)?;
|
||||
write!(f, ")")
|
||||
}
|
||||
}
|
||||
577
crates/api/src/runtime.rs
Normal file
577
crates/api/src/runtime.rs
Normal file
@@ -0,0 +1,577 @@
|
||||
use anyhow::Result;
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use wasmparser::{OperatorValidatorConfig, ValidatingParserConfig};
|
||||
use wasmtime_environ::settings::{self, Configurable};
|
||||
use wasmtime_environ::CacheConfig;
|
||||
use wasmtime_jit::{native, CompilationStrategy, Compiler};
|
||||
use wasmtime_profiling::{JitDumpAgent, ProfilingAgent, ProfilingStrategy};
|
||||
|
||||
// Runtime Environment
|
||||
|
||||
// Configuration
|
||||
|
||||
/// Global configuration options used to create an [`Engine`] and customize its
|
||||
/// behavior.
|
||||
///
|
||||
/// This structure exposed a builder-like interface and is primarily consumed by
|
||||
/// [`Engine::new()`]
|
||||
#[derive(Clone)]
|
||||
pub struct Config {
|
||||
pub(crate) flags: settings::Builder,
|
||||
pub(crate) validating_config: ValidatingParserConfig,
|
||||
pub(crate) debug_info: bool,
|
||||
pub(crate) strategy: CompilationStrategy,
|
||||
pub(crate) cache_config: CacheConfig,
|
||||
pub(crate) profiler: Option<Arc<Mutex<Box<dyn ProfilingAgent + Send>>>>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Creates a new configuration object with the default configuration
|
||||
/// specified.
|
||||
pub fn new() -> Config {
|
||||
let mut flags = settings::builder();
|
||||
|
||||
// There are two possible traps for division, and this way
|
||||
// we get the proper one if code traps.
|
||||
flags
|
||||
.enable("avoid_div_traps")
|
||||
.expect("should be valid flag");
|
||||
|
||||
// Invert cranelift's default-on verification to instead default off.
|
||||
flags
|
||||
.set("enable_verifier", "false")
|
||||
.expect("should be valid flag");
|
||||
|
||||
// Turn on cranelift speed optimizations by default
|
||||
flags
|
||||
.set("opt_level", "speed")
|
||||
.expect("should be valid flag");
|
||||
|
||||
Config {
|
||||
debug_info: false,
|
||||
validating_config: ValidatingParserConfig {
|
||||
operator_config: OperatorValidatorConfig {
|
||||
enable_threads: false,
|
||||
enable_reference_types: false,
|
||||
enable_bulk_memory: false,
|
||||
enable_simd: false,
|
||||
enable_multi_value: false,
|
||||
},
|
||||
},
|
||||
flags,
|
||||
strategy: CompilationStrategy::Auto,
|
||||
cache_config: CacheConfig::new_cache_disabled(),
|
||||
profiler: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Configures whether DWARF debug information will be emitted during
|
||||
/// compilation.
|
||||
///
|
||||
/// By default this option is `false`.
|
||||
pub fn debug_info(&mut self, enable: bool) -> &mut Self {
|
||||
self.debug_info = enable;
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures whether the WebAssembly threads proposal will be enabled for
|
||||
/// compilation.
|
||||
///
|
||||
/// The [WebAssembly threads proposal][threads] is not currently fully
|
||||
/// standardized and is undergoing development. Additionally the support in
|
||||
/// wasmtime itself is still being worked on. Support for this feature can
|
||||
/// be enabled through this method for appropriate wasm modules.
|
||||
///
|
||||
/// This feature gates items such as shared memories and atomic
|
||||
/// instructions. Note that enabling the threads feature will
|
||||
/// also enable the bulk memory feature.
|
||||
///
|
||||
/// This is `false` by default.
|
||||
///
|
||||
/// [threads]: https://github.com/webassembly/threads
|
||||
pub fn wasm_threads(&mut self, enable: bool) -> &mut Self {
|
||||
self.validating_config.operator_config.enable_threads = enable;
|
||||
// The threads proposal depends on the bulk memory proposal
|
||||
if enable {
|
||||
self.wasm_bulk_memory(true);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures whether the WebAssembly reference types proposal will be
|
||||
/// enabled for compilation.
|
||||
///
|
||||
/// The [WebAssembly reference types proposal][proposal] is not currently
|
||||
/// fully standardized and is undergoing development. Additionally the
|
||||
/// support in wasmtime itself is still being worked on. Support for this
|
||||
/// feature can be enabled through this method for appropriate wasm
|
||||
/// modules.
|
||||
///
|
||||
/// This feature gates items such as the `anyref` type and multiple tables
|
||||
/// being in a module. Note that enabling the reference types feature will
|
||||
/// also enable the bulk memory feature.
|
||||
///
|
||||
/// This is `false` by default.
|
||||
///
|
||||
/// [proposal]: https://github.com/webassembly/reference-types
|
||||
pub fn wasm_reference_types(&mut self, enable: bool) -> &mut Self {
|
||||
self.validating_config
|
||||
.operator_config
|
||||
.enable_reference_types = enable;
|
||||
// The reference types proposal depends on the bulk memory proposal
|
||||
if enable {
|
||||
self.wasm_bulk_memory(true);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures whether the WebAssembly SIMD proposal will be
|
||||
/// enabled for compilation.
|
||||
///
|
||||
/// The [WebAssembly SIMD proposal][proposal] is not currently
|
||||
/// fully standardized and is undergoing development. Additionally the
|
||||
/// support in wasmtime itself is still being worked on. Support for this
|
||||
/// feature can be enabled through this method for appropriate wasm
|
||||
/// modules.
|
||||
///
|
||||
/// This feature gates items such as the `v128` type and all of its
|
||||
/// operators being in a module.
|
||||
///
|
||||
/// This is `false` by default.
|
||||
///
|
||||
/// [proposal]: https://github.com/webassembly/simd
|
||||
pub fn wasm_simd(&mut self, enable: bool) -> &mut Self {
|
||||
self.validating_config.operator_config.enable_simd = enable;
|
||||
let val = if enable { "true" } else { "false" };
|
||||
self.flags
|
||||
.set("enable_simd", val)
|
||||
.expect("should be valid flag");
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures whether the WebAssembly bulk memory operations proposal will
|
||||
/// be enabled for compilation.
|
||||
///
|
||||
/// The [WebAssembly bulk memory operations proposal][proposal] is not
|
||||
/// currently fully standardized and is undergoing development.
|
||||
/// Additionally the support in wasmtime itself is still being worked on.
|
||||
/// Support for this feature can be enabled through this method for
|
||||
/// appropriate wasm modules.
|
||||
///
|
||||
/// This feature gates items such as the `memory.copy` instruction, passive
|
||||
/// data/table segments, etc, being in a module.
|
||||
///
|
||||
/// This is `false` by default.
|
||||
///
|
||||
/// [proposal]: https://github.com/webassembly/bulk-memory-operations
|
||||
pub fn wasm_bulk_memory(&mut self, enable: bool) -> &mut Self {
|
||||
self.validating_config.operator_config.enable_bulk_memory = enable;
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures whether the WebAssembly multi-value proposal will
|
||||
/// be enabled for compilation.
|
||||
///
|
||||
/// The [WebAssembly multi-value proposal][proposal] is not
|
||||
/// currently fully standardized and is undergoing development.
|
||||
/// Additionally the support in wasmtime itself is still being worked on.
|
||||
/// Support for this feature can be enabled through this method for
|
||||
/// appropriate wasm modules.
|
||||
///
|
||||
/// This feature gates functions and blocks returning multiple values in a
|
||||
/// module, for example.
|
||||
///
|
||||
/// This is `false` by default.
|
||||
///
|
||||
/// [proposal]: https://github.com/webassembly/multi-value
|
||||
pub fn wasm_multi_value(&mut self, enable: bool) -> &mut Self {
|
||||
self.validating_config.operator_config.enable_multi_value = enable;
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures which compilation strategy will be used for wasm modules.
|
||||
///
|
||||
/// This method can be used to configure which compiler is used for wasm
|
||||
/// modules, and for more documentation consult the [`Strategy`] enumeration
|
||||
/// and its documentation.
|
||||
///
|
||||
/// The default value for this is `Strategy::Auto`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Some compilation strategies require compile-time options of `wasmtime`
|
||||
/// itself to be set, but if they're not set and the strategy is specified
|
||||
/// here then an error will be returned.
|
||||
pub fn strategy(&mut self, strategy: Strategy) -> Result<&mut Self> {
|
||||
self.strategy = match strategy {
|
||||
Strategy::Auto => CompilationStrategy::Auto,
|
||||
Strategy::Cranelift => CompilationStrategy::Cranelift,
|
||||
#[cfg(feature = "lightbeam")]
|
||||
Strategy::Lightbeam => CompilationStrategy::Lightbeam,
|
||||
#[cfg(not(feature = "lightbeam"))]
|
||||
Strategy::Lightbeam => {
|
||||
anyhow::bail!("lightbeam compilation strategy wasn't enabled at compile time");
|
||||
}
|
||||
};
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Creates a default profiler based on the profiling strategy choosen
|
||||
///
|
||||
/// Profiler creation calls the type's default initializer where the purpose is
|
||||
/// really just to put in place the type used for profiling.
|
||||
pub fn profiler(&mut self, profile: ProfilingStrategy) -> Result<&mut Self> {
|
||||
match profile {
|
||||
ProfilingStrategy::JitDumpProfiler => {
|
||||
self.profiler = { Some(Arc::new(Mutex::new(Box::new(JitDumpAgent::default())))) }
|
||||
}
|
||||
_ => self.profiler = { None },
|
||||
};
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Configures whether the debug verifier of Cranelift is enabled or not.
|
||||
///
|
||||
/// When Cranelift is used as a code generation backend this will configure
|
||||
/// it to have the `enable_verifier` flag which will enable a number of debug
|
||||
/// checks inside of Cranelift. This is largely only useful for the
|
||||
/// developers of wasmtime itself.
|
||||
///
|
||||
/// The default value for this is `false`
|
||||
pub fn cranelift_debug_verifier(&mut self, enable: bool) -> &mut Self {
|
||||
let val = if enable { "true" } else { "false" };
|
||||
self.flags
|
||||
.set("enable_verifier", val)
|
||||
.expect("should be valid flag");
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures the Cranelift code generator optimization level.
|
||||
///
|
||||
/// When the Cranelift code generator is used you can configure the
|
||||
/// optimization level used for generated code in a few various ways. For
|
||||
/// more information see the documentation of [`OptLevel`].
|
||||
///
|
||||
/// The default value for this is `OptLevel::None`.
|
||||
pub fn cranelift_opt_level(&mut self, level: OptLevel) -> &mut Self {
|
||||
let val = match level {
|
||||
OptLevel::None => "none",
|
||||
OptLevel::Speed => "speed",
|
||||
OptLevel::SpeedAndSize => "speed_and_size",
|
||||
};
|
||||
self.flags
|
||||
.set("opt_level", val)
|
||||
.expect("should be valid flag");
|
||||
self
|
||||
}
|
||||
|
||||
/// Loads cache configuration specified at `path`.
|
||||
///
|
||||
/// This method will read the file specified by `path` on the filesystem and
|
||||
/// attempt to load cache configuration from it. This method can also fail
|
||||
/// due to I/O errors, misconfiguration, syntax errors, etc. For expected
|
||||
/// syntax in the configuration file see the [documentation online][docs].
|
||||
///
|
||||
/// By default cache configuration is not enabled or loaded.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This method can fail due to any error that happens when loading the file
|
||||
/// pointed to by `path` and attempting to load the cache configuration.
|
||||
///
|
||||
/// [docs]: https://bytecodealliance.github.io/wasmtime/cli-cache.html
|
||||
pub fn cache_config_load(&mut self, path: impl AsRef<Path>) -> Result<&mut Self> {
|
||||
self.cache_config = wasmtime_environ::CacheConfig::from_file(Some(path.as_ref()))?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Loads cache configuration from the system default path.
|
||||
///
|
||||
/// This commit is the same as [`Config::cache_config_load`] except that it
|
||||
/// does not take a path argument and instead loads the default
|
||||
/// configuration present on the system. This is located, for example, on
|
||||
/// Unix at `$HOME/.config/wasmtime/config.toml` and is typically created
|
||||
/// with the `wasmtime config new` command.
|
||||
///
|
||||
/// By default cache configuration is not enabled or loaded.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This method can fail due to any error that happens when loading the
|
||||
/// default system configuration. Note that it is not an error if the
|
||||
/// default config file does not exist, in which case the default settings
|
||||
/// for an enabled cache are applied.
|
||||
///
|
||||
/// [docs]: https://bytecodealliance.github.io/wasmtime/cli-cache.html
|
||||
pub fn cache_config_load_default(&mut self) -> Result<&mut Self> {
|
||||
self.cache_config = wasmtime_environ::CacheConfig::from_file(None)?;
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Config {
|
||||
Config::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Config {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let features = &self.validating_config.operator_config;
|
||||
f.debug_struct("Config")
|
||||
.field("debug_info", &self.debug_info)
|
||||
.field("strategy", &self.strategy)
|
||||
.field("wasm_threads", &features.enable_threads)
|
||||
.field("wasm_reference_types", &features.enable_reference_types)
|
||||
.field("wasm_bulk_memory", &features.enable_bulk_memory)
|
||||
.field("wasm_simd", &features.enable_simd)
|
||||
.field("wasm_multi_value", &features.enable_multi_value)
|
||||
.field(
|
||||
"flags",
|
||||
&settings::Flags::new(self.flags.clone()).to_string(),
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Possible Compilation strategies for a wasm module.
|
||||
///
|
||||
/// This is used as an argument to the [`Config::strategy`] method.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Strategy {
|
||||
/// An indicator that the compilation strategy should be automatically
|
||||
/// selected.
|
||||
///
|
||||
/// This is generally what you want for most projects and indicates that the
|
||||
/// `wasmtime` crate itself should make the decision about what the best
|
||||
/// code generator for a wasm module is.
|
||||
///
|
||||
/// Currently this always defaults to Cranelift, but the default value will
|
||||
/// change over time.
|
||||
Auto,
|
||||
|
||||
/// Currently the default backend, Cranelift aims to be a reasonably fast
|
||||
/// code generator which generates high quality machine code.
|
||||
Cranelift,
|
||||
|
||||
/// A single-pass code generator that is faster than Cranelift but doesn't
|
||||
/// produce as high-quality code.
|
||||
///
|
||||
/// To successfully pass this argument to [`Config::strategy`] the
|
||||
/// `lightbeam` feature of this crate must be enabled.
|
||||
Lightbeam,
|
||||
}
|
||||
|
||||
/// Possible optimization levels for the Cranelift codegen backend.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum OptLevel {
|
||||
/// No optimizations performed, minimizes compilation time by disabling most
|
||||
/// optimizations.
|
||||
None,
|
||||
/// Generates the fastest possible code, but may take longer.
|
||||
Speed,
|
||||
/// Similar to `speed`, but also performs transformations aimed at reducing
|
||||
/// code size.
|
||||
SpeedAndSize,
|
||||
}
|
||||
|
||||
// Engine
|
||||
|
||||
/// An `Engine` which is a global context for compilation and management of wasm
|
||||
/// modules.
|
||||
///
|
||||
/// An engine can be safely shared across threads and is a cheap cloneable
|
||||
/// handle to the actual engine. The engine itself will be deallocate once all
|
||||
/// references to it have gone away.
|
||||
///
|
||||
/// Engines store global configuration preferences such as compilation settings,
|
||||
/// enabled features, etc. You'll likely only need at most one of these for a
|
||||
/// program.
|
||||
///
|
||||
/// ## Engines and `Clone`
|
||||
///
|
||||
/// Using `clone` on an `Engine` is a cheap operation. It will not create an
|
||||
/// entirely new engine, but rather just a new reference to the existing engine.
|
||||
/// In other words it's a shallow copy, not a deep copy.
|
||||
///
|
||||
/// ## Engines and `Default`
|
||||
///
|
||||
/// You can create an engine with default configuration settings using
|
||||
/// `Engine::default()`. Be sure to consult the documentation of [`Config`] for
|
||||
/// default settings.
|
||||
#[derive(Default, Clone)]
|
||||
pub struct Engine {
|
||||
config: Arc<Config>,
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
/// Creates a new [`Engine`] with the specified compilation and
|
||||
/// configuration settings.
|
||||
pub fn new(config: &Config) -> Engine {
|
||||
Engine {
|
||||
config: Arc::new(config.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the configuration settings that this engine is using.
|
||||
pub fn config(&self) -> &Config {
|
||||
&self.config
|
||||
}
|
||||
}
|
||||
|
||||
// Store
|
||||
|
||||
/// A `Store` is a shared cache of information between WebAssembly modules.
|
||||
///
|
||||
/// Each `Module` is compiled into a `Store` and a `Store` is associated with an
|
||||
/// [`Engine`]. You'll use a `Store` to attach to a number of global items in
|
||||
/// the production of various items for wasm modules.
|
||||
///
|
||||
/// # Stores and `Clone`
|
||||
///
|
||||
/// Using `clone` on a `Store` is a cheap operation. It will not create an
|
||||
/// entirely new store, but rather just a new reference to the existing object.
|
||||
/// In other words it's a shallow copy, not a deep copy.
|
||||
///
|
||||
/// ## Stores and `Default`
|
||||
///
|
||||
/// You can create a store with default configuration settings using
|
||||
/// `Store::default()`. This will create a brand new [`Engine`] with default
|
||||
/// ocnfiguration (see [`Config`] for more information).
|
||||
#[derive(Clone)]
|
||||
pub struct Store {
|
||||
// FIXME(#777) should be `Arc` and this type should be thread-safe
|
||||
inner: Rc<StoreInner>,
|
||||
}
|
||||
|
||||
struct StoreInner {
|
||||
engine: Engine,
|
||||
compiler: RefCell<Compiler>,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
/// Creates a new store to be associated with the given [`Engine`].
|
||||
pub fn new(engine: &Engine) -> Store {
|
||||
let isa = native::builder().finish(settings::Flags::new(engine.config.flags.clone()));
|
||||
let compiler = Compiler::new(
|
||||
isa,
|
||||
engine.config.strategy,
|
||||
engine.config.cache_config.clone(),
|
||||
);
|
||||
Store {
|
||||
inner: Rc::new(StoreInner {
|
||||
engine: engine.clone(),
|
||||
compiler: RefCell::new(compiler),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`Engine`] that this store is associated with.
|
||||
pub fn engine(&self) -> &Engine {
|
||||
&self.inner.engine
|
||||
}
|
||||
|
||||
pub(crate) fn compiler(&self) -> std::cell::Ref<'_, Compiler> {
|
||||
self.inner.compiler.borrow()
|
||||
}
|
||||
|
||||
pub(crate) fn compiler_mut(&self) -> std::cell::RefMut<'_, Compiler> {
|
||||
self.inner.compiler.borrow_mut()
|
||||
}
|
||||
|
||||
/// Returns whether the stores `a` and `b` refer to the same underlying
|
||||
/// `Store`.
|
||||
///
|
||||
/// Because the `Store` type is reference counted multiple clones may point
|
||||
/// to the same underlying storage, and this method can be used to determine
|
||||
/// whether two stores are indeed the same.
|
||||
pub fn same(a: &Store, b: &Store) -> bool {
|
||||
Rc::ptr_eq(&a.inner, &b.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Store {
|
||||
fn default() -> Store {
|
||||
Store::new(&Engine::default())
|
||||
}
|
||||
}
|
||||
|
||||
fn _assert_send_sync() {
|
||||
fn _assert<T: Send + Sync>() {}
|
||||
_assert::<Engine>();
|
||||
_assert::<Config>();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::Module;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn cache_accounts_for_opt_level() -> Result<()> {
|
||||
let td = TempDir::new()?;
|
||||
let config_path = td.path().join("config.toml");
|
||||
std::fs::write(
|
||||
&config_path,
|
||||
&format!(
|
||||
"
|
||||
[cache]
|
||||
enabled = true
|
||||
directory = '{}'
|
||||
",
|
||||
td.path().join("cache").display()
|
||||
),
|
||||
)?;
|
||||
let mut cfg = Config::new();
|
||||
cfg.cranelift_opt_level(OptLevel::None)
|
||||
.cache_config_load(&config_path)?;
|
||||
let store = Store::new(&Engine::new(&cfg));
|
||||
Module::new(&store, "(module (func))")?;
|
||||
assert_eq!(store.engine().config.cache_config.cache_hits(), 0);
|
||||
assert_eq!(store.engine().config.cache_config.cache_misses(), 1);
|
||||
Module::new(&store, "(module (func))")?;
|
||||
assert_eq!(store.engine().config.cache_config.cache_hits(), 1);
|
||||
assert_eq!(store.engine().config.cache_config.cache_misses(), 1);
|
||||
|
||||
let mut cfg = Config::new();
|
||||
cfg.cranelift_opt_level(OptLevel::Speed)
|
||||
.cache_config_load(&config_path)?;
|
||||
let store = Store::new(&Engine::new(&cfg));
|
||||
Module::new(&store, "(module (func))")?;
|
||||
assert_eq!(store.engine().config.cache_config.cache_hits(), 0);
|
||||
assert_eq!(store.engine().config.cache_config.cache_misses(), 1);
|
||||
Module::new(&store, "(module (func))")?;
|
||||
assert_eq!(store.engine().config.cache_config.cache_hits(), 1);
|
||||
assert_eq!(store.engine().config.cache_config.cache_misses(), 1);
|
||||
|
||||
let mut cfg = Config::new();
|
||||
cfg.cranelift_opt_level(OptLevel::SpeedAndSize)
|
||||
.cache_config_load(&config_path)?;
|
||||
let store = Store::new(&Engine::new(&cfg));
|
||||
Module::new(&store, "(module (func))")?;
|
||||
assert_eq!(store.engine().config.cache_config.cache_hits(), 0);
|
||||
assert_eq!(store.engine().config.cache_config.cache_misses(), 1);
|
||||
Module::new(&store, "(module (func))")?;
|
||||
assert_eq!(store.engine().config.cache_config.cache_hits(), 1);
|
||||
assert_eq!(store.engine().config.cache_config.cache_misses(), 1);
|
||||
|
||||
let mut cfg = Config::new();
|
||||
cfg.debug_info(true).cache_config_load(&config_path)?;
|
||||
let store = Store::new(&Engine::new(&cfg));
|
||||
Module::new(&store, "(module (func))")?;
|
||||
assert_eq!(store.engine().config.cache_config.cache_hits(), 0);
|
||||
assert_eq!(store.engine().config.cache_config.cache_misses(), 1);
|
||||
Module::new(&store, "(module (func))")?;
|
||||
assert_eq!(store.engine().config.cache_config.cache_hits(), 1);
|
||||
assert_eq!(store.engine().config.cache_config.cache_misses(), 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
54
crates/api/src/trampoline/create_handle.rs
Normal file
54
crates/api/src/trampoline/create_handle.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
//! Support for a calling of an imported function.
|
||||
|
||||
use crate::runtime::Store;
|
||||
use anyhow::Result;
|
||||
use std::any::Any;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::wasm::DefinedFuncIndex;
|
||||
use wasmtime_environ::Module;
|
||||
use wasmtime_runtime::{Imports, InstanceHandle, VMFunctionBody};
|
||||
|
||||
pub(crate) fn create_handle(
|
||||
module: Module,
|
||||
store: &Store,
|
||||
finished_functions: PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
|
||||
state: Box<dyn Any>,
|
||||
) -> Result<InstanceHandle> {
|
||||
let imports = Imports::new(
|
||||
HashSet::new(),
|
||||
PrimaryMap::new(),
|
||||
PrimaryMap::new(),
|
||||
PrimaryMap::new(),
|
||||
PrimaryMap::new(),
|
||||
);
|
||||
let data_initializers = Vec::new();
|
||||
|
||||
// Compute indices into the shared signature table.
|
||||
let signatures = module
|
||||
.local
|
||||
.signatures
|
||||
.values()
|
||||
.map(|sig| store.compiler().signatures().register(sig))
|
||||
.collect::<PrimaryMap<_, _>>();
|
||||
|
||||
unsafe {
|
||||
Ok(InstanceHandle::new(
|
||||
Arc::new(module),
|
||||
store.compiler().trap_registry().register_traps(Vec::new()),
|
||||
finished_functions.into_boxed_slice(),
|
||||
imports,
|
||||
&data_initializers,
|
||||
signatures.into_boxed_slice(),
|
||||
None,
|
||||
store
|
||||
.engine()
|
||||
.config()
|
||||
.validating_config
|
||||
.operator_config
|
||||
.enable_bulk_memory,
|
||||
state,
|
||||
)?)
|
||||
}
|
||||
}
|
||||
326
crates/api/src/trampoline/func.rs
Normal file
326
crates/api/src/trampoline/func.rs
Normal file
@@ -0,0 +1,326 @@
|
||||
//! Support for a calling of an imported function.
|
||||
|
||||
use super::create_handle::create_handle;
|
||||
use crate::{Callable, FuncType, Store, Trap, Val};
|
||||
use anyhow::{bail, Result};
|
||||
use std::any::Any;
|
||||
use std::cmp;
|
||||
use std::panic::{self, AssertUnwindSafe};
|
||||
use std::rc::Rc;
|
||||
use wasmtime_environ::entity::{EntityRef, PrimaryMap};
|
||||
use wasmtime_environ::ir::types;
|
||||
use wasmtime_environ::isa::TargetIsa;
|
||||
use wasmtime_environ::wasm::{DefinedFuncIndex, FuncIndex};
|
||||
use wasmtime_environ::{
|
||||
ir, settings, CompiledFunction, CompiledFunctionUnwindInfo, Export, Module,
|
||||
};
|
||||
use wasmtime_jit::trampoline::ir::{
|
||||
ExternalName, Function, InstBuilder, MemFlags, StackSlotData, StackSlotKind,
|
||||
};
|
||||
use wasmtime_jit::trampoline::{
|
||||
binemit, pretty_error, Context, FunctionBuilder, FunctionBuilderContext,
|
||||
};
|
||||
use wasmtime_jit::{native, CodeMemory};
|
||||
use wasmtime_runtime::{InstanceHandle, VMContext, VMFunctionBody};
|
||||
|
||||
struct TrampolineState {
|
||||
func: Rc<dyn Callable + 'static>,
|
||||
#[allow(dead_code)]
|
||||
code_memory: CodeMemory,
|
||||
}
|
||||
|
||||
impl TrampolineState {
|
||||
fn new(func: Rc<dyn Callable + 'static>, code_memory: CodeMemory) -> Self {
|
||||
TrampolineState { func, code_memory }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn stub_fn(
|
||||
vmctx: *mut VMContext,
|
||||
_caller_vmctx: *mut VMContext,
|
||||
call_id: u32,
|
||||
values_vec: *mut i128,
|
||||
) {
|
||||
// Here we are careful to use `catch_unwind` to ensure Rust panics don't
|
||||
// unwind past us. The primary reason for this is that Rust considers it UB
|
||||
// to unwind past an `extern "C"` function. Here we are in an `extern "C"`
|
||||
// function and the cross into wasm was through an `extern "C"` function at
|
||||
// the base of the stack as well. We'll need to wait for assorted RFCs and
|
||||
// language features to enable this to be done in a sound and stable fashion
|
||||
// before avoiding catching the panic here.
|
||||
//
|
||||
// Also note that there are intentionally no local variables on this stack
|
||||
// frame. The reason for that is that some of the "raise" functions we have
|
||||
// below will trigger a longjmp, which won't run local destructors if we
|
||||
// have any. To prevent leaks we avoid having any local destructors by
|
||||
// avoiding local variables.
|
||||
let result = panic::catch_unwind(AssertUnwindSafe(|| call_stub(vmctx, call_id, values_vec)));
|
||||
|
||||
match result {
|
||||
Ok(Ok(())) => {}
|
||||
|
||||
// If a trap was raised (an error returned from the imported function)
|
||||
// then we smuggle the trap through `Box<dyn Error>` through to the
|
||||
// call-site, which gets unwrapped in `Trap::from_jit` later on as we
|
||||
// convert from the internal `Trap` type to our own `Trap` type in this
|
||||
// crate.
|
||||
Ok(Err(trap)) => wasmtime_runtime::raise_user_trap(Box::new(trap)),
|
||||
|
||||
// And finally if the imported function panicked, then we trigger the
|
||||
// form of unwinding that's safe to jump over wasm code on all
|
||||
// platforms.
|
||||
Err(panic) => wasmtime_runtime::resume_panic(panic),
|
||||
}
|
||||
|
||||
unsafe fn call_stub(
|
||||
vmctx: *mut VMContext,
|
||||
call_id: u32,
|
||||
values_vec: *mut i128,
|
||||
) -> Result<(), Trap> {
|
||||
let instance = InstanceHandle::from_vmctx(vmctx);
|
||||
|
||||
let (args, returns_len) = {
|
||||
let module = instance.module_ref();
|
||||
let signature =
|
||||
&module.local.signatures[module.local.functions[FuncIndex::new(call_id as usize)]];
|
||||
|
||||
let mut args = Vec::new();
|
||||
for i in 2..signature.params.len() {
|
||||
args.push(Val::read_value_from(
|
||||
values_vec.offset(i as isize - 2),
|
||||
signature.params[i].value_type,
|
||||
))
|
||||
}
|
||||
(args, signature.returns.len())
|
||||
};
|
||||
|
||||
let mut returns = vec![Val::null(); returns_len];
|
||||
let state = &instance
|
||||
.host_state()
|
||||
.downcast_ref::<TrampolineState>()
|
||||
.expect("state");
|
||||
state.func.call(&args, &mut returns)?;
|
||||
|
||||
let module = instance.module_ref();
|
||||
let signature =
|
||||
&module.local.signatures[module.local.functions[FuncIndex::new(call_id as usize)]];
|
||||
for (i, ret) in returns.iter_mut().enumerate() {
|
||||
if ret.ty().get_wasmtime_type() != Some(signature.returns[i].value_type) {
|
||||
return Err(Trap::new(
|
||||
"`Callable` attempted to return an incompatible value",
|
||||
));
|
||||
}
|
||||
ret.write_value_to(values_vec.add(i));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a trampoline for invoking a Callable.
|
||||
fn make_trampoline(
|
||||
isa: &dyn TargetIsa,
|
||||
code_memory: &mut CodeMemory,
|
||||
fn_builder_ctx: &mut FunctionBuilderContext,
|
||||
call_id: u32,
|
||||
signature: &ir::Signature,
|
||||
) -> *mut [VMFunctionBody] {
|
||||
// Mostly reverse copy of the similar method from wasmtime's
|
||||
// wasmtime-jit/src/compiler.rs.
|
||||
let pointer_type = isa.pointer_type();
|
||||
let mut stub_sig = ir::Signature::new(isa.frontend_config().default_call_conv);
|
||||
|
||||
// Add the caller/callee `vmctx` parameters.
|
||||
stub_sig.params.push(ir::AbiParam::special(
|
||||
pointer_type,
|
||||
ir::ArgumentPurpose::VMContext,
|
||||
));
|
||||
|
||||
// Add the caller `vmctx` parameter.
|
||||
stub_sig.params.push(ir::AbiParam::new(pointer_type));
|
||||
|
||||
// Add the `call_id` parameter.
|
||||
stub_sig.params.push(ir::AbiParam::new(types::I32));
|
||||
|
||||
// Add the `values_vec` parameter.
|
||||
stub_sig.params.push(ir::AbiParam::new(pointer_type));
|
||||
|
||||
// Compute the size of the values vector. The vmctx and caller vmctx are passed separately.
|
||||
let value_size = 16;
|
||||
let values_vec_len = ((value_size as usize)
|
||||
* cmp::max(signature.params.len() - 2, signature.returns.len()))
|
||||
as u32;
|
||||
|
||||
let mut context = Context::new();
|
||||
context.func = Function::with_name_signature(ExternalName::user(0, 0), signature.clone());
|
||||
context.func.collect_frame_layout_info();
|
||||
|
||||
let ss = context.func.create_stack_slot(StackSlotData::new(
|
||||
StackSlotKind::ExplicitSlot,
|
||||
values_vec_len,
|
||||
));
|
||||
|
||||
{
|
||||
let mut builder = FunctionBuilder::new(&mut context.func, fn_builder_ctx);
|
||||
let block0 = builder.create_block();
|
||||
|
||||
builder.append_block_params_for_function_params(block0);
|
||||
builder.switch_to_block(block0);
|
||||
builder.seal_block(block0);
|
||||
|
||||
let values_vec_ptr_val = builder.ins().stack_addr(pointer_type, ss, 0);
|
||||
let mflags = MemFlags::trusted();
|
||||
for i in 2..signature.params.len() {
|
||||
if i == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let val = builder.func.dfg.block_params(block0)[i];
|
||||
builder.ins().store(
|
||||
mflags,
|
||||
val,
|
||||
values_vec_ptr_val,
|
||||
((i - 2) * value_size) as i32,
|
||||
);
|
||||
}
|
||||
|
||||
let block_params = builder.func.dfg.block_params(block0);
|
||||
let vmctx_ptr_val = block_params[0];
|
||||
let caller_vmctx_ptr_val = block_params[1];
|
||||
let call_id_val = builder.ins().iconst(types::I32, call_id as i64);
|
||||
|
||||
let callee_args = vec![
|
||||
vmctx_ptr_val,
|
||||
caller_vmctx_ptr_val,
|
||||
call_id_val,
|
||||
values_vec_ptr_val,
|
||||
];
|
||||
|
||||
let new_sig = builder.import_signature(stub_sig);
|
||||
|
||||
let callee_value = builder
|
||||
.ins()
|
||||
.iconst(pointer_type, stub_fn as *const VMFunctionBody as i64);
|
||||
builder
|
||||
.ins()
|
||||
.call_indirect(new_sig, callee_value, &callee_args);
|
||||
|
||||
let mflags = MemFlags::trusted();
|
||||
let mut results = Vec::new();
|
||||
for (i, r) in signature.returns.iter().enumerate() {
|
||||
let load = builder.ins().load(
|
||||
r.value_type,
|
||||
mflags,
|
||||
values_vec_ptr_val,
|
||||
(i * value_size) as i32,
|
||||
);
|
||||
results.push(load);
|
||||
}
|
||||
builder.ins().return_(&results);
|
||||
builder.finalize()
|
||||
}
|
||||
|
||||
let mut code_buf: Vec<u8> = Vec::new();
|
||||
let mut reloc_sink = binemit::TrampolineRelocSink {};
|
||||
let mut trap_sink = binemit::NullTrapSink {};
|
||||
let mut stackmap_sink = binemit::NullStackmapSink {};
|
||||
context
|
||||
.compile_and_emit(
|
||||
isa,
|
||||
&mut code_buf,
|
||||
&mut reloc_sink,
|
||||
&mut trap_sink,
|
||||
&mut stackmap_sink,
|
||||
)
|
||||
.map_err(|error| pretty_error(&context.func, Some(isa), error))
|
||||
.expect("compile_and_emit");
|
||||
|
||||
let unwind_info = CompiledFunctionUnwindInfo::new(isa, &context);
|
||||
|
||||
code_memory
|
||||
.allocate_for_function(&CompiledFunction {
|
||||
body: code_buf,
|
||||
jt_offsets: context.func.jt_offsets,
|
||||
unwind_info,
|
||||
})
|
||||
.expect("allocate_for_function")
|
||||
}
|
||||
|
||||
pub fn create_handle_with_function(
|
||||
ft: &FuncType,
|
||||
func: &Rc<dyn Callable + 'static>,
|
||||
store: &Store,
|
||||
) -> Result<InstanceHandle> {
|
||||
let isa = {
|
||||
let isa_builder = native::builder();
|
||||
let flag_builder = settings::builder();
|
||||
isa_builder.finish(settings::Flags::new(flag_builder))
|
||||
};
|
||||
|
||||
let pointer_type = isa.pointer_type();
|
||||
let sig = match ft.get_wasmtime_signature(pointer_type) {
|
||||
Some(sig) => sig.clone(),
|
||||
None => bail!("not a supported core wasm signature {:?}", ft),
|
||||
};
|
||||
|
||||
let mut fn_builder_ctx = FunctionBuilderContext::new();
|
||||
let mut module = Module::new();
|
||||
let mut finished_functions: PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]> =
|
||||
PrimaryMap::new();
|
||||
let mut code_memory = CodeMemory::new();
|
||||
|
||||
let sig_id = module.local.signatures.push(sig.clone());
|
||||
let func_id = module.local.functions.push(sig_id);
|
||||
module
|
||||
.exports
|
||||
.insert("trampoline".to_string(), Export::Function(func_id));
|
||||
let trampoline = make_trampoline(
|
||||
isa.as_ref(),
|
||||
&mut code_memory,
|
||||
&mut fn_builder_ctx,
|
||||
func_id.index() as u32,
|
||||
&sig,
|
||||
);
|
||||
code_memory.publish();
|
||||
|
||||
finished_functions.push(trampoline);
|
||||
|
||||
let trampoline_state = TrampolineState::new(func.clone(), code_memory);
|
||||
|
||||
create_handle(
|
||||
module,
|
||||
store,
|
||||
finished_functions,
|
||||
Box::new(trampoline_state),
|
||||
)
|
||||
}
|
||||
|
||||
pub unsafe fn create_handle_with_raw_function(
|
||||
ft: &FuncType,
|
||||
func: *mut [VMFunctionBody],
|
||||
store: &Store,
|
||||
state: Box<dyn Any>,
|
||||
) -> Result<InstanceHandle> {
|
||||
let isa = {
|
||||
let isa_builder = native::builder();
|
||||
let flag_builder = settings::builder();
|
||||
isa_builder.finish(settings::Flags::new(flag_builder))
|
||||
};
|
||||
|
||||
let pointer_type = isa.pointer_type();
|
||||
let sig = match ft.get_wasmtime_signature(pointer_type) {
|
||||
Some(sig) => sig.clone(),
|
||||
None => bail!("not a supported core wasm signature {:?}", ft),
|
||||
};
|
||||
|
||||
let mut module = Module::new();
|
||||
let mut finished_functions = PrimaryMap::new();
|
||||
|
||||
let sig_id = module.local.signatures.push(sig.clone());
|
||||
let func_id = module.local.functions.push(sig_id);
|
||||
module
|
||||
.exports
|
||||
.insert("trampoline".to_string(), Export::Function(func_id));
|
||||
finished_functions.push(func);
|
||||
|
||||
create_handle(module, store, finished_functions, state)
|
||||
}
|
||||
35
crates/api/src/trampoline/global.rs
Normal file
35
crates/api/src/trampoline/global.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use super::create_handle::create_handle;
|
||||
use crate::Store;
|
||||
use crate::{GlobalType, Mutability, Val};
|
||||
use anyhow::{bail, Result};
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::{wasm, Module};
|
||||
use wasmtime_runtime::InstanceHandle;
|
||||
|
||||
pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result<InstanceHandle> {
|
||||
let global = wasm::Global {
|
||||
ty: match gt.content().get_wasmtime_type() {
|
||||
Some(t) => t,
|
||||
None => bail!("cannot support {:?} as a wasm global type", gt.content()),
|
||||
},
|
||||
mutability: match gt.mutability() {
|
||||
Mutability::Const => false,
|
||||
Mutability::Var => true,
|
||||
},
|
||||
initializer: match val {
|
||||
Val::I32(i) => wasm::GlobalInit::I32Const(i),
|
||||
Val::I64(i) => wasm::GlobalInit::I64Const(i),
|
||||
Val::F32(f) => wasm::GlobalInit::F32Const(f),
|
||||
Val::F64(f) => wasm::GlobalInit::F64Const(f),
|
||||
_ => unimplemented!("create_global for {:?}", gt),
|
||||
},
|
||||
};
|
||||
let mut module = Module::new();
|
||||
let global_id = module.local.globals.push(global);
|
||||
module.exports.insert(
|
||||
"global".to_string(),
|
||||
wasmtime_environ::Export::Global(global_id),
|
||||
);
|
||||
let handle = create_handle(module, store, PrimaryMap::new(), Box::new(()))?;
|
||||
Ok(handle)
|
||||
}
|
||||
27
crates/api/src/trampoline/memory.rs
Normal file
27
crates/api/src/trampoline/memory.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use super::create_handle::create_handle;
|
||||
use crate::MemoryType;
|
||||
use crate::Store;
|
||||
use anyhow::Result;
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::{wasm, Module};
|
||||
use wasmtime_runtime::InstanceHandle;
|
||||
|
||||
pub fn create_handle_with_memory(store: &Store, memory: &MemoryType) -> Result<InstanceHandle> {
|
||||
let mut module = Module::new();
|
||||
|
||||
let memory = wasm::Memory {
|
||||
minimum: memory.limits().min(),
|
||||
maximum: memory.limits().max(),
|
||||
shared: false, // TODO
|
||||
};
|
||||
let tunable = Default::default();
|
||||
|
||||
let memory_plan = wasmtime_environ::MemoryPlan::for_memory(memory, &tunable);
|
||||
let memory_id = module.local.memory_plans.push(memory_plan);
|
||||
module.exports.insert(
|
||||
"memory".to_string(),
|
||||
wasmtime_environ::Export::Memory(memory_id),
|
||||
);
|
||||
|
||||
create_handle(module, store, PrimaryMap::new(), Box::new(()))
|
||||
}
|
||||
69
crates/api/src/trampoline/mod.rs
Normal file
69
crates/api/src/trampoline/mod.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
//! Utility module to create trampolines in/out WebAssembly module.
|
||||
|
||||
mod create_handle;
|
||||
mod func;
|
||||
mod global;
|
||||
mod memory;
|
||||
mod table;
|
||||
|
||||
use self::func::create_handle_with_function;
|
||||
use self::global::create_global;
|
||||
use self::memory::create_handle_with_memory;
|
||||
use self::table::create_handle_with_table;
|
||||
use super::{Callable, FuncType, GlobalType, MemoryType, Store, TableType, Val};
|
||||
use anyhow::Result;
|
||||
use std::any::Any;
|
||||
use std::rc::Rc;
|
||||
use wasmtime_runtime::VMFunctionBody;
|
||||
|
||||
pub fn generate_func_export(
|
||||
ft: &FuncType,
|
||||
func: &Rc<dyn Callable + 'static>,
|
||||
store: &Store,
|
||||
) -> Result<(wasmtime_runtime::InstanceHandle, wasmtime_runtime::Export)> {
|
||||
let instance = create_handle_with_function(ft, func, store)?;
|
||||
let export = instance.lookup("trampoline").expect("trampoline export");
|
||||
Ok((instance, export))
|
||||
}
|
||||
|
||||
/// Note that this is `unsafe` since `func` must be a valid function pointer and
|
||||
/// have a signature which matches `ft`, otherwise the returned
|
||||
/// instance/export/etc may exhibit undefined behavior.
|
||||
pub unsafe fn generate_raw_func_export(
|
||||
ft: &FuncType,
|
||||
func: *mut [VMFunctionBody],
|
||||
store: &Store,
|
||||
state: Box<dyn Any>,
|
||||
) -> Result<(wasmtime_runtime::InstanceHandle, wasmtime_runtime::Export)> {
|
||||
let instance = func::create_handle_with_raw_function(ft, func, store, state)?;
|
||||
let export = instance.lookup("trampoline").expect("trampoline export");
|
||||
Ok((instance, export))
|
||||
}
|
||||
|
||||
pub fn generate_global_export(
|
||||
store: &Store,
|
||||
gt: &GlobalType,
|
||||
val: Val,
|
||||
) -> Result<(wasmtime_runtime::InstanceHandle, wasmtime_runtime::Export)> {
|
||||
let instance = create_global(store, gt, val)?;
|
||||
let export = instance.lookup("global").expect("global export");
|
||||
Ok((instance, export))
|
||||
}
|
||||
|
||||
pub fn generate_memory_export(
|
||||
store: &Store,
|
||||
m: &MemoryType,
|
||||
) -> Result<(wasmtime_runtime::InstanceHandle, wasmtime_runtime::Export)> {
|
||||
let instance = create_handle_with_memory(store, m)?;
|
||||
let export = instance.lookup("memory").expect("memory export");
|
||||
Ok((instance, export))
|
||||
}
|
||||
|
||||
pub fn generate_table_export(
|
||||
store: &Store,
|
||||
t: &TableType,
|
||||
) -> Result<(wasmtime_runtime::InstanceHandle, wasmtime_runtime::Export)> {
|
||||
let instance = create_handle_with_table(store, t)?;
|
||||
let export = instance.lookup("table").expect("table export");
|
||||
Ok((instance, export))
|
||||
}
|
||||
30
crates/api/src/trampoline/table.rs
Normal file
30
crates/api/src/trampoline/table.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use super::create_handle::create_handle;
|
||||
use crate::Store;
|
||||
use crate::{TableType, ValType};
|
||||
use anyhow::{bail, Result};
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::{wasm, Module};
|
||||
use wasmtime_runtime::InstanceHandle;
|
||||
|
||||
pub fn create_handle_with_table(store: &Store, table: &TableType) -> Result<InstanceHandle> {
|
||||
let mut module = Module::new();
|
||||
|
||||
let table = wasm::Table {
|
||||
minimum: table.limits().min(),
|
||||
maximum: table.limits().max(),
|
||||
ty: match table.element() {
|
||||
ValType::FuncRef => wasm::TableElementType::Func,
|
||||
_ => bail!("cannot support {:?} as a table element", table.element()),
|
||||
},
|
||||
};
|
||||
let tunable = Default::default();
|
||||
|
||||
let table_plan = wasmtime_environ::TablePlan::for_table(table, &tunable);
|
||||
let table_id = module.local.table_plans.push(table_plan);
|
||||
module.exports.insert(
|
||||
"table".to_string(),
|
||||
wasmtime_environ::Export::Table(table_id),
|
||||
);
|
||||
|
||||
create_handle(module, store, PrimaryMap::new(), Box::new(()))
|
||||
}
|
||||
119
crates/api/src/trap.rs
Normal file
119
crates/api/src/trap.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use crate::frame_info::FRAME_INFO;
|
||||
use crate::FrameInfo;
|
||||
use backtrace::Backtrace;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A struct representing an aborted instruction execution, with a message
|
||||
/// indicating the cause.
|
||||
#[derive(Clone)]
|
||||
pub struct Trap {
|
||||
inner: Arc<TrapInner>,
|
||||
}
|
||||
|
||||
struct TrapInner {
|
||||
message: String,
|
||||
wasm_trace: Vec<FrameInfo>,
|
||||
native_trace: Backtrace,
|
||||
}
|
||||
|
||||
fn _assert_trap_is_sync_and_send(t: &Trap) -> (&dyn Sync, &dyn Send) {
|
||||
(t, t)
|
||||
}
|
||||
|
||||
impl Trap {
|
||||
/// Creates a new `Trap` with `message`.
|
||||
/// # Example
|
||||
/// ```
|
||||
/// let trap = wasmtime::Trap::new("unexpected error");
|
||||
/// assert_eq!("unexpected error", trap.message());
|
||||
/// ```
|
||||
pub fn new<I: Into<String>>(message: I) -> Self {
|
||||
Trap::new_with_trace(message.into(), Backtrace::new_unresolved())
|
||||
}
|
||||
|
||||
pub(crate) fn from_jit(jit: wasmtime_runtime::Trap) -> Self {
|
||||
match jit {
|
||||
wasmtime_runtime::Trap::User(error) => {
|
||||
// Since we're the only one using the wasmtime internals (in
|
||||
// theory) we should only see user errors which were originally
|
||||
// created from our own `Trap` type (see the trampoline module
|
||||
// with functions).
|
||||
//
|
||||
// If this unwrap trips for someone we'll need to tweak the
|
||||
// return type of this function to probably be `anyhow::Error`
|
||||
// or something like that.
|
||||
*error
|
||||
.downcast()
|
||||
.expect("only `Trap` user errors are supported")
|
||||
}
|
||||
wasmtime_runtime::Trap::Wasm { desc, backtrace } => {
|
||||
Trap::new_with_trace(desc.to_string(), backtrace)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn new_with_trace(message: String, native_trace: Backtrace) -> Self {
|
||||
let mut wasm_trace = Vec::new();
|
||||
for frame in native_trace.frames() {
|
||||
let pc = frame.ip() as usize;
|
||||
if let Some(info) = FRAME_INFO.lookup(pc) {
|
||||
wasm_trace.push(info);
|
||||
}
|
||||
}
|
||||
Trap {
|
||||
inner: Arc::new(TrapInner {
|
||||
message,
|
||||
wasm_trace,
|
||||
native_trace,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference the `message` stored in `Trap`.
|
||||
pub fn message(&self) -> &str {
|
||||
&self.inner.message
|
||||
}
|
||||
|
||||
/// Returns a list of function frames in WebAssembly code that led to this
|
||||
/// trap happening.
|
||||
pub fn trace(&self) -> &[FrameInfo] {
|
||||
&self.inner.wasm_trace
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Trap {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Trap")
|
||||
.field("message", &self.inner.message)
|
||||
.field("wasm_trace", &self.inner.wasm_trace)
|
||||
.field("native_trace", &self.inner.native_trace)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Trap {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.inner.message)?;
|
||||
let trace = self.trace();
|
||||
if trace.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
writeln!(f, "\nwasm backtrace:")?;
|
||||
for (i, frame) in self.trace().iter().enumerate() {
|
||||
let name = frame.module_name().unwrap_or("<unknown>");
|
||||
write!(f, " {}: {}!", i, name)?;
|
||||
match frame.func_name() {
|
||||
Some(name) => match rustc_demangle::try_demangle(name) {
|
||||
Ok(name) => write!(f, "{}", name)?,
|
||||
Err(_) => write!(f, "{}", name)?,
|
||||
},
|
||||
None => write!(f, "<wasm function {}>", frame.func_index())?,
|
||||
}
|
||||
writeln!(f, "")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Trap {}
|
||||
437
crates/api/src/types.rs
Normal file
437
crates/api/src/types.rs
Normal file
@@ -0,0 +1,437 @@
|
||||
use wasmtime_environ::{ir, wasm};
|
||||
|
||||
// Type Representations
|
||||
|
||||
// Type attributes
|
||||
|
||||
/// Indicator of whether a global is mutable or not
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Mutability {
|
||||
/// The global is constant and its value does not change
|
||||
Const,
|
||||
/// The value of the global can change over time
|
||||
Var,
|
||||
}
|
||||
|
||||
/// Limits of tables/memories where the units of the limits are defined by the
|
||||
/// table/memory types.
|
||||
///
|
||||
/// A minimum is always available but the maximum may not be present.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Limits {
|
||||
min: u32,
|
||||
max: Option<u32>,
|
||||
}
|
||||
|
||||
impl Limits {
|
||||
/// Creates a new set of limits with the minimum and maximum both specified.
|
||||
pub fn new(min: u32, max: Option<u32>) -> Limits {
|
||||
Limits { min, max }
|
||||
}
|
||||
|
||||
/// Creates a new `Limits` with the `min` specified and no maximum specified.
|
||||
pub fn at_least(min: u32) -> Limits {
|
||||
Limits::new(min, None)
|
||||
}
|
||||
|
||||
/// Returns the minimum amount for these limits.
|
||||
pub fn min(&self) -> u32 {
|
||||
self.min
|
||||
}
|
||||
|
||||
/// Returns the maximum amount for these limits, if specified.
|
||||
pub fn max(&self) -> Option<u32> {
|
||||
self.max
|
||||
}
|
||||
}
|
||||
|
||||
// Value Types
|
||||
|
||||
/// A list of all possible value types in WebAssembly.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum ValType {
|
||||
/// Signed 32 bit integer.
|
||||
I32,
|
||||
/// Signed 64 bit integer.
|
||||
I64,
|
||||
/// Floating point 32 bit integer.
|
||||
F32,
|
||||
/// Floating point 64 bit integer.
|
||||
F64,
|
||||
/// A 128 bit number.
|
||||
V128,
|
||||
/// A reference to opaque data in the Wasm instance.
|
||||
AnyRef, /* = 128 */
|
||||
/// A reference to a Wasm function.
|
||||
FuncRef,
|
||||
}
|
||||
|
||||
impl ValType {
|
||||
/// Returns true if `ValType` matches any of the numeric types. (e.g. `I32`,
|
||||
/// `I64`, `F32`, `F64`).
|
||||
pub fn is_num(&self) -> bool {
|
||||
match self {
|
||||
ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if `ValType` matches either of the reference types.
|
||||
pub fn is_ref(&self) -> bool {
|
||||
match self {
|
||||
ValType::AnyRef | ValType::FuncRef => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_wasmtime_type(&self) -> Option<ir::Type> {
|
||||
match self {
|
||||
ValType::I32 => Some(ir::types::I32),
|
||||
ValType::I64 => Some(ir::types::I64),
|
||||
ValType::F32 => Some(ir::types::F32),
|
||||
ValType::F64 => Some(ir::types::F64),
|
||||
ValType::V128 => Some(ir::types::I8X16),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_wasmtime_type(ty: ir::Type) -> Option<ValType> {
|
||||
match ty {
|
||||
ir::types::I32 => Some(ValType::I32),
|
||||
ir::types::I64 => Some(ValType::I64),
|
||||
ir::types::F32 => Some(ValType::F32),
|
||||
ir::types::F64 => Some(ValType::F64),
|
||||
ir::types::I8X16 => Some(ValType::V128),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// External Types
|
||||
|
||||
/// A list of all possible types which can be externally referenced from a
|
||||
/// WebAssembly module.
|
||||
///
|
||||
/// This list can be found in [`ImportType`] or [`ExportType`], so these types
|
||||
/// can either be imported or exported.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ExternType {
|
||||
/// This external type is the type of a WebAssembly function.
|
||||
Func(FuncType),
|
||||
/// This external type is the type of a WebAssembly global.
|
||||
Global(GlobalType),
|
||||
/// This external type is the type of a WebAssembly table.
|
||||
Table(TableType),
|
||||
/// This external type is the type of a WebAssembly memory.
|
||||
Memory(MemoryType),
|
||||
}
|
||||
|
||||
macro_rules! accessors {
|
||||
($(($variant:ident($ty:ty) $get:ident $unwrap:ident))*) => ($(
|
||||
/// Attempt to return the underlying type of this external type,
|
||||
/// returning `None` if it is a different type.
|
||||
pub fn $get(&self) -> Option<&$ty> {
|
||||
if let ExternType::$variant(e) = self {
|
||||
Some(e)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the underlying descriptor of this [`ExternType`], panicking
|
||||
/// if it is a different type.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `self` is not of the right type.
|
||||
pub fn $unwrap(&self) -> &$ty {
|
||||
self.$get().expect(concat!("expected ", stringify!($ty)))
|
||||
}
|
||||
)*)
|
||||
}
|
||||
|
||||
impl ExternType {
|
||||
accessors! {
|
||||
(Func(FuncType) func unwrap_func)
|
||||
(Global(GlobalType) global unwrap_global)
|
||||
(Table(TableType) table unwrap_table)
|
||||
(Memory(MemoryType) memory unwrap_memory)
|
||||
}
|
||||
}
|
||||
|
||||
// Function Types
|
||||
fn from_wasmtime_abiparam(param: &ir::AbiParam) -> Option<ValType> {
|
||||
assert_eq!(param.purpose, ir::ArgumentPurpose::Normal);
|
||||
ValType::from_wasmtime_type(param.value_type)
|
||||
}
|
||||
|
||||
/// A descriptor for a function in a WebAssembly module.
|
||||
///
|
||||
/// WebAssembly functions can have 0 or more parameters and results.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FuncType {
|
||||
params: Box<[ValType]>,
|
||||
results: Box<[ValType]>,
|
||||
}
|
||||
|
||||
impl FuncType {
|
||||
/// Creates a new function descriptor from the given parameters and results.
|
||||
///
|
||||
/// The function descriptor returned will represent a function which takes
|
||||
/// `params` as arguments and returns `results` when it is finished.
|
||||
pub fn new(params: Box<[ValType]>, results: Box<[ValType]>) -> FuncType {
|
||||
FuncType { params, results }
|
||||
}
|
||||
|
||||
/// Returns the list of parameter types for this function.
|
||||
pub fn params(&self) -> &[ValType] {
|
||||
&self.params
|
||||
}
|
||||
|
||||
/// Returns the list of result types for this function.
|
||||
pub fn results(&self) -> &[ValType] {
|
||||
&self.results
|
||||
}
|
||||
|
||||
/// Returns `Some` if this function signature was compatible with cranelift,
|
||||
/// or `None` if one of the types/results wasn't supported or compatible
|
||||
/// with cranelift.
|
||||
pub(crate) fn get_wasmtime_signature(&self, pointer_type: ir::Type) -> Option<ir::Signature> {
|
||||
use wasmtime_environ::ir::{types, AbiParam, ArgumentPurpose, Signature};
|
||||
use wasmtime_jit::native;
|
||||
let call_conv = native::call_conv();
|
||||
let mut params = self
|
||||
.params
|
||||
.iter()
|
||||
.map(|p| p.get_wasmtime_type().map(AbiParam::new))
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
let returns = self
|
||||
.results
|
||||
.iter()
|
||||
.map(|p| p.get_wasmtime_type().map(AbiParam::new))
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
params.insert(0, AbiParam::special(types::I64, ArgumentPurpose::VMContext));
|
||||
params.insert(1, AbiParam::new(pointer_type));
|
||||
|
||||
Some(Signature {
|
||||
params,
|
||||
returns,
|
||||
call_conv,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns `None` if any types in the signature can't be converted to the
|
||||
/// types in this crate, but that should very rarely happen and largely only
|
||||
/// indicate a bug in our cranelift integration.
|
||||
pub(crate) fn from_wasmtime_signature(signature: ir::Signature) -> Option<FuncType> {
|
||||
let params = signature
|
||||
.params
|
||||
.iter()
|
||||
.skip(2) // skip the caller/callee vmctx
|
||||
.map(|p| from_wasmtime_abiparam(p))
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
let results = signature
|
||||
.returns
|
||||
.iter()
|
||||
.map(|p| from_wasmtime_abiparam(p))
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
Some(FuncType {
|
||||
params: params.into_boxed_slice(),
|
||||
results: results.into_boxed_slice(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Global Types
|
||||
|
||||
/// A WebAssembly global descriptor.
|
||||
///
|
||||
/// This type describes an instance of a global in a WebAssembly module. Globals
|
||||
/// are local to an [`Instance`](crate::Instance) and are either immutable or
|
||||
/// mutable.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GlobalType {
|
||||
content: ValType,
|
||||
mutability: Mutability,
|
||||
}
|
||||
|
||||
impl GlobalType {
|
||||
/// Creates a new global descriptor of the specified `content` type and
|
||||
/// whether or not it's mutable.
|
||||
pub fn new(content: ValType, mutability: Mutability) -> GlobalType {
|
||||
GlobalType {
|
||||
content,
|
||||
mutability,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value type of this global descriptor.
|
||||
pub fn content(&self) -> &ValType {
|
||||
&self.content
|
||||
}
|
||||
|
||||
/// Returns whether or not this global is mutable.
|
||||
pub fn mutability(&self) -> Mutability {
|
||||
self.mutability
|
||||
}
|
||||
|
||||
/// Returns `None` if the wasmtime global has a type that we can't
|
||||
/// represent, but that should only very rarely happen and indicate a bug.
|
||||
pub(crate) fn from_wasmtime_global(global: &wasm::Global) -> Option<GlobalType> {
|
||||
let ty = ValType::from_wasmtime_type(global.ty)?;
|
||||
let mutability = if global.mutability {
|
||||
Mutability::Var
|
||||
} else {
|
||||
Mutability::Const
|
||||
};
|
||||
Some(GlobalType::new(ty, mutability))
|
||||
}
|
||||
}
|
||||
|
||||
// Table Types
|
||||
|
||||
/// A descriptor for a table in a WebAssembly module.
|
||||
///
|
||||
/// Tables are contiguous chunks of a specific element, typically a `funcref` or
|
||||
/// an `anyref`. The most common use for tables is a function table through
|
||||
/// which `call_indirect` can invoke other functions.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TableType {
|
||||
element: ValType,
|
||||
limits: Limits,
|
||||
}
|
||||
|
||||
impl TableType {
|
||||
/// Creates a new table descriptor which will contain the specified
|
||||
/// `element` and have the `limits` applied to its length.
|
||||
pub fn new(element: ValType, limits: Limits) -> TableType {
|
||||
TableType { element, limits }
|
||||
}
|
||||
|
||||
/// Returns the element value type of this table.
|
||||
pub fn element(&self) -> &ValType {
|
||||
&self.element
|
||||
}
|
||||
|
||||
/// Returns the limits, in units of elements, of this table.
|
||||
pub fn limits(&self) -> &Limits {
|
||||
&self.limits
|
||||
}
|
||||
|
||||
pub(crate) fn from_wasmtime_table(table: &wasm::Table) -> TableType {
|
||||
assert!(if let wasm::TableElementType::Func = table.ty {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
});
|
||||
let ty = ValType::FuncRef;
|
||||
let limits = Limits::new(table.minimum, table.maximum);
|
||||
TableType::new(ty, limits)
|
||||
}
|
||||
}
|
||||
|
||||
// Memory Types
|
||||
|
||||
/// A descriptor for a WebAssembly memory type.
|
||||
///
|
||||
/// Memories are described in units of pages (64KB) and represent contiguous
|
||||
/// chunks of addressable memory.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MemoryType {
|
||||
limits: Limits,
|
||||
}
|
||||
|
||||
impl MemoryType {
|
||||
/// Creates a new descriptor for a WebAssembly memory given the specified
|
||||
/// limits of the memory.
|
||||
pub fn new(limits: Limits) -> MemoryType {
|
||||
MemoryType { limits }
|
||||
}
|
||||
|
||||
/// Returns the limits (in pages) that are configured for this memory.
|
||||
pub fn limits(&self) -> &Limits {
|
||||
&self.limits
|
||||
}
|
||||
|
||||
pub(crate) fn from_wasmtime_memory(memory: &wasm::Memory) -> MemoryType {
|
||||
MemoryType::new(Limits::new(memory.minimum, memory.maximum))
|
||||
}
|
||||
}
|
||||
|
||||
// Import Types
|
||||
|
||||
/// A descriptor for an imported value into a wasm module.
|
||||
///
|
||||
/// This type is primarily accessed from the
|
||||
/// [`Module::imports`](crate::Module::imports) API. Each [`ImportType`]
|
||||
/// describes an import into the wasm module with the module/name that it's
|
||||
/// imported from as well as the type of item that's being imported.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ImportType {
|
||||
module: String,
|
||||
name: String,
|
||||
ty: ExternType,
|
||||
}
|
||||
|
||||
impl ImportType {
|
||||
/// Creates a new import descriptor which comes from `module` and `name` and
|
||||
/// is of type `ty`.
|
||||
pub fn new(module: &str, name: &str, ty: ExternType) -> ImportType {
|
||||
ImportType {
|
||||
module: module.to_string(),
|
||||
name: name.to_string(),
|
||||
ty,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the module name that this import is expected to come from.
|
||||
pub fn module(&self) -> &str {
|
||||
&self.module
|
||||
}
|
||||
|
||||
/// Returns the field name of the module that this import is expected to
|
||||
/// come from.
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Returns the expected type of this import.
|
||||
pub fn ty(&self) -> &ExternType {
|
||||
&self.ty
|
||||
}
|
||||
}
|
||||
|
||||
// Export Types
|
||||
|
||||
/// A descriptor for an exported WebAssembly value.
|
||||
///
|
||||
/// This type is primarily accessed from the
|
||||
/// [`Module::exports`](crate::Module::exports) accessor and describes what
|
||||
/// names are exported from a wasm module and the type of the item that is
|
||||
/// exported.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExportType {
|
||||
name: String,
|
||||
ty: ExternType,
|
||||
}
|
||||
|
||||
impl ExportType {
|
||||
/// Creates a new export which is exported with the given `name` and has the
|
||||
/// given `ty`.
|
||||
pub fn new(name: &str, ty: ExternType) -> ExportType {
|
||||
ExportType {
|
||||
name: name.to_string(),
|
||||
ty,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the name by which this export is known by.
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Returns the type of this export.
|
||||
pub fn ty(&self) -> &ExternType {
|
||||
&self.ty
|
||||
}
|
||||
}
|
||||
31
crates/api/src/unix.rs
Normal file
31
crates/api/src/unix.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
//! Unix-specific extension for the `wasmtime` crate.
|
||||
//!
|
||||
//! This module is only available on Unix targets, for example Linux and macOS.
|
||||
//! It is not available on Windows, for example. Note that the import path for
|
||||
//! this module is `wasmtime::unix::...`, which is intended to emphasize that it
|
||||
//! is platform-specific.
|
||||
//!
|
||||
//! The traits contained in this module are intended to extend various types
|
||||
//! throughout the `wasmtime` crate with extra functionality that's only
|
||||
//! available on Unix.
|
||||
|
||||
use crate::Instance;
|
||||
|
||||
/// Extensions for the [`Instance`] type only available on Unix.
|
||||
pub trait InstanceExt {
|
||||
// TODO: needs more docs?
|
||||
/// The signal handler must be
|
||||
/// [async-signal-safe](http://man7.org/linux/man-pages/man7/signal-safety.7.html).
|
||||
unsafe fn set_signal_handler<H>(&self, handler: H)
|
||||
where
|
||||
H: 'static + Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool;
|
||||
}
|
||||
|
||||
impl InstanceExt for Instance {
|
||||
unsafe fn set_signal_handler<H>(&self, handler: H)
|
||||
where
|
||||
H: 'static + Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool,
|
||||
{
|
||||
self.instance_handle.clone().set_signal_handler(handler);
|
||||
}
|
||||
}
|
||||
224
crates/api/src/values.rs
Normal file
224
crates/api/src/values.rs
Normal file
@@ -0,0 +1,224 @@
|
||||
use crate::r#ref::AnyRef;
|
||||
use crate::{Func, Store, ValType};
|
||||
use anyhow::{bail, Result};
|
||||
use std::ptr;
|
||||
use wasmtime_environ::ir;
|
||||
|
||||
/// Possible runtime values that a WebAssembly module can either consume or
|
||||
/// produce.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Val {
|
||||
/// A 32-bit integer
|
||||
I32(i32),
|
||||
|
||||
/// A 64-bit integer
|
||||
I64(i64),
|
||||
|
||||
/// A 32-bit float.
|
||||
///
|
||||
/// Note that the raw bits of the float are stored here, and you can use
|
||||
/// `f32::from_bits` to create an `f32` value.
|
||||
F32(u32),
|
||||
|
||||
/// A 64-bit float.
|
||||
///
|
||||
/// Note that the raw bits of the float are stored here, and you can use
|
||||
/// `f64::from_bits` to create an `f64` value.
|
||||
F64(u64),
|
||||
|
||||
/// An `anyref` value which can hold opaque data to the wasm instance itself.
|
||||
///
|
||||
/// Note that this is a nullable value as well.
|
||||
AnyRef(AnyRef),
|
||||
|
||||
/// A first-class reference to a WebAssembly function.
|
||||
FuncRef(Func),
|
||||
|
||||
/// A 128-bit number
|
||||
V128(u128),
|
||||
}
|
||||
|
||||
macro_rules! accessors {
|
||||
($bind:ident $(($variant:ident($ty:ty) $get:ident $unwrap:ident $cvt:expr))*) => ($(
|
||||
/// Attempt to access the underlying value of this `Val`, returning
|
||||
/// `None` if it is not the correct type.
|
||||
pub fn $get(&self) -> Option<$ty> {
|
||||
if let Val::$variant($bind) = self {
|
||||
Some($cvt)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the underlying value of this `Val`, panicking if it's the
|
||||
/// wrong type.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `self` is not of the right type.
|
||||
pub fn $unwrap(&self) -> $ty {
|
||||
self.$get().expect(concat!("expected ", stringify!($ty)))
|
||||
}
|
||||
)*)
|
||||
}
|
||||
|
||||
impl Val {
|
||||
/// Returns a null `anyref` value.
|
||||
pub fn null() -> Val {
|
||||
Val::AnyRef(AnyRef::null())
|
||||
}
|
||||
|
||||
/// Returns the corresponding [`ValType`] for this `Val`.
|
||||
pub fn ty(&self) -> ValType {
|
||||
match self {
|
||||
Val::I32(_) => ValType::I32,
|
||||
Val::I64(_) => ValType::I64,
|
||||
Val::F32(_) => ValType::F32,
|
||||
Val::F64(_) => ValType::F64,
|
||||
Val::AnyRef(_) => ValType::AnyRef,
|
||||
Val::FuncRef(_) => ValType::FuncRef,
|
||||
Val::V128(_) => ValType::V128,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn write_value_to(&self, p: *mut i128) {
|
||||
match self {
|
||||
Val::I32(i) => ptr::write(p as *mut i32, *i),
|
||||
Val::I64(i) => ptr::write(p as *mut i64, *i),
|
||||
Val::F32(u) => ptr::write(p as *mut u32, *u),
|
||||
Val::F64(u) => ptr::write(p as *mut u64, *u),
|
||||
Val::V128(b) => ptr::write(p as *mut u128, *b),
|
||||
_ => unimplemented!("Val::write_value_to"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn read_value_from(p: *const i128, ty: ir::Type) -> Val {
|
||||
match ty {
|
||||
ir::types::I32 => Val::I32(ptr::read(p as *const i32)),
|
||||
ir::types::I64 => Val::I64(ptr::read(p as *const i64)),
|
||||
ir::types::F32 => Val::F32(ptr::read(p as *const u32)),
|
||||
ir::types::F64 => Val::F64(ptr::read(p as *const u64)),
|
||||
ir::types::I8X16 => Val::V128(ptr::read(p as *const u128)),
|
||||
_ => unimplemented!("Val::read_value_from"),
|
||||
}
|
||||
}
|
||||
|
||||
accessors! {
|
||||
e
|
||||
(I32(i32) i32 unwrap_i32 *e)
|
||||
(I64(i64) i64 unwrap_i64 *e)
|
||||
(F32(f32) f32 unwrap_f32 f32::from_bits(*e))
|
||||
(F64(f64) f64 unwrap_f64 f64::from_bits(*e))
|
||||
(FuncRef(&Func) funcref unwrap_funcref e)
|
||||
(V128(u128) v128 unwrap_v128 *e)
|
||||
}
|
||||
|
||||
/// Attempt to access the underlying value of this `Val`, returning
|
||||
/// `None` if it is not the correct type.
|
||||
///
|
||||
/// This will return `Some` for both the `AnyRef` and `FuncRef` types.
|
||||
pub fn anyref(&self) -> Option<AnyRef> {
|
||||
match self {
|
||||
Val::AnyRef(e) => Some(e.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the underlying value of this `Val`, panicking if it's the
|
||||
/// wrong type.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `self` is not of the right type.
|
||||
pub fn unwrap_anyref(&self) -> AnyRef {
|
||||
self.anyref().expect("expected anyref")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Val {
|
||||
fn from(val: i32) -> Val {
|
||||
Val::I32(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for Val {
|
||||
fn from(val: i64) -> Val {
|
||||
Val::I64(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Val {
|
||||
fn from(val: f32) -> Val {
|
||||
Val::F32(val.to_bits())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Val {
|
||||
fn from(val: f64) -> Val {
|
||||
Val::F64(val.to_bits())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AnyRef> for Val {
|
||||
fn from(val: AnyRef) -> Val {
|
||||
Val::AnyRef(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Func> for Val {
|
||||
fn from(val: Func) -> Val {
|
||||
Val::FuncRef(val)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_checked_anyfunc(
|
||||
val: Val,
|
||||
store: &Store,
|
||||
) -> Result<wasmtime_runtime::VMCallerCheckedAnyfunc> {
|
||||
Ok(match val {
|
||||
Val::AnyRef(AnyRef::Null) => wasmtime_runtime::VMCallerCheckedAnyfunc {
|
||||
func_ptr: ptr::null(),
|
||||
type_index: wasmtime_runtime::VMSharedSignatureIndex::default(),
|
||||
vmctx: ptr::null_mut(),
|
||||
},
|
||||
Val::FuncRef(f) => {
|
||||
let (vmctx, func_ptr, signature) = match f.wasmtime_export() {
|
||||
wasmtime_runtime::Export::Function {
|
||||
vmctx,
|
||||
address,
|
||||
signature,
|
||||
} => (*vmctx, *address, signature),
|
||||
_ => panic!("expected function export"),
|
||||
};
|
||||
let type_index = store.compiler().signatures().register(signature);
|
||||
wasmtime_runtime::VMCallerCheckedAnyfunc {
|
||||
func_ptr,
|
||||
type_index,
|
||||
vmctx,
|
||||
}
|
||||
}
|
||||
_ => bail!("val is not funcref"),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn from_checked_anyfunc(
|
||||
item: wasmtime_runtime::VMCallerCheckedAnyfunc,
|
||||
store: &Store,
|
||||
) -> Val {
|
||||
if item.type_index == wasmtime_runtime::VMSharedSignatureIndex::default() {
|
||||
return Val::AnyRef(AnyRef::Null);
|
||||
}
|
||||
let signature = store
|
||||
.compiler()
|
||||
.signatures()
|
||||
.lookup(item.type_index)
|
||||
.expect("signature");
|
||||
let instance_handle = unsafe { wasmtime_runtime::InstanceHandle::from_vmctx(item.vmctx) };
|
||||
let export = wasmtime_runtime::Export::Function {
|
||||
address: item.func_ptr,
|
||||
signature,
|
||||
vmctx: item.vmctx,
|
||||
};
|
||||
let f = Func::from_wasmtime_function(export, store, instance_handle);
|
||||
Val::FuncRef(f)
|
||||
}
|
||||
31
crates/api/src/windows.rs
Normal file
31
crates/api/src/windows.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
//! windows-specific extension for the `wasmtime` crate.
|
||||
//!
|
||||
//! This module is only available on Windows targets.
|
||||
//! It is not available on Linux or macOS, for example. Note that the import path for
|
||||
//! this module is `wasmtime::windows::...`, which is intended to emphasize that it
|
||||
//! is platform-specific.
|
||||
//!
|
||||
//! The traits contained in this module are intended to extend various types
|
||||
//! throughout the `wasmtime` crate with extra functionality that's only
|
||||
//! available on Windows.
|
||||
|
||||
use crate::Instance;
|
||||
|
||||
/// Extensions for the [`Instance`] type only available on Windows.
|
||||
pub trait InstanceExt {
|
||||
/// Configures a custom signal handler to execute.
|
||||
///
|
||||
/// TODO: needs more documentation.
|
||||
unsafe fn set_signal_handler<H>(&self, handler: H)
|
||||
where
|
||||
H: 'static + Fn(winapi::um::winnt::PEXCEPTION_POINTERS) -> bool;
|
||||
}
|
||||
|
||||
impl InstanceExt for Instance {
|
||||
unsafe fn set_signal_handler<H>(&self, handler: H)
|
||||
where
|
||||
H: 'static + Fn(winapi::um::winnt::PEXCEPTION_POINTERS) -> bool,
|
||||
{
|
||||
self.instance_handle.clone().set_signal_handler(handler);
|
||||
}
|
||||
}
|
||||
38
crates/api/tests/examples.rs
Normal file
38
crates/api/tests/examples.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use std::env;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
fn run_example(name: &'static str) {
|
||||
let cargo = env::var("CARGO").unwrap_or("cargo".to_string());
|
||||
let pkg_dir = env!("CARGO_MANIFEST_DIR");
|
||||
assert!(
|
||||
Command::new(cargo)
|
||||
.current_dir(pkg_dir)
|
||||
.stdout(Stdio::null())
|
||||
.args(&["run", "-q", "--example", name])
|
||||
.status()
|
||||
.expect("success")
|
||||
.success(),
|
||||
"failed to execute the example '{}'",
|
||||
name,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_hello_example() {
|
||||
run_example("hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_gcd_example() {
|
||||
run_example("gcd");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_memory_example() {
|
||||
run_example("memory");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_multi_example() {
|
||||
run_example("multi");
|
||||
}
|
||||
54
crates/api/tests/externals.rs
Normal file
54
crates/api/tests/externals.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use wasmtime::*;
|
||||
|
||||
#[test]
|
||||
fn bad_globals() {
|
||||
let ty = GlobalType::new(ValType::I32, Mutability::Var);
|
||||
assert!(Global::new(&Store::default(), ty.clone(), Val::I64(0)).is_err());
|
||||
assert!(Global::new(&Store::default(), ty.clone(), Val::F32(0)).is_err());
|
||||
assert!(Global::new(&Store::default(), ty.clone(), Val::F64(0)).is_err());
|
||||
|
||||
let ty = GlobalType::new(ValType::I32, Mutability::Const);
|
||||
let g = Global::new(&Store::default(), ty.clone(), Val::I32(0)).unwrap();
|
||||
assert!(g.set(Val::I32(1)).is_err());
|
||||
|
||||
let ty = GlobalType::new(ValType::I32, Mutability::Var);
|
||||
let g = Global::new(&Store::default(), ty.clone(), Val::I32(0)).unwrap();
|
||||
assert!(g.set(Val::I64(0)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_tables() {
|
||||
// i32 not supported yet
|
||||
let ty = TableType::new(ValType::I32, Limits::new(0, Some(1)));
|
||||
assert!(Table::new(&Store::default(), ty.clone(), Val::I32(0)).is_err());
|
||||
|
||||
// mismatched initializer
|
||||
let ty = TableType::new(ValType::FuncRef, Limits::new(0, Some(1)));
|
||||
assert!(Table::new(&Store::default(), ty.clone(), Val::I32(0)).is_err());
|
||||
|
||||
// get out of bounds
|
||||
let ty = TableType::new(ValType::FuncRef, Limits::new(0, Some(1)));
|
||||
let t = Table::new(&Store::default(), ty.clone(), Val::AnyRef(AnyRef::Null)).unwrap();
|
||||
assert!(t.get(0).is_none());
|
||||
assert!(t.get(u32::max_value()).is_none());
|
||||
|
||||
// set out of bounds or wrong type
|
||||
let ty = TableType::new(ValType::FuncRef, Limits::new(1, Some(1)));
|
||||
let t = Table::new(&Store::default(), ty.clone(), Val::AnyRef(AnyRef::Null)).unwrap();
|
||||
assert!(t.set(0, Val::I32(0)).is_err());
|
||||
assert!(t.set(0, Val::AnyRef(AnyRef::Null)).is_ok());
|
||||
assert!(t.set(1, Val::AnyRef(AnyRef::Null)).is_err());
|
||||
|
||||
// grow beyond max
|
||||
let ty = TableType::new(ValType::FuncRef, Limits::new(1, Some(1)));
|
||||
let t = Table::new(&Store::default(), ty.clone(), Val::AnyRef(AnyRef::Null)).unwrap();
|
||||
assert!(t.grow(0, Val::AnyRef(AnyRef::Null)).is_ok());
|
||||
assert!(t.grow(1, Val::AnyRef(AnyRef::Null)).is_err());
|
||||
assert_eq!(t.size(), 1);
|
||||
|
||||
// grow wrong type
|
||||
let ty = TableType::new(ValType::FuncRef, Limits::new(1, Some(2)));
|
||||
let t = Table::new(&Store::default(), ty.clone(), Val::AnyRef(AnyRef::Null)).unwrap();
|
||||
assert!(t.grow(1, Val::I32(0)).is_err());
|
||||
assert_eq!(t.size(), 1);
|
||||
}
|
||||
291
crates/api/tests/func.rs
Normal file
291
crates/api/tests/func.rs
Normal file
@@ -0,0 +1,291 @@
|
||||
use anyhow::Result;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
|
||||
use wasmtime::{Callable, Func, FuncType, Instance, Module, Store, Trap, Val, ValType};
|
||||
|
||||
#[test]
|
||||
fn func_constructors() {
|
||||
let store = Store::default();
|
||||
Func::wrap0(&store, || {});
|
||||
Func::wrap1(&store, |_: i32| {});
|
||||
Func::wrap2(&store, |_: i32, _: i64| {});
|
||||
Func::wrap2(&store, |_: f32, _: f64| {});
|
||||
Func::wrap0(&store, || -> i32 { 0 });
|
||||
Func::wrap0(&store, || -> i64 { 0 });
|
||||
Func::wrap0(&store, || -> f32 { 0.0 });
|
||||
Func::wrap0(&store, || -> f64 { 0.0 });
|
||||
|
||||
Func::wrap0(&store, || -> Result<(), Trap> { loop {} });
|
||||
Func::wrap0(&store, || -> Result<i32, Trap> { loop {} });
|
||||
Func::wrap0(&store, || -> Result<i64, Trap> { loop {} });
|
||||
Func::wrap0(&store, || -> Result<f32, Trap> { loop {} });
|
||||
Func::wrap0(&store, || -> Result<f64, Trap> { loop {} });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dtor_runs() {
|
||||
static HITS: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
struct A;
|
||||
|
||||
impl Drop for A {
|
||||
fn drop(&mut self) {
|
||||
HITS.fetch_add(1, SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
let store = Store::default();
|
||||
let a = A;
|
||||
assert_eq!(HITS.load(SeqCst), 0);
|
||||
Func::wrap0(&store, move || {
|
||||
drop(&a);
|
||||
});
|
||||
assert_eq!(HITS.load(SeqCst), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dtor_delayed() -> Result<()> {
|
||||
static HITS: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
struct A;
|
||||
|
||||
impl Drop for A {
|
||||
fn drop(&mut self) {
|
||||
HITS.fetch_add(1, SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
let store = Store::default();
|
||||
let a = A;
|
||||
let func = Func::wrap0(&store, move || drop(&a));
|
||||
|
||||
assert_eq!(HITS.load(SeqCst), 0);
|
||||
let wasm = wat::parse_str(r#"(import "" "" (func))"#)?;
|
||||
let module = Module::new(&store, &wasm)?;
|
||||
let instance = Instance::new(&module, &[func.into()])?;
|
||||
assert_eq!(HITS.load(SeqCst), 0);
|
||||
drop(instance);
|
||||
assert_eq!(HITS.load(SeqCst), 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signatures_match() {
|
||||
let store = Store::default();
|
||||
|
||||
let f = Func::wrap0(&store, || {});
|
||||
assert_eq!(f.ty().params(), &[]);
|
||||
assert_eq!(f.ty().results(), &[]);
|
||||
|
||||
let f = Func::wrap0(&store, || -> i32 { loop {} });
|
||||
assert_eq!(f.ty().params(), &[]);
|
||||
assert_eq!(f.ty().results(), &[ValType::I32]);
|
||||
|
||||
let f = Func::wrap0(&store, || -> i64 { loop {} });
|
||||
assert_eq!(f.ty().params(), &[]);
|
||||
assert_eq!(f.ty().results(), &[ValType::I64]);
|
||||
|
||||
let f = Func::wrap0(&store, || -> f32 { loop {} });
|
||||
assert_eq!(f.ty().params(), &[]);
|
||||
assert_eq!(f.ty().results(), &[ValType::F32]);
|
||||
|
||||
let f = Func::wrap0(&store, || -> f64 { loop {} });
|
||||
assert_eq!(f.ty().params(), &[]);
|
||||
assert_eq!(f.ty().results(), &[ValType::F64]);
|
||||
|
||||
let f = Func::wrap5(&store, |_: f32, _: f64, _: i32, _: i64, _: i32| -> f64 {
|
||||
loop {}
|
||||
});
|
||||
assert_eq!(
|
||||
f.ty().params(),
|
||||
&[
|
||||
ValType::F32,
|
||||
ValType::F64,
|
||||
ValType::I32,
|
||||
ValType::I64,
|
||||
ValType::I32
|
||||
]
|
||||
);
|
||||
assert_eq!(f.ty().results(), &[ValType::F64]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_works() -> Result<()> {
|
||||
static HITS: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
let wasm = wat::parse_str(
|
||||
r#"
|
||||
(import "" "" (func))
|
||||
(import "" "" (func (param i32) (result i32)))
|
||||
(import "" "" (func (param i32) (param i64)))
|
||||
(import "" "" (func (param i32 i64 i32 f32 f64)))
|
||||
|
||||
(func $foo
|
||||
call 0
|
||||
i32.const 0
|
||||
call 1
|
||||
i32.const 1
|
||||
i32.add
|
||||
i64.const 3
|
||||
call 2
|
||||
|
||||
i32.const 100
|
||||
i64.const 200
|
||||
i32.const 300
|
||||
f32.const 400
|
||||
f64.const 500
|
||||
call 3
|
||||
)
|
||||
(start $foo)
|
||||
"#,
|
||||
)?;
|
||||
let store = Store::default();
|
||||
let module = Module::new(&store, &wasm)?;
|
||||
Instance::new(
|
||||
&module,
|
||||
&[
|
||||
Func::wrap0(&store, || {
|
||||
assert_eq!(HITS.fetch_add(1, SeqCst), 0);
|
||||
})
|
||||
.into(),
|
||||
Func::wrap1(&store, |x: i32| -> i32 {
|
||||
assert_eq!(x, 0);
|
||||
assert_eq!(HITS.fetch_add(1, SeqCst), 1);
|
||||
1
|
||||
})
|
||||
.into(),
|
||||
Func::wrap2(&store, |x: i32, y: i64| {
|
||||
assert_eq!(x, 2);
|
||||
assert_eq!(y, 3);
|
||||
assert_eq!(HITS.fetch_add(1, SeqCst), 2);
|
||||
})
|
||||
.into(),
|
||||
Func::wrap5(&store, |a: i32, b: i64, c: i32, d: f32, e: f64| {
|
||||
assert_eq!(a, 100);
|
||||
assert_eq!(b, 200);
|
||||
assert_eq!(c, 300);
|
||||
assert_eq!(d, 400.0);
|
||||
assert_eq!(e, 500.0);
|
||||
assert_eq!(HITS.fetch_add(1, SeqCst), 3);
|
||||
})
|
||||
.into(),
|
||||
],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trap_smoke() {
|
||||
let store = Store::default();
|
||||
let f = Func::wrap0(&store, || -> Result<(), Trap> { Err(Trap::new("test")) });
|
||||
let err = f.call(&[]).unwrap_err();
|
||||
assert_eq!(err.message(), "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trap_import() -> Result<()> {
|
||||
let wasm = wat::parse_str(
|
||||
r#"
|
||||
(import "" "" (func))
|
||||
(start 0)
|
||||
"#,
|
||||
)?;
|
||||
let store = Store::default();
|
||||
let module = Module::new(&store, &wasm)?;
|
||||
let trap = Instance::new(
|
||||
&module,
|
||||
&[Func::wrap0(&store, || -> Result<(), Trap> { Err(Trap::new("foo")) }).into()],
|
||||
)
|
||||
.err()
|
||||
.unwrap()
|
||||
.downcast::<Trap>()?;
|
||||
assert_eq!(trap.message(), "foo");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_from_wrapper() {
|
||||
let store = Store::default();
|
||||
let f = Func::wrap0(&store, || {});
|
||||
assert!(f.get0::<()>().is_ok());
|
||||
assert!(f.get0::<i32>().is_err());
|
||||
assert!(f.get1::<(), ()>().is_ok());
|
||||
assert!(f.get1::<i32, ()>().is_err());
|
||||
assert!(f.get1::<i32, i32>().is_err());
|
||||
assert!(f.get2::<(), (), ()>().is_ok());
|
||||
assert!(f.get2::<i32, i32, ()>().is_err());
|
||||
assert!(f.get2::<i32, i32, i32>().is_err());
|
||||
|
||||
let f = Func::wrap0(&store, || -> i32 { loop {} });
|
||||
assert!(f.get0::<i32>().is_ok());
|
||||
let f = Func::wrap0(&store, || -> f32 { loop {} });
|
||||
assert!(f.get0::<f32>().is_ok());
|
||||
let f = Func::wrap0(&store, || -> f64 { loop {} });
|
||||
assert!(f.get0::<f64>().is_ok());
|
||||
|
||||
let f = Func::wrap1(&store, |_: i32| {});
|
||||
assert!(f.get1::<i32, ()>().is_ok());
|
||||
assert!(f.get1::<i64, ()>().is_err());
|
||||
assert!(f.get1::<f32, ()>().is_err());
|
||||
assert!(f.get1::<f64, ()>().is_err());
|
||||
let f = Func::wrap1(&store, |_: i64| {});
|
||||
assert!(f.get1::<i64, ()>().is_ok());
|
||||
let f = Func::wrap1(&store, |_: f32| {});
|
||||
assert!(f.get1::<f32, ()>().is_ok());
|
||||
let f = Func::wrap1(&store, |_: f64| {});
|
||||
assert!(f.get1::<f64, ()>().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_from_signature() {
|
||||
struct Foo;
|
||||
impl Callable for Foo {
|
||||
fn call(&self, _params: &[Val], _results: &mut [Val]) -> Result<(), Trap> {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
let store = Store::default();
|
||||
let ty = FuncType::new(Box::new([]), Box::new([]));
|
||||
let f = Func::new(&store, ty, Rc::new(Foo));
|
||||
assert!(f.get0::<()>().is_ok());
|
||||
assert!(f.get0::<i32>().is_err());
|
||||
assert!(f.get1::<i32, ()>().is_err());
|
||||
|
||||
let ty = FuncType::new(Box::new([ValType::I32]), Box::new([ValType::F64]));
|
||||
let f = Func::new(&store, ty, Rc::new(Foo));
|
||||
assert!(f.get0::<()>().is_err());
|
||||
assert!(f.get0::<i32>().is_err());
|
||||
assert!(f.get1::<i32, ()>().is_err());
|
||||
assert!(f.get1::<i32, f64>().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_from_module() -> anyhow::Result<()> {
|
||||
let store = Store::default();
|
||||
let module = Module::new(
|
||||
&store,
|
||||
r#"
|
||||
(module
|
||||
(func (export "f0"))
|
||||
(func (export "f1") (param i32))
|
||||
(func (export "f2") (result i32)
|
||||
i32.const 0)
|
||||
)
|
||||
|
||||
"#,
|
||||
)?;
|
||||
let instance = Instance::new(&module, &[])?;
|
||||
let f0 = instance.get_export("f0").unwrap().func().unwrap();
|
||||
assert!(f0.get0::<()>().is_ok());
|
||||
assert!(f0.get0::<i32>().is_err());
|
||||
let f1 = instance.get_export("f1").unwrap().func().unwrap();
|
||||
assert!(f1.get0::<()>().is_err());
|
||||
assert!(f1.get1::<i32, ()>().is_ok());
|
||||
assert!(f1.get1::<i32, f32>().is_err());
|
||||
let f2 = instance.get_export("f2").unwrap().func().unwrap();
|
||||
assert!(f2.get0::<()>().is_err());
|
||||
assert!(f2.get0::<i32>().is_ok());
|
||||
assert!(f2.get1::<i32, ()>().is_err());
|
||||
assert!(f2.get1::<i32, f32>().is_err());
|
||||
Ok(())
|
||||
}
|
||||
93
crates/api/tests/globals.rs
Normal file
93
crates/api/tests/globals.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
use wasmtime::*;
|
||||
|
||||
#[test]
|
||||
fn smoke() -> anyhow::Result<()> {
|
||||
let store = Store::default();
|
||||
let g = Global::new(
|
||||
&store,
|
||||
GlobalType::new(ValType::I32, Mutability::Const),
|
||||
0.into(),
|
||||
)?;
|
||||
assert_eq!(g.get().i32(), Some(0));
|
||||
assert!(g.set(0.into()).is_err());
|
||||
|
||||
let g = Global::new(
|
||||
&store,
|
||||
GlobalType::new(ValType::I32, Mutability::Const),
|
||||
1i32.into(),
|
||||
)?;
|
||||
assert_eq!(g.get().i32(), Some(1));
|
||||
|
||||
let g = Global::new(
|
||||
&store,
|
||||
GlobalType::new(ValType::I64, Mutability::Const),
|
||||
2i64.into(),
|
||||
)?;
|
||||
assert_eq!(g.get().i64(), Some(2));
|
||||
|
||||
let g = Global::new(
|
||||
&store,
|
||||
GlobalType::new(ValType::F32, Mutability::Const),
|
||||
3.0f32.into(),
|
||||
)?;
|
||||
assert_eq!(g.get().f32(), Some(3.0));
|
||||
|
||||
let g = Global::new(
|
||||
&store,
|
||||
GlobalType::new(ValType::F64, Mutability::Const),
|
||||
4.0f64.into(),
|
||||
)?;
|
||||
assert_eq!(g.get().f64(), Some(4.0));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mutability() -> anyhow::Result<()> {
|
||||
let store = Store::default();
|
||||
let g = Global::new(
|
||||
&store,
|
||||
GlobalType::new(ValType::I32, Mutability::Var),
|
||||
0.into(),
|
||||
)?;
|
||||
assert_eq!(g.get().i32(), Some(0));
|
||||
g.set(1.into())?;
|
||||
assert_eq!(g.get().i32(), Some(1));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Make sure that a global is still usable after its original instance is
|
||||
// dropped. This is a bit of a weird test and really only fails depending on the
|
||||
// implementation, but for now should hopefully be resilient enough to catch at
|
||||
// least some cases of heap corruption.
|
||||
#[test]
|
||||
fn use_after_drop() -> anyhow::Result<()> {
|
||||
let store = Store::default();
|
||||
let module = Module::new(
|
||||
&store,
|
||||
r#"
|
||||
(module
|
||||
(global (export "foo") (mut i32) (i32.const 100)))
|
||||
"#,
|
||||
)?;
|
||||
let instance = Instance::new(&module, &[])?;
|
||||
let g = instance.exports()[0].global().unwrap().clone();
|
||||
assert_eq!(g.get().i32(), Some(100));
|
||||
g.set(101.into())?;
|
||||
drop(instance);
|
||||
assert_eq!(g.get().i32(), Some(101));
|
||||
Instance::new(&module, &[])?;
|
||||
assert_eq!(g.get().i32(), Some(101));
|
||||
drop(module);
|
||||
assert_eq!(g.get().i32(), Some(101));
|
||||
drop(store);
|
||||
assert_eq!(g.get().i32(), Some(101));
|
||||
|
||||
// spray some heap values
|
||||
let mut x = Vec::new();
|
||||
for _ in 0..100 {
|
||||
x.push("xy".to_string());
|
||||
}
|
||||
drop(x);
|
||||
assert_eq!(g.get().i32(), Some(101));
|
||||
Ok(())
|
||||
}
|
||||
100
crates/api/tests/host-segfault.rs
Normal file
100
crates/api/tests/host-segfault.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
// To handle out-of-bounds reads and writes we use segfaults right now. We only
|
||||
// want to catch a subset of segfaults, however, rather than all segfaults
|
||||
// happening everywhere. The purpose of this test is to ensure that we *don't*
|
||||
// catch segfaults if it happens in a random place in the code, but we instead
|
||||
// bail out of our segfault handler early.
|
||||
//
|
||||
// This is sort of hard to test for but the general idea here is that we confirm
|
||||
// that execution made it to our `segfault` function by printing something, and
|
||||
// then we also make sure that stderr is empty to confirm that no weird panics
|
||||
// happened or anything like that.
|
||||
|
||||
use std::env;
|
||||
use std::process::{Command, ExitStatus};
|
||||
use wasmtime::*;
|
||||
|
||||
const VAR_NAME: &str = "__TEST_TO_RUN";
|
||||
const CONFIRM: &str = "well at least we ran up to the segfault\n";
|
||||
|
||||
fn segfault() -> ! {
|
||||
unsafe {
|
||||
print!("{}", CONFIRM);
|
||||
*(0x4 as *mut i32) = 3;
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let tests: &[(&str, fn())] = &[
|
||||
("normal segfault", || segfault()),
|
||||
("make instance then segfault", || {
|
||||
let store = Store::default();
|
||||
let module = Module::new(&store, "(module)").unwrap();
|
||||
let _instance = Instance::new(&module, &[]).unwrap();
|
||||
segfault();
|
||||
}),
|
||||
];
|
||||
match env::var(VAR_NAME) {
|
||||
Ok(s) => {
|
||||
let test = tests
|
||||
.iter()
|
||||
.find(|p| p.0 == s)
|
||||
.expect("failed to find test")
|
||||
.1;
|
||||
test();
|
||||
}
|
||||
Err(_) => {
|
||||
for (name, _test) in tests {
|
||||
runtest(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn runtest(name: &str) {
|
||||
let me = env::current_exe().unwrap();
|
||||
let mut cmd = Command::new(me);
|
||||
cmd.env(VAR_NAME, name);
|
||||
let output = cmd.output().expect("failed to spawn subprocess");
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let mut desc = format!("got status: {}", output.status);
|
||||
if !stdout.trim().is_empty() {
|
||||
desc.push_str("\nstdout: ----\n");
|
||||
desc.push_str(" ");
|
||||
desc.push_str(&stdout.replace("\n", "\n "));
|
||||
}
|
||||
if !stderr.trim().is_empty() {
|
||||
desc.push_str("\nstderr: ----\n");
|
||||
desc.push_str(" ");
|
||||
desc.push_str(&stderr.replace("\n", "\n "));
|
||||
}
|
||||
if is_segfault(&output.status) {
|
||||
assert!(
|
||||
stdout.ends_with(CONFIRM) && stderr.is_empty(),
|
||||
"failed to find confirmation in test `{}`\n{}",
|
||||
name,
|
||||
desc
|
||||
);
|
||||
} else {
|
||||
panic!("\n\nexpected a segfault on `{}`\n{}\n\n", name, desc);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn is_segfault(status: &ExitStatus) -> bool {
|
||||
use std::os::unix::prelude::*;
|
||||
|
||||
match status.signal() {
|
||||
Some(libc::SIGSEGV) | Some(libc::SIGBUS) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_segfault(status: &ExitStatus) -> bool {
|
||||
match status.code().map(|s| s as u32) {
|
||||
Some(0xc0000005) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
67
crates/api/tests/import-indexes.rs
Normal file
67
crates/api/tests/import-indexes.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
use std::rc::Rc;
|
||||
use wasmtime::*;
|
||||
|
||||
#[test]
|
||||
fn same_import_names_still_distinct() -> anyhow::Result<()> {
|
||||
const WAT: &str = r#"
|
||||
(module
|
||||
(import "" "" (func $a (result i32)))
|
||||
(import "" "" (func $b (result f32)))
|
||||
(func (export "foo") (result i32)
|
||||
call $a
|
||||
call $b
|
||||
i32.trunc_f32_u
|
||||
i32.add)
|
||||
)
|
||||
"#;
|
||||
|
||||
struct Ret1;
|
||||
|
||||
impl Callable for Ret1 {
|
||||
fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), Trap> {
|
||||
assert!(params.is_empty());
|
||||
assert_eq!(results.len(), 1);
|
||||
results[0] = 1i32.into();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct Ret2;
|
||||
|
||||
impl Callable for Ret2 {
|
||||
fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), Trap> {
|
||||
assert!(params.is_empty());
|
||||
assert_eq!(results.len(), 1);
|
||||
results[0] = 2.0f32.into();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let store = Store::default();
|
||||
let module = Module::new(&store, WAT)?;
|
||||
|
||||
let imports = [
|
||||
Func::new(
|
||||
&store,
|
||||
FuncType::new(Box::new([]), Box::new([ValType::I32])),
|
||||
Rc::new(Ret1),
|
||||
)
|
||||
.into(),
|
||||
Func::new(
|
||||
&store,
|
||||
FuncType::new(Box::new([]), Box::new([ValType::F32])),
|
||||
Rc::new(Ret2),
|
||||
)
|
||||
.into(),
|
||||
];
|
||||
let instance = Instance::new(&module, &imports)?;
|
||||
|
||||
let func = instance.get_export("foo").unwrap().func().unwrap();
|
||||
let results = func.call(&[])?;
|
||||
assert_eq!(results.len(), 1);
|
||||
match results[0] {
|
||||
Val::I32(n) => assert_eq!(n, 3),
|
||||
_ => panic!("unexpected type of return"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
116
crates/api/tests/import_calling_export.rs
Normal file
116
crates/api/tests/import_calling_export.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use wasmtime::*;
|
||||
|
||||
#[test]
|
||||
fn test_import_calling_export() {
|
||||
const WAT: &str = r#"
|
||||
(module
|
||||
(type $t0 (func))
|
||||
(import "" "imp" (func $.imp (type $t0)))
|
||||
(func $run call $.imp)
|
||||
(func $other)
|
||||
(export "run" (func $run))
|
||||
(export "other" (func $other))
|
||||
)
|
||||
"#;
|
||||
|
||||
struct Callback {
|
||||
pub other: RefCell<Option<Func>>,
|
||||
}
|
||||
|
||||
impl Callable for Callback {
|
||||
fn call(&self, _params: &[Val], _results: &mut [Val]) -> Result<(), Trap> {
|
||||
self.other
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.expect("expected a function ref")
|
||||
.call(&[])
|
||||
.expect("expected function not to trap");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let store = Store::default();
|
||||
let module = Module::new(&store, WAT).expect("failed to create module");
|
||||
|
||||
let callback = Rc::new(Callback {
|
||||
other: RefCell::new(None),
|
||||
});
|
||||
|
||||
let callback_func = Func::new(
|
||||
&store,
|
||||
FuncType::new(Box::new([]), Box::new([])),
|
||||
callback.clone(),
|
||||
);
|
||||
|
||||
let imports = vec![callback_func.into()];
|
||||
let instance =
|
||||
Instance::new(&module, imports.as_slice()).expect("failed to instantiate module");
|
||||
|
||||
let exports = instance.exports();
|
||||
assert!(!exports.is_empty());
|
||||
|
||||
let run_func = exports[0]
|
||||
.func()
|
||||
.expect("expected a run func in the module");
|
||||
|
||||
*callback.other.borrow_mut() = Some(
|
||||
exports[1]
|
||||
.func()
|
||||
.expect("expected an other func in the module")
|
||||
.clone(),
|
||||
);
|
||||
|
||||
run_func.call(&[]).expect("expected function not to trap");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_returns_incorrect_type() {
|
||||
const WAT: &str = r#"
|
||||
(module
|
||||
(import "env" "evil" (func $evil (result i32)))
|
||||
(func (export "run") (result i32)
|
||||
(call $evil)
|
||||
)
|
||||
)
|
||||
"#;
|
||||
|
||||
struct EvilCallback;
|
||||
|
||||
impl Callable for EvilCallback {
|
||||
fn call(&self, _params: &[Val], results: &mut [Val]) -> Result<(), Trap> {
|
||||
// Evil! Returns I64 here instead of promised in the signature I32.
|
||||
results[0] = Val::I64(228);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let store = Store::default();
|
||||
let module = Module::new(&store, WAT).expect("failed to create module");
|
||||
|
||||
let callback = Rc::new(EvilCallback);
|
||||
|
||||
let callback_func = Func::new(
|
||||
&store,
|
||||
FuncType::new(Box::new([]), Box::new([ValType::I32])),
|
||||
callback.clone(),
|
||||
);
|
||||
|
||||
let imports = vec![callback_func.into()];
|
||||
let instance =
|
||||
Instance::new(&module, imports.as_slice()).expect("failed to instantiate module");
|
||||
|
||||
let exports = instance.exports();
|
||||
assert!(!exports.is_empty());
|
||||
|
||||
let run_func = exports[0]
|
||||
.func()
|
||||
.expect("expected a run func in the module");
|
||||
|
||||
let trap = run_func.call(&[]).expect_err("the execution should fail");
|
||||
assert_eq!(
|
||||
trap.message(),
|
||||
"`Callable` attempted to return an incompatible value"
|
||||
);
|
||||
}
|
||||
32
crates/api/tests/invoke_func_via_table.rs
Normal file
32
crates/api/tests/invoke_func_via_table.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
use wasmtime::*;
|
||||
|
||||
#[test]
|
||||
fn test_invoke_func_via_table() -> Result<()> {
|
||||
let store = Store::default();
|
||||
|
||||
let wat = r#"
|
||||
(module
|
||||
(func $f (result i64) (i64.const 42))
|
||||
|
||||
(table (export "table") 1 1 anyfunc)
|
||||
(elem (i32.const 0) $f)
|
||||
)
|
||||
"#;
|
||||
let module = Module::new(&store, wat).context("> Error compiling module!")?;
|
||||
let instance = Instance::new(&module, &[]).context("> Error instantiating module!")?;
|
||||
|
||||
let f = instance
|
||||
.get_export("table")
|
||||
.unwrap()
|
||||
.table()
|
||||
.unwrap()
|
||||
.get(0)
|
||||
.unwrap()
|
||||
.funcref()
|
||||
.unwrap()
|
||||
.clone();
|
||||
let result = f.call(&[]).unwrap();
|
||||
assert_eq!(result[0].unwrap_i64(), 42);
|
||||
Ok(())
|
||||
}
|
||||
34
crates/api/tests/name.rs
Normal file
34
crates/api/tests/name.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use wasmtime::*;
|
||||
|
||||
#[test]
|
||||
fn test_module_no_name() -> anyhow::Result<()> {
|
||||
let store = Store::default();
|
||||
let wat = r#"
|
||||
(module
|
||||
(func (export "run") (nop))
|
||||
)
|
||||
"#;
|
||||
|
||||
let module = Module::new(&store, wat)?;
|
||||
assert_eq!(module.name(), None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_module_name() -> anyhow::Result<()> {
|
||||
let store = Store::default();
|
||||
let wat = r#"
|
||||
(module $from_name_section
|
||||
(func (export "run") (nop))
|
||||
)
|
||||
"#;
|
||||
|
||||
let module = Module::new(&store, wat)?;
|
||||
assert_eq!(module.name(), Some("from_name_section"));
|
||||
|
||||
let module = Module::new_with_name(&store, wat, "override")?;
|
||||
assert_eq!(module.name(), Some("override"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
404
crates/api/tests/traps.rs
Normal file
404
crates/api/tests/traps.rs
Normal file
@@ -0,0 +1,404 @@
|
||||
use anyhow::Result;
|
||||
use std::panic::{self, AssertUnwindSafe};
|
||||
use std::rc::Rc;
|
||||
use wasmtime::*;
|
||||
|
||||
#[test]
|
||||
fn test_trap_return() -> Result<()> {
|
||||
struct HelloCallback;
|
||||
|
||||
impl Callable for HelloCallback {
|
||||
fn call(&self, _params: &[Val], _results: &mut [Val]) -> Result<(), Trap> {
|
||||
Err(Trap::new("test 123"))
|
||||
}
|
||||
}
|
||||
|
||||
let store = Store::default();
|
||||
let wat = r#"
|
||||
(module
|
||||
(func $hello (import "" "hello"))
|
||||
(func (export "run") (call $hello))
|
||||
)
|
||||
"#;
|
||||
|
||||
let module = Module::new(&store, wat)?;
|
||||
let hello_type = FuncType::new(Box::new([]), Box::new([]));
|
||||
let hello_func = Func::new(&store, hello_type, Rc::new(HelloCallback));
|
||||
|
||||
let instance = Instance::new(&module, &[hello_func.into()])?;
|
||||
let run_func = instance.exports()[0]
|
||||
.func()
|
||||
.expect("expected function export");
|
||||
|
||||
let e = run_func.call(&[]).err().expect("error calling function");
|
||||
|
||||
assert_eq!(e.message(), "test 123");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trap_trace() -> Result<()> {
|
||||
let store = Store::default();
|
||||
let wat = r#"
|
||||
(module $hello_mod
|
||||
(func (export "run") (call $hello))
|
||||
(func $hello (unreachable))
|
||||
)
|
||||
"#;
|
||||
|
||||
let module = Module::new(&store, wat)?;
|
||||
let instance = Instance::new(&module, &[])?;
|
||||
let run_func = instance.exports()[0]
|
||||
.func()
|
||||
.expect("expected function export");
|
||||
|
||||
let e = run_func.call(&[]).err().expect("error calling function");
|
||||
|
||||
let trace = e.trace();
|
||||
assert_eq!(trace.len(), 2);
|
||||
assert_eq!(trace[0].module_name().unwrap(), "hello_mod");
|
||||
assert_eq!(trace[0].func_index(), 1);
|
||||
assert_eq!(trace[0].func_name(), Some("hello"));
|
||||
assert_eq!(trace[1].module_name().unwrap(), "hello_mod");
|
||||
assert_eq!(trace[1].func_index(), 0);
|
||||
assert_eq!(trace[1].func_name(), None);
|
||||
assert!(
|
||||
e.message().contains("unreachable"),
|
||||
"wrong message: {}",
|
||||
e.message()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trap_trace_cb() -> Result<()> {
|
||||
struct ThrowCallback;
|
||||
|
||||
impl Callable for ThrowCallback {
|
||||
fn call(&self, _params: &[Val], _results: &mut [Val]) -> Result<(), Trap> {
|
||||
Err(Trap::new("cb throw"))
|
||||
}
|
||||
}
|
||||
|
||||
let store = Store::default();
|
||||
let wat = r#"
|
||||
(module $hello_mod
|
||||
(import "" "throw" (func $throw))
|
||||
(func (export "run") (call $hello))
|
||||
(func $hello (call $throw))
|
||||
)
|
||||
"#;
|
||||
|
||||
let fn_type = FuncType::new(Box::new([]), Box::new([]));
|
||||
let fn_func = Func::new(&store, fn_type, Rc::new(ThrowCallback));
|
||||
|
||||
let module = Module::new(&store, wat)?;
|
||||
let instance = Instance::new(&module, &[fn_func.into()])?;
|
||||
let run_func = instance.exports()[0]
|
||||
.func()
|
||||
.expect("expected function export");
|
||||
|
||||
let e = run_func.call(&[]).err().expect("error calling function");
|
||||
|
||||
let trace = e.trace();
|
||||
assert_eq!(trace.len(), 2);
|
||||
assert_eq!(trace[0].module_name().unwrap(), "hello_mod");
|
||||
assert_eq!(trace[0].func_index(), 2);
|
||||
assert_eq!(trace[1].module_name().unwrap(), "hello_mod");
|
||||
assert_eq!(trace[1].func_index(), 1);
|
||||
assert_eq!(e.message(), "cb throw");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trap_stack_overflow() -> Result<()> {
|
||||
let store = Store::default();
|
||||
let wat = r#"
|
||||
(module $rec_mod
|
||||
(func $run (export "run") (call $run))
|
||||
)
|
||||
"#;
|
||||
|
||||
let module = Module::new(&store, wat)?;
|
||||
let instance = Instance::new(&module, &[])?;
|
||||
let run_func = instance.exports()[0]
|
||||
.func()
|
||||
.expect("expected function export");
|
||||
|
||||
let e = run_func.call(&[]).err().expect("error calling function");
|
||||
|
||||
let trace = e.trace();
|
||||
assert!(trace.len() >= 32);
|
||||
for i in 0..trace.len() {
|
||||
assert_eq!(trace[i].module_name().unwrap(), "rec_mod");
|
||||
assert_eq!(trace[i].func_index(), 0);
|
||||
assert_eq!(trace[i].func_name(), Some("run"));
|
||||
}
|
||||
assert!(e.message().contains("call stack exhausted"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trap_display_pretty() -> Result<()> {
|
||||
let store = Store::default();
|
||||
let wat = r#"
|
||||
(module $m
|
||||
(func $die unreachable)
|
||||
(func call $die)
|
||||
(func $foo call 1)
|
||||
(func (export "bar") call $foo)
|
||||
)
|
||||
"#;
|
||||
|
||||
let module = Module::new(&store, wat)?;
|
||||
let instance = Instance::new(&module, &[])?;
|
||||
let run_func = instance.exports()[0]
|
||||
.func()
|
||||
.expect("expected function export");
|
||||
|
||||
let e = run_func.call(&[]).err().expect("error calling function");
|
||||
assert_eq!(
|
||||
e.to_string(),
|
||||
"\
|
||||
wasm trap: unreachable, source location: @0023
|
||||
wasm backtrace:
|
||||
0: m!die
|
||||
1: m!<wasm function 1>
|
||||
2: m!foo
|
||||
3: m!<wasm function 3>
|
||||
"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trap_display_multi_module() -> Result<()> {
|
||||
let store = Store::default();
|
||||
let wat = r#"
|
||||
(module $a
|
||||
(func $die unreachable)
|
||||
(func call $die)
|
||||
(func $foo call 1)
|
||||
(func (export "bar") call $foo)
|
||||
)
|
||||
"#;
|
||||
|
||||
let module = Module::new(&store, wat)?;
|
||||
let instance = Instance::new(&module, &[])?;
|
||||
let bar = instance.exports()[0].clone();
|
||||
|
||||
let wat = r#"
|
||||
(module $b
|
||||
(import "" "" (func $bar))
|
||||
(func $middle call $bar)
|
||||
(func (export "bar2") call $middle)
|
||||
)
|
||||
"#;
|
||||
let module = Module::new(&store, wat)?;
|
||||
let instance = Instance::new(&module, &[bar])?;
|
||||
let bar2 = instance.exports()[0]
|
||||
.func()
|
||||
.expect("expected function export");
|
||||
|
||||
let e = bar2.call(&[]).err().expect("error calling function");
|
||||
assert_eq!(
|
||||
e.to_string(),
|
||||
"\
|
||||
wasm trap: unreachable, source location: @0023
|
||||
wasm backtrace:
|
||||
0: a!die
|
||||
1: a!<wasm function 1>
|
||||
2: a!foo
|
||||
3: a!<wasm function 3>
|
||||
4: b!middle
|
||||
5: b!<wasm function 2>
|
||||
"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trap_start_function_import() -> Result<()> {
|
||||
struct ReturnTrap;
|
||||
|
||||
impl Callable for ReturnTrap {
|
||||
fn call(&self, _params: &[Val], _results: &mut [Val]) -> Result<(), Trap> {
|
||||
Err(Trap::new("user trap"))
|
||||
}
|
||||
}
|
||||
|
||||
let store = Store::default();
|
||||
let binary = wat::parse_str(
|
||||
r#"
|
||||
(module $a
|
||||
(import "" "" (func $foo))
|
||||
(start $foo)
|
||||
)
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let module = Module::new(&store, &binary)?;
|
||||
let sig = FuncType::new(Box::new([]), Box::new([]));
|
||||
let func = Func::new(&store, sig, Rc::new(ReturnTrap));
|
||||
let err = Instance::new(&module, &[func.into()]).err().unwrap();
|
||||
assert_eq!(err.downcast_ref::<Trap>().unwrap().message(), "user trap");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_panic_import() -> Result<()> {
|
||||
struct Panic;
|
||||
|
||||
impl Callable for Panic {
|
||||
fn call(&self, _params: &[Val], _results: &mut [Val]) -> Result<(), Trap> {
|
||||
panic!("this is a panic");
|
||||
}
|
||||
}
|
||||
|
||||
let store = Store::default();
|
||||
let binary = wat::parse_str(
|
||||
r#"
|
||||
(module $a
|
||||
(import "" "" (func $foo))
|
||||
(import "" "" (func $bar))
|
||||
(func (export "foo") call $foo)
|
||||
(func (export "bar") call $bar)
|
||||
)
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let module = Module::new(&store, &binary)?;
|
||||
let sig = FuncType::new(Box::new([]), Box::new([]));
|
||||
let func = Func::new(&store, sig, Rc::new(Panic));
|
||||
let instance = Instance::new(
|
||||
&module,
|
||||
&[
|
||||
func.into(),
|
||||
Func::wrap0(&store, || panic!("this is another panic")).into(),
|
||||
],
|
||||
)?;
|
||||
let func = instance.exports()[0].func().unwrap().clone();
|
||||
let err = panic::catch_unwind(AssertUnwindSafe(|| {
|
||||
drop(func.call(&[]));
|
||||
}))
|
||||
.unwrap_err();
|
||||
assert_eq!(err.downcast_ref::<&'static str>(), Some(&"this is a panic"));
|
||||
|
||||
let func = instance.exports()[1].func().unwrap().clone();
|
||||
let err = panic::catch_unwind(AssertUnwindSafe(|| {
|
||||
drop(func.call(&[]));
|
||||
}))
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
err.downcast_ref::<&'static str>(),
|
||||
Some(&"this is another panic")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_panic_start_function() -> Result<()> {
|
||||
struct Panic;
|
||||
|
||||
impl Callable for Panic {
|
||||
fn call(&self, _params: &[Val], _results: &mut [Val]) -> Result<(), Trap> {
|
||||
panic!("this is a panic");
|
||||
}
|
||||
}
|
||||
|
||||
let store = Store::default();
|
||||
let binary = wat::parse_str(
|
||||
r#"
|
||||
(module $a
|
||||
(import "" "" (func $foo))
|
||||
(start $foo)
|
||||
)
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let module = Module::new(&store, &binary)?;
|
||||
let sig = FuncType::new(Box::new([]), Box::new([]));
|
||||
let func = Func::new(&store, sig, Rc::new(Panic));
|
||||
let err = panic::catch_unwind(AssertUnwindSafe(|| {
|
||||
drop(Instance::new(&module, &[func.into()]));
|
||||
}))
|
||||
.unwrap_err();
|
||||
assert_eq!(err.downcast_ref::<&'static str>(), Some(&"this is a panic"));
|
||||
|
||||
let func = Func::wrap0(&store, || panic!("this is another panic"));
|
||||
let err = panic::catch_unwind(AssertUnwindSafe(|| {
|
||||
drop(Instance::new(&module, &[func.into()]));
|
||||
}))
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
err.downcast_ref::<&'static str>(),
|
||||
Some(&"this is another panic")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mismatched_arguments() -> Result<()> {
|
||||
let store = Store::default();
|
||||
let binary = wat::parse_str(
|
||||
r#"
|
||||
(module $a
|
||||
(func (export "foo") (param i32))
|
||||
)
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let module = Module::new(&store, &binary)?;
|
||||
let instance = Instance::new(&module, &[])?;
|
||||
let func = instance.exports()[0].func().unwrap().clone();
|
||||
assert_eq!(
|
||||
func.call(&[]).unwrap_err().message(),
|
||||
"expected 1 arguments, got 0"
|
||||
);
|
||||
assert_eq!(
|
||||
func.call(&[Val::F32(0)]).unwrap_err().message(),
|
||||
"argument type mismatch",
|
||||
);
|
||||
assert_eq!(
|
||||
func.call(&[Val::I32(0), Val::I32(1)])
|
||||
.unwrap_err()
|
||||
.message(),
|
||||
"expected 1 arguments, got 2"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_signature_mismatch() -> Result<()> {
|
||||
let store = Store::default();
|
||||
let binary = wat::parse_str(
|
||||
r#"
|
||||
(module $a
|
||||
(func $foo
|
||||
i32.const 0
|
||||
call_indirect)
|
||||
(func $bar (param i32))
|
||||
(start $foo)
|
||||
|
||||
(table 1 anyfunc)
|
||||
(elem (i32.const 0) 1)
|
||||
)
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let module = Module::new(&store, &binary)?;
|
||||
let err = Instance::new(&module, &[])
|
||||
.err()
|
||||
.unwrap()
|
||||
.downcast::<Trap>()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
err.message(),
|
||||
"wasm trap: indirect call type mismatch, source location: @0030"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
22
crates/c-api/Cargo.toml
Normal file
22
crates/c-api/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "wasmtime-c-api"
|
||||
version = "0.12.0"
|
||||
authors = ["The Wasmtime Project Developers"]
|
||||
description = "C API to expose the Wasmtime runtime"
|
||||
license = "Apache-2.0 WITH LLVM-exception"
|
||||
repository = "https://github.com/bytecodealliance/wasmtime"
|
||||
readme = "README.md"
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "wasmtime"
|
||||
crate-type = ["staticlib", "cdylib"]
|
||||
doc = false
|
||||
test = false
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
wasmtime = { path = "../api" }
|
||||
wasi-common = { path = "../wasi-common" }
|
||||
wasmtime-wasi = { path = "../wasi" }
|
||||
220
crates/c-api/LICENSE
Normal file
220
crates/c-api/LICENSE
Normal file
@@ -0,0 +1,220 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
--- LLVM Exceptions to the Apache 2.0 License ----
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into an Object form of such source code, you
|
||||
may redistribute such embedded portions in such Object form without complying
|
||||
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
|
||||
|
||||
In addition, if you combine or link compiled forms of this Software with
|
||||
software that is licensed under the GPLv2 ("Combined Software") and if a
|
||||
court of competent jurisdiction determines that the patent provision (Section
|
||||
3), the indemnity provision (Section 9) or other Section of the License
|
||||
conflicts with the conditions of the GPLv2, you may retroactively and
|
||||
prospectively choose to deem waived or otherwise exclude such Section(s) of
|
||||
the License, but only in their entirety and only with respect to the Combined
|
||||
Software.
|
||||
|
||||
3
crates/c-api/README.md
Normal file
3
crates/c-api/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Implementation of wasm-c-api in Rust
|
||||
|
||||
https://github.com/WebAssembly/wasm-c-api
|
||||
171
crates/c-api/examples/Makefile
Normal file
171
crates/c-api/examples/Makefile
Normal file
@@ -0,0 +1,171 @@
|
||||
###############################################################################
|
||||
# Configuration
|
||||
|
||||
# Inherited from wasm-c-api/Makefile to just run C examples
|
||||
|
||||
WASM_FLAGS = -DWASM_API_DEBUG # -DWASM_API_DEBUG_LOG
|
||||
C_FLAGS = ${WASM_FLAGS} -Wall -Werror -ggdb -O -fsanitize=address
|
||||
CC_FLAGS = -std=c++11 ${C_FLAGS}
|
||||
LD_FLAGS = -fsanitize-memory-track-origins -fsanitize-memory-use-after-dtor
|
||||
|
||||
C_COMP = clang
|
||||
|
||||
WASMTIME_API_MODE = debug
|
||||
|
||||
|
||||
# Base directories
|
||||
WASMTIME_API_DIR = ..
|
||||
WASM_DIR = wasm-c-api
|
||||
EXAMPLE_DIR = ${WASM_DIR}/example
|
||||
OUT_DIR = ${WASM_DIR}/out
|
||||
|
||||
# Example config
|
||||
EXAMPLE_OUT = ${OUT_DIR}/example
|
||||
EXAMPLES = \
|
||||
hello \
|
||||
callback \
|
||||
trap \
|
||||
start \
|
||||
reflect \
|
||||
global \
|
||||
table \
|
||||
memory \
|
||||
hostref \
|
||||
finalize \
|
||||
serialize \
|
||||
threads \
|
||||
# multi \
|
||||
|
||||
# Wasm config
|
||||
WASM_INCLUDE = ${WASM_DIR}/include
|
||||
WASM_SRC = ${WASM_DIR}/src
|
||||
WASM_OUT = ${OUT_DIR}
|
||||
WASM_C_LIBS = wasm-bin wasm-rust-api
|
||||
WASM_CC_LIBS = $(error unsupported C++)
|
||||
|
||||
|
||||
# Compiler config
|
||||
ifeq (${WASMTIME_API_MODE},release)
|
||||
CARGO_BUILD_FLAGS = --release
|
||||
else
|
||||
CARGO_BUILD_FLAGS =
|
||||
endif
|
||||
|
||||
ifeq (${C_COMP},clang)
|
||||
CC_COMP = clang++
|
||||
LD_GROUP_START =
|
||||
LD_GROUP_END =
|
||||
else ifeq (${C_COMP},gcc)
|
||||
CC_COMP = g++
|
||||
LD_GROUP_START = -Wl,--start-group
|
||||
LD_GROUP_END = -Wl,--end-group
|
||||
else
|
||||
$(error C_COMP set to unknown compiler, must be clang or gcc)
|
||||
endif
|
||||
|
||||
WASMTIME_BIN_DIR = ${WASMTIME_API_DIR}/../../target/${WASMTIME_API_MODE}
|
||||
WASMTIME_C_LIB_DIR = ${WASMTIME_BIN_DIR}
|
||||
WASMTIME_C_LIBS = wasmtime
|
||||
WASMTIME_CC_LIBS = $(error unsupported c++)
|
||||
|
||||
WASMTIME_C_BINS = ${WASMTIME_C_LIBS:%=${WASMTIME_C_LIB_DIR}/lib%.a}
|
||||
|
||||
###############################################################################
|
||||
# Examples
|
||||
#
|
||||
# To build Wasm APIs and run all examples:
|
||||
# make all
|
||||
#
|
||||
# To run only C examples:
|
||||
# make c
|
||||
#
|
||||
# To run only C++ examples:
|
||||
# make cc
|
||||
#
|
||||
# To run individual C example (e.g. hello):
|
||||
# make run-hello-c
|
||||
#
|
||||
# To run individual C++ example (e.g. hello):
|
||||
# make run-hello-cc
|
||||
#
|
||||
|
||||
.PHONY: all cc c
|
||||
all: cc c
|
||||
cc: ${EXAMPLES:%=run-%-cc}
|
||||
c: ${EXAMPLES:%=run-%-c}
|
||||
|
||||
# Running a C / C++ example
|
||||
run-%-c: ${EXAMPLE_OUT}/%-c ${EXAMPLE_OUT}/%.wasm
|
||||
@echo ==== C ${@:run-%-c=%} ====; \
|
||||
cd ${EXAMPLE_OUT}; ./${@:run-%=%}
|
||||
@echo ==== Done ====
|
||||
|
||||
run-%-cc: ${EXAMPLE_OUT}/%-cc ${EXAMPLE_OUT}/%.wasm
|
||||
@echo ==== C++ ${@:run-%-cc=%} ====; \
|
||||
cd ${EXAMPLE_OUT}; ./${@:run-%=%}
|
||||
@echo ==== Done ====
|
||||
|
||||
# Compiling C / C++ example
|
||||
${EXAMPLE_OUT}/%-c.o: ${EXAMPLE_DIR}/%.c ${WASM_INCLUDE}/wasm.h
|
||||
mkdir -p ${EXAMPLE_OUT}
|
||||
${C_COMP} -c ${C_FLAGS} -I. -I${WASM_INCLUDE} $< -o $@
|
||||
|
||||
${EXAMPLE_OUT}/%-cc.o: ${EXAMPLE_DIR}/%.cc ${WASM_INCLUDE}/wasm.hh
|
||||
mkdir -p ${EXAMPLE_OUT}
|
||||
${CC_COMP} -c ${CC_FLAGS} -I. -I${WASM_INCLUDE} $< -o $@
|
||||
|
||||
# Linking C / C++ example
|
||||
.PRECIOUS: ${EXAMPLES:%=${EXAMPLE_OUT}/%-c}
|
||||
${EXAMPLE_OUT}/%-c: ${EXAMPLE_OUT}/%-c.o ${WASMTIME_C_BINS}
|
||||
${CC_COMP} ${CC_FLAGS} ${LD_FLAGS} $< -o $@ \
|
||||
${LD_GROUP_START} \
|
||||
${WASMTIME_C_BINS} \
|
||||
${LD_GROUP_END} \
|
||||
-ldl -pthread
|
||||
|
||||
# Installing Wasm binaries
|
||||
.PRECIOUS: ${EXAMPLES:%=${EXAMPLE_OUT}/%.wasm}
|
||||
${EXAMPLE_OUT}/%.wasm: ${EXAMPLE_DIR}/%.wasm
|
||||
cp $< $@
|
||||
|
||||
###############################################################################
|
||||
# Wasm C / C++ API
|
||||
#
|
||||
# To build both C / C++ APIs:
|
||||
# make wasm
|
||||
|
||||
.PHONY: wasm wasm-c wasm-cc
|
||||
wasm: wasm-c wasm-cc
|
||||
wasm-c: ${WASMTIME_C_BIN}
|
||||
wasm-cc: ${WASMTIME_CC_BIN}
|
||||
|
||||
${WASMTIME_C_BINS}: CARGO_RUN
|
||||
cd ${WASMTIME_API_DIR}; cargo build --lib ${CARGO_BUILD_FLAGS}
|
||||
|
||||
.PHONY: CARGO_RUN
|
||||
CARGO_RUN:
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Clean-up
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf ${OUT_DIR}
|
||||
|
||||
###############################################################################
|
||||
# Other examples
|
||||
|
||||
WASM_EXT_INCLUDE = ${WASMTIME_API_DIR}/include
|
||||
|
||||
run-config-debug-c: ${EXAMPLE_OUT}/config-debug-c ${EXAMPLE_OUT}/fib-wasm.wasm
|
||||
@echo ==== C config ====; \
|
||||
cd ${EXAMPLE_OUT}; ./config-debug-c
|
||||
@echo ==== Done ====
|
||||
|
||||
${EXAMPLE_OUT}/fib-wasm.wasm: fib-wasm.wasm
|
||||
cp $< $@
|
||||
|
||||
${EXAMPLE_OUT}/config-debug-c.o: config-debug.c ${WASM_INCLUDE}/wasm.h ${WASM_EXT_INCLUDE}/wasmtime.h
|
||||
mkdir -p ${EXAMPLE_OUT}
|
||||
${C_COMP} -c ${C_FLAGS} -I. -I${WASM_INCLUDE} -I${WASM_EXT_INCLUDE} $< -o $@
|
||||
99
crates/c-api/examples/config-debug.c
Normal file
99
crates/c-api/examples/config-debug.c
Normal file
@@ -0,0 +1,99 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <wasm.h>
|
||||
#include "wasmtime.h"
|
||||
|
||||
#define own
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
// Configuring engine to support generating of DWARF info.
|
||||
// lldb can be used to attach to the program and observe
|
||||
// original fib-wasm.c source code and variables.
|
||||
wasm_config_t* config = wasm_config_new();
|
||||
wasmtime_config_debug_info_set(config, true);
|
||||
|
||||
// Initialize.
|
||||
printf("Initializing...\n");
|
||||
wasm_engine_t* engine = wasm_engine_new_with_config(config);
|
||||
wasm_store_t* store = wasm_store_new(engine);
|
||||
|
||||
// Load binary.
|
||||
printf("Loading binary...\n");
|
||||
FILE* file = fopen("fib-wasm.wasm", "r");
|
||||
if (!file) {
|
||||
printf("> Error loading module!\n");
|
||||
return 1;
|
||||
}
|
||||
fseek(file, 0L, SEEK_END);
|
||||
size_t file_size = ftell(file);
|
||||
fseek(file, 0L, SEEK_SET);
|
||||
wasm_byte_vec_t binary;
|
||||
wasm_byte_vec_new_uninitialized(&binary, file_size);
|
||||
if (fread(binary.data, file_size, 1, file) != 1) {
|
||||
printf("> Error loading module!\n");
|
||||
return 1;
|
||||
}
|
||||
fclose(file);
|
||||
|
||||
// Compile.
|
||||
printf("Compiling module...\n");
|
||||
own wasm_module_t* module = wasm_module_new(store, &binary);
|
||||
if (!module) {
|
||||
printf("> Error compiling module!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
wasm_byte_vec_delete(&binary);
|
||||
|
||||
// Instantiate.
|
||||
printf("Instantiating module...\n");
|
||||
own wasm_instance_t* instance =
|
||||
wasm_instance_new(store, module, NULL, NULL);
|
||||
if (!instance) {
|
||||
printf("> Error instantiating module!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Extract export.
|
||||
printf("Extracting export...\n");
|
||||
own wasm_extern_vec_t exports;
|
||||
wasm_instance_exports(instance, &exports);
|
||||
if (exports.size == 0) {
|
||||
printf("> Error accessing exports!\n");
|
||||
return 1;
|
||||
}
|
||||
// Getting second export (first is memory).
|
||||
const wasm_func_t* run_func = wasm_extern_as_func(exports.data[1]);
|
||||
if (run_func == NULL) {
|
||||
printf("> Error accessing export!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
wasm_module_delete(module);
|
||||
wasm_instance_delete(instance);
|
||||
|
||||
// Call.
|
||||
printf("Calling fib...\n");
|
||||
wasm_val_t params[1] = { {.kind = WASM_I32, .of = {.i32 = 6}} };
|
||||
wasm_val_t results[1];
|
||||
if (wasm_func_call(run_func, params, results)) {
|
||||
printf("> Error calling function!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
wasm_extern_vec_delete(&exports);
|
||||
|
||||
printf("> fib(6) = %d\n", results[0].of.i32);
|
||||
|
||||
// Shut down.
|
||||
printf("Shutting down...\n");
|
||||
wasm_store_delete(store);
|
||||
wasm_engine_delete(engine);
|
||||
|
||||
// All done.
|
||||
printf("Done.\n");
|
||||
return 0;
|
||||
}
|
||||
13
crates/c-api/examples/fib-wasm.c
Normal file
13
crates/c-api/examples/fib-wasm.c
Normal file
@@ -0,0 +1,13 @@
|
||||
// Compile with:
|
||||
// clang --target=wasm32 fib-wasm.c -o fib-wasm.wasm -g \
|
||||
// -Wl,--no-entry,--export=fib -nostdlib -fdebug-prefix-map=$PWD=.
|
||||
|
||||
int fib(int n) {
|
||||
int i, t, a = 0, b = 1;
|
||||
for (i = 0; i < n; i++) {
|
||||
t = a;
|
||||
a = b;
|
||||
b += t;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
BIN
crates/c-api/examples/fib-wasm.wasm
Executable file
BIN
crates/c-api/examples/fib-wasm.wasm
Executable file
Binary file not shown.
1
crates/c-api/examples/wasm-c-api
Submodule
1
crates/c-api/examples/wasm-c-api
Submodule
Submodule crates/c-api/examples/wasm-c-api added at d9a80099d4
70
crates/c-api/include/wasi.h
Normal file
70
crates/c-api/include/wasi.h
Normal file
@@ -0,0 +1,70 @@
|
||||
// WASI C API
|
||||
|
||||
#ifndef WASI_H
|
||||
#define WASI_H
|
||||
|
||||
#include "wasm.h"
|
||||
|
||||
#ifndef WASI_API_EXTERN
|
||||
#ifdef _WIN32
|
||||
#define WASI_API_EXTERN __declspec(dllimport)
|
||||
#else
|
||||
#define WASI_API_EXTERN
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define own
|
||||
|
||||
#define WASI_DECLARE_OWN(name) \
|
||||
typedef struct wasi_##name##_t wasi_##name##_t; \
|
||||
WASI_API_EXTERN void wasi_##name##_delete(own wasi_##name##_t*);
|
||||
|
||||
// WASI config
|
||||
|
||||
WASI_DECLARE_OWN(config)
|
||||
|
||||
WASI_API_EXTERN own wasi_config_t* wasi_config_new();
|
||||
|
||||
WASI_API_EXTERN void wasi_config_set_argv(wasi_config_t* config, int argc, const char* argv[]);
|
||||
WASI_API_EXTERN void wasi_config_inherit_argv(wasi_config_t* config);
|
||||
|
||||
WASI_API_EXTERN void wasi_config_set_env(wasi_config_t* config, int envc, const char* names[], const char* values[]);
|
||||
WASI_API_EXTERN void wasi_config_inherit_env(wasi_config_t* config);
|
||||
|
||||
WASI_API_EXTERN bool wasi_config_set_stdin_file(wasi_config_t* config, const char* path);
|
||||
WASI_API_EXTERN void wasi_config_inherit_stdin(wasi_config_t* config);
|
||||
|
||||
WASI_API_EXTERN bool wasi_config_set_stdout_file(wasi_config_t* config, const char* path);
|
||||
WASI_API_EXTERN void wasi_config_inherit_stdout(wasi_config_t* config);
|
||||
|
||||
WASI_API_EXTERN bool wasi_config_set_stderr_file(wasi_config_t* config, const char* path);
|
||||
WASI_API_EXTERN void wasi_config_inherit_stderr(wasi_config_t* config);
|
||||
|
||||
WASI_API_EXTERN bool wasi_config_preopen_dir(wasi_config_t* config, const char* path, const char* guest_path);
|
||||
|
||||
// WASI instance
|
||||
|
||||
WASI_DECLARE_OWN(instance)
|
||||
|
||||
WASI_API_EXTERN own wasi_instance_t* wasi_instance_new(
|
||||
wasm_store_t* store,
|
||||
own wasi_config_t* config,
|
||||
own wasm_trap_t** trap
|
||||
);
|
||||
|
||||
WASI_API_EXTERN const wasm_extern_t* wasi_instance_bind_import(
|
||||
const wasi_instance_t* instance,
|
||||
const wasm_importtype_t* import
|
||||
);
|
||||
|
||||
#undef own
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // #ifdef WASI_H
|
||||
45
crates/c-api/include/wasmtime.h
Normal file
45
crates/c-api/include/wasmtime.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// WebAssembly C API extension for Wasmtime
|
||||
|
||||
#ifndef WASMTIME_API_H
|
||||
#define WASMTIME_API_H
|
||||
|
||||
#include <wasm.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef uint8_t wasmtime_strategy_t;
|
||||
enum wasmtime_strategy_enum { // Strategy
|
||||
WASMTIME_STRATEGY_AUTO,
|
||||
WASMTIME_STRATEGY_CRANELIFT,
|
||||
WASMTIME_STRATEGY_LIGHTBEAM,
|
||||
};
|
||||
|
||||
typedef uint8_t wasmtime_opt_level_t;
|
||||
enum wasmtime_opt_level_enum { // OptLevel
|
||||
WASMTIME_OPT_LEVEL_NONE,
|
||||
WASMTIME_OPT_LEVEL_SPEED,
|
||||
WASMTIME_OPT_LEVEL_SPEED_AND_SIZE,
|
||||
};
|
||||
|
||||
#define WASMTIME_CONFIG_PROP(name, ty) \
|
||||
WASM_API_EXTERN void wasmtime_config_##name##_set(wasm_config_t*, ty);
|
||||
|
||||
WASMTIME_CONFIG_PROP(debug_info, bool)
|
||||
WASMTIME_CONFIG_PROP(wasm_threads, bool)
|
||||
WASMTIME_CONFIG_PROP(wasm_reference_types, bool)
|
||||
WASMTIME_CONFIG_PROP(wasm_simd, bool)
|
||||
WASMTIME_CONFIG_PROP(wasm_bulk_memory, bool)
|
||||
WASMTIME_CONFIG_PROP(wasm_multi_value, bool)
|
||||
WASMTIME_CONFIG_PROP(strategy, wasmtime_strategy_t)
|
||||
WASMTIME_CONFIG_PROP(cranelift_debug_verifier, bool)
|
||||
WASMTIME_CONFIG_PROP(cranelift_opt_level, wasmtime_opt_level_t)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // WASMTIME_API_H
|
||||
88
crates/c-api/src/ext.rs
Normal file
88
crates/c-api/src/ext.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
//! This file defines the extern "C" API extension, which are specific
|
||||
//! to the wasmtime implementation.
|
||||
|
||||
use crate::wasm_config_t;
|
||||
use wasmtime::{OptLevel, Strategy};
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone)]
|
||||
pub enum wasmtime_strategy_t {
|
||||
WASMTIME_STRATEGY_AUTO,
|
||||
WASMTIME_STRATEGY_CRANELIFT,
|
||||
WASMTIME_STRATEGY_LIGHTBEAM,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone)]
|
||||
pub enum wasmtime_opt_level_t {
|
||||
WASMTIME_OPT_LEVEL_NONE,
|
||||
WASMTIME_OPT_LEVEL_SPEED,
|
||||
WASMTIME_OPT_LEVEL_SPEED_AND_SIZE,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_config_debug_info_set(c: *mut wasm_config_t, enable: bool) {
|
||||
(*c).config.debug_info(enable);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_config_wasm_threads_set(c: *mut wasm_config_t, enable: bool) {
|
||||
(*c).config.wasm_threads(enable);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_config_wasm_reference_types_set(
|
||||
c: *mut wasm_config_t,
|
||||
enable: bool,
|
||||
) {
|
||||
(*c).config.wasm_reference_types(enable);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_config_wasm_simd_set(c: *mut wasm_config_t, enable: bool) {
|
||||
(*c).config.wasm_simd(enable);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_config_wasm_bulk_memory_set(c: *mut wasm_config_t, enable: bool) {
|
||||
(*c).config.wasm_bulk_memory(enable);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_config_wasm_multi_value_set(c: *mut wasm_config_t, enable: bool) {
|
||||
(*c).config.wasm_multi_value(enable);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_config_strategy_set(
|
||||
c: *mut wasm_config_t,
|
||||
strategy: wasmtime_strategy_t,
|
||||
) {
|
||||
use wasmtime_strategy_t::*;
|
||||
drop((*c).config.strategy(match strategy {
|
||||
WASMTIME_STRATEGY_AUTO => Strategy::Auto,
|
||||
WASMTIME_STRATEGY_CRANELIFT => Strategy::Cranelift,
|
||||
WASMTIME_STRATEGY_LIGHTBEAM => Strategy::Lightbeam,
|
||||
}));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_config_cranelift_debug_verifier_set(
|
||||
c: *mut wasm_config_t,
|
||||
enable: bool,
|
||||
) {
|
||||
(*c).config.cranelift_debug_verifier(enable);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_config_cranelift_opt_level_set(
|
||||
c: *mut wasm_config_t,
|
||||
opt_level: wasmtime_opt_level_t,
|
||||
) {
|
||||
use wasmtime_opt_level_t::*;
|
||||
(*c).config.cranelift_opt_level(match opt_level {
|
||||
WASMTIME_OPT_LEVEL_NONE => OptLevel::None,
|
||||
WASMTIME_OPT_LEVEL_SPEED => OptLevel::Speed,
|
||||
WASMTIME_OPT_LEVEL_SPEED_AND_SIZE => OptLevel::SpeedAndSize,
|
||||
});
|
||||
}
|
||||
1785
crates/c-api/src/lib.rs
Normal file
1785
crates/c-api/src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
239
crates/c-api/src/wasi.rs
Normal file
239
crates/c-api/src/wasi.rs
Normal file
@@ -0,0 +1,239 @@
|
||||
//! The WASI embedding API definitions for Wasmtime.
|
||||
use crate::{wasm_extern_t, wasm_importtype_t, wasm_store_t, wasm_trap_t, ExternHost, ExternType};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CStr;
|
||||
use std::fs::File;
|
||||
use std::os::raw::{c_char, c_int};
|
||||
use std::path::Path;
|
||||
use std::slice;
|
||||
use wasi_common::{preopen_dir, WasiCtxBuilder};
|
||||
use wasmtime::{HostRef, Trap};
|
||||
use wasmtime_wasi::Wasi;
|
||||
|
||||
unsafe fn cstr_to_path<'a>(path: *const c_char) -> Option<&'a Path> {
|
||||
CStr::from_ptr(path).to_str().map(Path::new).ok()
|
||||
}
|
||||
|
||||
unsafe fn open_file(path: *const c_char) -> Option<File> {
|
||||
File::open(cstr_to_path(path)?).ok()
|
||||
}
|
||||
|
||||
unsafe fn create_file(path: *const c_char) -> Option<File> {
|
||||
File::create(cstr_to_path(path)?).ok()
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct wasi_config_t {
|
||||
builder: WasiCtxBuilder,
|
||||
}
|
||||
|
||||
impl wasi_config_t {}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_config_new() -> *mut wasi_config_t {
|
||||
Box::into_raw(Box::new(wasi_config_t {
|
||||
builder: WasiCtxBuilder::new(),
|
||||
}))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_config_delete(config: *mut wasi_config_t) {
|
||||
drop(Box::from_raw(config));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_config_set_argv(
|
||||
config: *mut wasi_config_t,
|
||||
argc: c_int,
|
||||
argv: *const *const c_char,
|
||||
) {
|
||||
(*config).builder.args(
|
||||
slice::from_raw_parts(argv, argc as usize)
|
||||
.iter()
|
||||
.map(|a| slice::from_raw_parts(*a as *const u8, CStr::from_ptr(*a).to_bytes().len())),
|
||||
);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_config_inherit_argv(config: *mut wasi_config_t) {
|
||||
(*config).builder.inherit_args();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_config_set_env(
|
||||
config: *mut wasi_config_t,
|
||||
envc: c_int,
|
||||
names: *const *const c_char,
|
||||
values: *const *const c_char,
|
||||
) {
|
||||
let names = slice::from_raw_parts(names, envc as usize);
|
||||
let values = slice::from_raw_parts(values, envc as usize);
|
||||
|
||||
(*config).builder.envs(
|
||||
names
|
||||
.iter()
|
||||
.map(|p| CStr::from_ptr(*p).to_bytes())
|
||||
.zip(values.iter().map(|p| CStr::from_ptr(*p).to_bytes())),
|
||||
);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_config_inherit_env(config: *mut wasi_config_t) {
|
||||
(*config).builder.inherit_env();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_config_set_stdin_file(
|
||||
config: *mut wasi_config_t,
|
||||
path: *const c_char,
|
||||
) -> bool {
|
||||
let file = match open_file(path) {
|
||||
Some(f) => f,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
(*config).builder.stdin(file);
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_config_inherit_stdin(config: *mut wasi_config_t) {
|
||||
(*config).builder.inherit_stdin();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_config_set_stdout_file(
|
||||
config: *mut wasi_config_t,
|
||||
path: *const c_char,
|
||||
) -> bool {
|
||||
let file = match create_file(path) {
|
||||
Some(f) => f,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
(*config).builder.stdout(file);
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_config_inherit_stdout(config: *mut wasi_config_t) {
|
||||
(*config).builder.inherit_stdout();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_config_set_stderr_file(
|
||||
config: *mut wasi_config_t,
|
||||
path: *const c_char,
|
||||
) -> bool {
|
||||
let file = match create_file(path) {
|
||||
Some(f) => f,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
(*config).builder.stderr(file);
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_config_inherit_stderr(config: *mut wasi_config_t) {
|
||||
(*config).builder.inherit_stderr();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_config_preopen_dir(
|
||||
config: *mut wasi_config_t,
|
||||
path: *const c_char,
|
||||
guest_path: *const c_char,
|
||||
) -> bool {
|
||||
let guest_path = match cstr_to_path(guest_path) {
|
||||
Some(p) => p,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
let dir = match cstr_to_path(path) {
|
||||
Some(p) => match preopen_dir(p) {
|
||||
Ok(d) => d,
|
||||
Err(_) => return false,
|
||||
},
|
||||
None => return false,
|
||||
};
|
||||
|
||||
(*config).builder.preopened_dir(dir, guest_path);
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct wasi_instance_t {
|
||||
wasi: Wasi,
|
||||
export_cache: HashMap<String, Box<wasm_extern_t>>,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_instance_new(
|
||||
store: *mut wasm_store_t,
|
||||
config: *mut wasi_config_t,
|
||||
trap: *mut *mut wasm_trap_t,
|
||||
) -> *mut wasi_instance_t {
|
||||
let store = &(*store).store.borrow();
|
||||
let mut config = Box::from_raw(config);
|
||||
|
||||
match config.builder.build() {
|
||||
Ok(ctx) => Box::into_raw(Box::new(wasi_instance_t {
|
||||
wasi: Wasi::new(store, ctx),
|
||||
export_cache: HashMap::new(),
|
||||
})),
|
||||
Err(e) => {
|
||||
(*trap) = Box::into_raw(Box::new(wasm_trap_t {
|
||||
trap: HostRef::new(Trap::new(e.to_string())),
|
||||
}));
|
||||
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_instance_delete(instance: *mut wasi_instance_t) {
|
||||
drop(Box::from_raw(instance));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasi_instance_bind_import(
|
||||
instance: *mut wasi_instance_t,
|
||||
import: *const wasm_importtype_t,
|
||||
) -> *const wasm_extern_t {
|
||||
// TODO: support previous versions?
|
||||
if (*import).ty.module() != "wasi_snapshot_preview1" {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
// The import should be a function (WASI only exports functions)
|
||||
let func_type = match (*import).ty.ty() {
|
||||
ExternType::Func(f) => f,
|
||||
_ => return std::ptr::null_mut(),
|
||||
};
|
||||
|
||||
let name = (*import).ty.name();
|
||||
|
||||
match (*instance).wasi.get_export(name) {
|
||||
Some(export) => {
|
||||
if export.ty() != func_type {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
&**(*instance)
|
||||
.export_cache
|
||||
.entry(name.to_string())
|
||||
.or_insert_with(|| {
|
||||
Box::new(wasm_extern_t {
|
||||
which: ExternHost::Func(HostRef::new(export.clone())),
|
||||
})
|
||||
}) as *const wasm_extern_t
|
||||
}
|
||||
None => std::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
198
crates/c-api/tests/wasm-c-examples.rs
Normal file
198
crates/c-api/tests/wasm-c-examples.rs
Normal file
@@ -0,0 +1,198 @@
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
fn run_c_example(name: &'static str, expected_out: &[u8]) {
|
||||
let cargo = env::var("MAKE").unwrap_or("make".to_string());
|
||||
let pkg_dir = env!("CARGO_MANIFEST_DIR");
|
||||
let examples_dir = PathBuf::from(pkg_dir).join("examples");
|
||||
let make_arg = format!("run-{}-c", name);
|
||||
let output = Command::new(cargo)
|
||||
.current_dir(examples_dir)
|
||||
.args(&["-s", &make_arg])
|
||||
.output()
|
||||
.expect("success");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"failed to execute the C example '{}': {}",
|
||||
name,
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
);
|
||||
assert_eq!(
|
||||
output.stdout.as_slice(),
|
||||
expected_out,
|
||||
"unexpected stdout from example: {}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_hello_example() {
|
||||
run_c_example(
|
||||
"hello",
|
||||
br#"==== C hello ====
|
||||
Initializing...
|
||||
Loading binary...
|
||||
Compiling module...
|
||||
Creating callback...
|
||||
Instantiating module...
|
||||
Extracting export...
|
||||
Calling export...
|
||||
Calling back...
|
||||
> Hello World!
|
||||
Shutting down...
|
||||
Done.
|
||||
==== Done ====
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_memory_example() {
|
||||
run_c_example(
|
||||
"memory",
|
||||
br#"==== C memory ====
|
||||
Initializing...
|
||||
Loading binary...
|
||||
Compiling module...
|
||||
Instantiating module...
|
||||
Extracting exports...
|
||||
Checking memory...
|
||||
Mutating memory...
|
||||
Growing memory...
|
||||
Creating stand-alone memory...
|
||||
Shutting down...
|
||||
Done.
|
||||
==== Done ====
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_global_example() {
|
||||
run_c_example(
|
||||
"global",
|
||||
br#"==== C global ====
|
||||
Initializing...
|
||||
Loading binary...
|
||||
Compiling module...
|
||||
Creating globals...
|
||||
Instantiating module...
|
||||
Extracting exports...
|
||||
Accessing globals...
|
||||
Shutting down...
|
||||
Done.
|
||||
==== Done ====
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_callback_example() {
|
||||
run_c_example(
|
||||
"callback",
|
||||
br#"==== C callback ====
|
||||
Initializing...
|
||||
Loading binary...
|
||||
Compiling module...
|
||||
Creating callback...
|
||||
Instantiating module...
|
||||
Extracting export...
|
||||
Calling export...
|
||||
Calling back...
|
||||
> 7
|
||||
Calling back closure...
|
||||
> 42
|
||||
Printing result...
|
||||
> 49
|
||||
Shutting down...
|
||||
Done.
|
||||
==== Done ====
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_reflect_example() {
|
||||
run_c_example(
|
||||
"reflect",
|
||||
br#"==== C reflect ====
|
||||
Initializing...
|
||||
Loading binary...
|
||||
Compiling module...
|
||||
Instantiating module...
|
||||
Extracting export...
|
||||
> export 0 "func"
|
||||
>> initial: func i32 f64 f32 -> i32
|
||||
>> current: func i32 f64 f32 -> i32
|
||||
>> in-arity: 3, out-arity: 1
|
||||
> export 1 "global"
|
||||
>> initial: global const f64
|
||||
>> current: global const f64
|
||||
> export 2 "table"
|
||||
>> initial: table 0d 50d funcref
|
||||
>> current: table 0d 50d funcref
|
||||
> export 3 "memory"
|
||||
>> initial: memory 1d
|
||||
>> current: memory 1d
|
||||
Shutting down...
|
||||
Done.
|
||||
==== Done ====
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_start_example() {
|
||||
run_c_example(
|
||||
"start",
|
||||
br#"==== C start ====
|
||||
Initializing...
|
||||
Loading binary...
|
||||
Compiling module...
|
||||
Instantiating module...
|
||||
Printing message...
|
||||
> wasm trap: unreachable, source location: @002e
|
||||
Printing origin...
|
||||
> Empty origin.
|
||||
Printing trace...
|
||||
> Empty trace.
|
||||
Shutting down...
|
||||
Done.
|
||||
==== Done ====
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_trap_example() {
|
||||
run_c_example(
|
||||
"trap",
|
||||
br#"==== C trap ====
|
||||
Initializing...
|
||||
Loading binary...
|
||||
Compiling module...
|
||||
Creating callback...
|
||||
Instantiating module...
|
||||
Extracting exports...
|
||||
Calling export 0...
|
||||
Calling back...
|
||||
Printing message...
|
||||
> callback abort
|
||||
Printing origin...
|
||||
> Empty origin.
|
||||
Printing trace...
|
||||
> Empty trace.
|
||||
Calling export 1...
|
||||
Printing message...
|
||||
> wasm trap: unreachable, source location: @0065
|
||||
Printing origin...
|
||||
> Empty origin.
|
||||
Printing trace...
|
||||
> Empty trace.
|
||||
Shutting down...
|
||||
Done.
|
||||
==== Done ====
|
||||
"#,
|
||||
);
|
||||
}
|
||||
3
crates/debug/.gitignore
vendored
Normal file
3
crates/debug/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
target/
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
25
crates/debug/Cargo.toml
Normal file
25
crates/debug/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "wasmtime-debug"
|
||||
version = "0.12.0"
|
||||
authors = ["The Wasmtime Project Developers"]
|
||||
description = "Debug utils for WebAsssembly code in Cranelift"
|
||||
license = "Apache-2.0 WITH LLVM-exception"
|
||||
repository = "https://github.com/bytecodealliance/wasmtime"
|
||||
documentation = "https://docs.rs/wasmtime-debug/"
|
||||
categories = ["wasm"]
|
||||
keywords = ["webassembly", "wasm", "debuginfo"]
|
||||
readme = "README.md"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
gimli = "0.20.0"
|
||||
wasmparser = "0.51.2"
|
||||
faerie = "0.14.0"
|
||||
wasmtime-environ = { path = "../environ", version = "0.12.0" }
|
||||
target-lexicon = { version = "0.10.0", default-features = false }
|
||||
anyhow = "1.0"
|
||||
thiserror = "1.0.4"
|
||||
more-asserts = "0.2.1"
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "actively-developed" }
|
||||
220
crates/debug/LICENSE
Normal file
220
crates/debug/LICENSE
Normal file
@@ -0,0 +1,220 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
--- LLVM Exceptions to the Apache 2.0 License ----
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into an Object form of such source code, you
|
||||
may redistribute such embedded portions in such Object form without complying
|
||||
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
|
||||
|
||||
In addition, if you combine or link compiled forms of this Software with
|
||||
software that is licensed under the GPLv2 ("Combined Software") and if a
|
||||
court of competent jurisdiction determines that the patent provision (Section
|
||||
3), the indemnity provision (Section 9) or other Section of the License
|
||||
conflicts with the conditions of the GPLv2, you may retroactively and
|
||||
prospectively choose to deem waived or otherwise exclude such Section(s) of
|
||||
the License, but only in their entirety and only with respect to the Combined
|
||||
Software.
|
||||
|
||||
4
crates/debug/README.md
Normal file
4
crates/debug/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
This is the `wasmtime-debug` crate, which provides functionality to
|
||||
read, transform, and write DWARF section.
|
||||
|
||||
[`wasmtime-debug`]: https://crates.io/crates/wasmtime-debug
|
||||
231
crates/debug/src/gc.rs
Normal file
231
crates/debug/src/gc.rs
Normal file
@@ -0,0 +1,231 @@
|
||||
use crate::transform::AddressTransform;
|
||||
use gimli::constants;
|
||||
use gimli::read;
|
||||
use gimli::{Reader, UnitSectionOffset};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Dependencies {
|
||||
edges: HashMap<UnitSectionOffset, HashSet<UnitSectionOffset>>,
|
||||
roots: HashSet<UnitSectionOffset>,
|
||||
}
|
||||
|
||||
impl Dependencies {
|
||||
fn new() -> Dependencies {
|
||||
Dependencies {
|
||||
edges: HashMap::new(),
|
||||
roots: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_edge(&mut self, a: UnitSectionOffset, b: UnitSectionOffset) {
|
||||
use std::collections::hash_map::Entry;
|
||||
match self.edges.entry(a) {
|
||||
Entry::Occupied(mut o) => {
|
||||
o.get_mut().insert(b);
|
||||
}
|
||||
Entry::Vacant(v) => {
|
||||
let mut set = HashSet::new();
|
||||
set.insert(b);
|
||||
v.insert(set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_root(&mut self, root: UnitSectionOffset) {
|
||||
self.roots.insert(root);
|
||||
}
|
||||
|
||||
pub fn get_reachable(&self) -> HashSet<UnitSectionOffset> {
|
||||
let mut reachable = self.roots.clone();
|
||||
let mut queue = Vec::new();
|
||||
for i in self.roots.iter() {
|
||||
if let Some(deps) = self.edges.get(i) {
|
||||
for j in deps {
|
||||
if reachable.contains(j) {
|
||||
continue;
|
||||
}
|
||||
reachable.insert(*j);
|
||||
queue.push(*j);
|
||||
}
|
||||
}
|
||||
}
|
||||
while let Some(i) = queue.pop() {
|
||||
if let Some(deps) = self.edges.get(&i) {
|
||||
for j in deps {
|
||||
if reachable.contains(j) {
|
||||
continue;
|
||||
}
|
||||
reachable.insert(*j);
|
||||
queue.push(*j);
|
||||
}
|
||||
}
|
||||
}
|
||||
reachable
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_dependencies<R: Reader<Offset = usize>>(
|
||||
dwarf: &read::Dwarf<R>,
|
||||
at: &AddressTransform,
|
||||
) -> read::Result<Dependencies> {
|
||||
let mut deps = Dependencies::new();
|
||||
let mut units = dwarf.units();
|
||||
while let Some(unit) = units.next()? {
|
||||
build_unit_dependencies(unit, dwarf, at, &mut deps)?;
|
||||
}
|
||||
Ok(deps)
|
||||
}
|
||||
|
||||
fn build_unit_dependencies<R: Reader<Offset = usize>>(
|
||||
header: read::CompilationUnitHeader<R>,
|
||||
dwarf: &read::Dwarf<R>,
|
||||
at: &AddressTransform,
|
||||
deps: &mut Dependencies,
|
||||
) -> read::Result<()> {
|
||||
let unit = dwarf.unit(header)?;
|
||||
let mut tree = unit.entries_tree(None)?;
|
||||
let root = tree.root()?;
|
||||
build_die_dependencies(root, dwarf, &unit, at, deps)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn has_die_back_edge<R: Reader<Offset = usize>>(die: &read::DebuggingInformationEntry<R>) -> bool {
|
||||
match die.tag() {
|
||||
constants::DW_TAG_variable
|
||||
| constants::DW_TAG_constant
|
||||
| constants::DW_TAG_inlined_subroutine
|
||||
| constants::DW_TAG_lexical_block
|
||||
| constants::DW_TAG_label
|
||||
| constants::DW_TAG_with_stmt
|
||||
| constants::DW_TAG_try_block
|
||||
| constants::DW_TAG_catch_block
|
||||
| constants::DW_TAG_template_type_parameter
|
||||
| constants::DW_TAG_member
|
||||
| constants::DW_TAG_formal_parameter => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn has_valid_code_range<R: Reader<Offset = usize>>(
|
||||
die: &read::DebuggingInformationEntry<R>,
|
||||
dwarf: &read::Dwarf<R>,
|
||||
unit: &read::Unit<R>,
|
||||
at: &AddressTransform,
|
||||
) -> read::Result<bool> {
|
||||
match die.tag() {
|
||||
constants::DW_TAG_subprogram => {
|
||||
if let Some(ranges_attr) = die.attr_value(constants::DW_AT_ranges)? {
|
||||
let offset = match ranges_attr {
|
||||
read::AttributeValue::RangeListsRef(val) => val,
|
||||
read::AttributeValue::DebugRngListsIndex(index) => {
|
||||
dwarf.ranges_offset(unit, index)?
|
||||
}
|
||||
_ => return Ok(false),
|
||||
};
|
||||
let mut has_valid_base = if let Some(read::AttributeValue::Addr(low_pc)) =
|
||||
die.attr_value(constants::DW_AT_low_pc)?
|
||||
{
|
||||
Some(at.can_translate_address(low_pc))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut it = dwarf.ranges.raw_ranges(offset, unit.encoding())?;
|
||||
while let Some(range) = it.next()? {
|
||||
// If at least one of the range addresses can be converted,
|
||||
// declaring code range as valid.
|
||||
match range {
|
||||
read::RawRngListEntry::AddressOrOffsetPair { .. }
|
||||
if has_valid_base.is_some() =>
|
||||
{
|
||||
if has_valid_base.unwrap() {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
read::RawRngListEntry::StartEnd { begin, .. }
|
||||
| read::RawRngListEntry::StartLength { begin, .. }
|
||||
| read::RawRngListEntry::AddressOrOffsetPair { begin, .. } => {
|
||||
if at.can_translate_address(begin) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
read::RawRngListEntry::StartxEndx { begin, .. }
|
||||
| read::RawRngListEntry::StartxLength { begin, .. } => {
|
||||
let addr = dwarf.address(unit, begin)?;
|
||||
if at.can_translate_address(addr) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
read::RawRngListEntry::BaseAddress { addr } => {
|
||||
has_valid_base = Some(at.can_translate_address(addr));
|
||||
}
|
||||
read::RawRngListEntry::BaseAddressx { addr } => {
|
||||
let addr = dwarf.address(unit, addr)?;
|
||||
has_valid_base = Some(at.can_translate_address(addr));
|
||||
}
|
||||
read::RawRngListEntry::OffsetPair { .. } => (),
|
||||
}
|
||||
}
|
||||
return Ok(false);
|
||||
} else if let Some(low_pc) = die.attr_value(constants::DW_AT_low_pc)? {
|
||||
if let read::AttributeValue::Addr(a) = low_pc {
|
||||
return Ok(at.can_translate_address(a));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn build_die_dependencies<R: Reader<Offset = usize>>(
|
||||
die: read::EntriesTreeNode<R>,
|
||||
dwarf: &read::Dwarf<R>,
|
||||
unit: &read::Unit<R>,
|
||||
at: &AddressTransform,
|
||||
deps: &mut Dependencies,
|
||||
) -> read::Result<()> {
|
||||
let entry = die.entry();
|
||||
let offset = entry.offset().to_unit_section_offset(unit);
|
||||
let mut attrs = entry.attrs();
|
||||
while let Some(attr) = attrs.next()? {
|
||||
build_attr_dependencies(&attr, offset, dwarf, unit, at, deps)?;
|
||||
}
|
||||
|
||||
let mut children = die.children();
|
||||
while let Some(child) = children.next()? {
|
||||
let child_entry = child.entry();
|
||||
let child_offset = child_entry.offset().to_unit_section_offset(unit);
|
||||
deps.add_edge(child_offset, offset);
|
||||
if has_die_back_edge(child_entry) {
|
||||
deps.add_edge(offset, child_offset);
|
||||
}
|
||||
if has_valid_code_range(child_entry, dwarf, unit, at)? {
|
||||
deps.add_root(child_offset);
|
||||
}
|
||||
build_die_dependencies(child, dwarf, unit, at, deps)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_attr_dependencies<R: Reader<Offset = usize>>(
|
||||
attr: &read::Attribute<R>,
|
||||
offset: UnitSectionOffset,
|
||||
_dwarf: &read::Dwarf<R>,
|
||||
unit: &read::Unit<R>,
|
||||
_at: &AddressTransform,
|
||||
deps: &mut Dependencies,
|
||||
) -> read::Result<()> {
|
||||
match attr.value() {
|
||||
read::AttributeValue::UnitRef(val) => {
|
||||
let ref_offset = val.to_unit_section_offset(unit);
|
||||
deps.add_edge(offset, ref_offset);
|
||||
}
|
||||
read::AttributeValue::DebugInfoRef(val) => {
|
||||
let ref_offset = UnitSectionOffset::DebugInfoOffset(val);
|
||||
deps.add_edge(offset, ref_offset);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
184
crates/debug/src/lib.rs
Normal file
184
crates/debug/src/lib.rs
Normal file
@@ -0,0 +1,184 @@
|
||||
//! Debug utils for WebAssembly using Cranelift.
|
||||
|
||||
#![allow(clippy::cast_ptr_alignment)]
|
||||
|
||||
use anyhow::Error;
|
||||
use faerie::{Artifact, Decl};
|
||||
use more_asserts::assert_gt;
|
||||
use target_lexicon::{BinaryFormat, Triple};
|
||||
use wasmtime_environ::isa::TargetFrontendConfig;
|
||||
use wasmtime_environ::{ModuleAddressMap, ModuleVmctxInfo, ValueLabelsRanges};
|
||||
|
||||
pub use crate::read_debuginfo::{read_debuginfo, DebugInfoData, WasmFileInfo};
|
||||
pub use crate::transform::transform_dwarf;
|
||||
pub use crate::write_debuginfo::{emit_dwarf, ResolvedSymbol, SymbolResolver};
|
||||
|
||||
mod gc;
|
||||
mod read_debuginfo;
|
||||
mod transform;
|
||||
mod write_debuginfo;
|
||||
|
||||
struct FunctionRelocResolver {}
|
||||
impl SymbolResolver for FunctionRelocResolver {
|
||||
fn resolve_symbol(&self, symbol: usize, addend: i64) -> ResolvedSymbol {
|
||||
let name = format!("_wasm_function_{}", symbol);
|
||||
ResolvedSymbol::Reloc { name, addend }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_debugsections(
|
||||
obj: &mut Artifact,
|
||||
vmctx_info: &ModuleVmctxInfo,
|
||||
target_config: TargetFrontendConfig,
|
||||
debuginfo_data: &DebugInfoData,
|
||||
at: &ModuleAddressMap,
|
||||
ranges: &ValueLabelsRanges,
|
||||
) -> Result<(), Error> {
|
||||
let resolver = FunctionRelocResolver {};
|
||||
let dwarf = transform_dwarf(target_config, debuginfo_data, at, vmctx_info, ranges)?;
|
||||
emit_dwarf(obj, dwarf, &resolver)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct ImageRelocResolver<'a> {
|
||||
func_offsets: &'a Vec<u64>,
|
||||
}
|
||||
|
||||
impl<'a> SymbolResolver for ImageRelocResolver<'a> {
|
||||
fn resolve_symbol(&self, symbol: usize, addend: i64) -> ResolvedSymbol {
|
||||
let func_start = self.func_offsets[symbol];
|
||||
ResolvedSymbol::PhysicalAddress(func_start + addend as u64)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_debugsections_image(
|
||||
triple: Triple,
|
||||
target_config: TargetFrontendConfig,
|
||||
debuginfo_data: &DebugInfoData,
|
||||
vmctx_info: &ModuleVmctxInfo,
|
||||
at: &ModuleAddressMap,
|
||||
ranges: &ValueLabelsRanges,
|
||||
funcs: &[(*const u8, usize)],
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let func_offsets = &funcs
|
||||
.iter()
|
||||
.map(|(ptr, _)| *ptr as u64)
|
||||
.collect::<Vec<u64>>();
|
||||
let mut obj = Artifact::new(triple, String::from("module"));
|
||||
let resolver = ImageRelocResolver { func_offsets };
|
||||
let dwarf = transform_dwarf(target_config, debuginfo_data, at, vmctx_info, ranges)?;
|
||||
|
||||
// Assuming all functions in the same code block, looking min/max of its range.
|
||||
assert_gt!(funcs.len(), 0);
|
||||
let mut segment_body: (usize, usize) = (!0, 0);
|
||||
for (body_ptr, body_len) in funcs {
|
||||
segment_body.0 = std::cmp::min(segment_body.0, *body_ptr as usize);
|
||||
segment_body.1 = std::cmp::max(segment_body.1, *body_ptr as usize + body_len);
|
||||
}
|
||||
let segment_body = (segment_body.0 as *const u8, segment_body.1 - segment_body.0);
|
||||
|
||||
let body = unsafe { std::slice::from_raw_parts(segment_body.0, segment_body.1) };
|
||||
obj.declare_with("all", Decl::function(), body.to_vec())?;
|
||||
|
||||
emit_dwarf(&mut obj, dwarf, &resolver)?;
|
||||
|
||||
// LLDB is too "magical" about mach-o, generating elf
|
||||
let mut bytes = obj.emit_as(BinaryFormat::Elf)?;
|
||||
// elf is still missing details...
|
||||
convert_faerie_elf_to_loadable_file(&mut bytes, segment_body.0);
|
||||
|
||||
// let mut file = ::std::fs::File::create(::std::path::Path::new("test.o")).expect("file");
|
||||
// ::std::io::Write::write(&mut file, &bytes).expect("write");
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
fn convert_faerie_elf_to_loadable_file(bytes: &mut Vec<u8>, code_ptr: *const u8) {
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
assert!(
|
||||
bytes[0x4] == 2 && bytes[0x5] == 1,
|
||||
"bits and endianess in .ELF"
|
||||
);
|
||||
let e_phoff = unsafe { *(bytes.as_ptr().offset(0x20) as *const u64) };
|
||||
let e_phnum = unsafe { *(bytes.as_ptr().offset(0x38) as *const u16) };
|
||||
assert!(
|
||||
e_phoff == 0 && e_phnum == 0,
|
||||
"program header table is empty"
|
||||
);
|
||||
let e_phentsize = unsafe { *(bytes.as_ptr().offset(0x36) as *const u16) };
|
||||
assert_eq!(e_phentsize, 0x38, "size of ph");
|
||||
let e_shentsize = unsafe { *(bytes.as_ptr().offset(0x3A) as *const u16) };
|
||||
assert_eq!(e_shentsize, 0x40, "size of sh");
|
||||
|
||||
let e_shoff = unsafe { *(bytes.as_ptr().offset(0x28) as *const u64) };
|
||||
let e_shnum = unsafe { *(bytes.as_ptr().offset(0x3C) as *const u16) };
|
||||
let mut shstrtab_off = 0;
|
||||
let mut segment = None;
|
||||
for i in 0..e_shnum {
|
||||
let off = e_shoff as isize + i as isize * e_shentsize as isize;
|
||||
let sh_type = unsafe { *(bytes.as_ptr().offset(off + 0x4) as *const u32) };
|
||||
if sh_type == /* SHT_SYMTAB */ 3 {
|
||||
shstrtab_off = unsafe { *(bytes.as_ptr().offset(off + 0x18) as *const u64) };
|
||||
}
|
||||
if sh_type != /* SHT_PROGBITS */ 1 {
|
||||
continue;
|
||||
}
|
||||
// It is a SHT_PROGBITS, but we need to check sh_name to ensure it is our function
|
||||
let sh_name = unsafe {
|
||||
let sh_name_off = *(bytes.as_ptr().offset(off) as *const u32);
|
||||
CStr::from_ptr(
|
||||
bytes
|
||||
.as_ptr()
|
||||
.offset((shstrtab_off + sh_name_off as u64) as isize)
|
||||
as *const c_char,
|
||||
)
|
||||
.to_str()
|
||||
.expect("name")
|
||||
};
|
||||
if sh_name != ".text.all" {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert!(segment.is_none());
|
||||
// Functions was added at emit_debugsections_image as .text.all.
|
||||
// Patch vaddr, and save file location and its size.
|
||||
unsafe {
|
||||
*(bytes.as_ptr().offset(off + 0x10) as *mut u64) = code_ptr as u64;
|
||||
};
|
||||
let sh_offset = unsafe { *(bytes.as_ptr().offset(off + 0x18) as *const u64) };
|
||||
let sh_size = unsafe { *(bytes.as_ptr().offset(off + 0x20) as *const u64) };
|
||||
segment = Some((sh_offset, code_ptr, sh_size));
|
||||
// Fix name too: cut it to just ".text"
|
||||
unsafe {
|
||||
let sh_name_off = *(bytes.as_ptr().offset(off) as *const u32);
|
||||
bytes[(shstrtab_off + sh_name_off as u64) as usize + ".text".len()] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// LLDB wants segment with virtual address set, placing them at the end of ELF.
|
||||
let ph_off = bytes.len();
|
||||
if let Some((sh_offset, v_offset, sh_size)) = segment {
|
||||
let segment = vec![0; 0x38];
|
||||
unsafe {
|
||||
*(segment.as_ptr() as *mut u32) = /* PT_LOAD */ 0x1;
|
||||
*(segment.as_ptr().offset(0x8) as *mut u64) = sh_offset;
|
||||
*(segment.as_ptr().offset(0x10) as *mut u64) = v_offset as u64;
|
||||
*(segment.as_ptr().offset(0x18) as *mut u64) = v_offset as u64;
|
||||
*(segment.as_ptr().offset(0x20) as *mut u64) = sh_size;
|
||||
*(segment.as_ptr().offset(0x28) as *mut u64) = sh_size;
|
||||
}
|
||||
bytes.extend_from_slice(&segment);
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
// It is somewhat loadable ELF file at this moment.
|
||||
// Update e_flags, e_phoff and e_phnum.
|
||||
unsafe {
|
||||
*(bytes.as_ptr().offset(0x10) as *mut u16) = /* ET_DYN */ 3;
|
||||
*(bytes.as_ptr().offset(0x20) as *mut u64) = ph_off as u64;
|
||||
*(bytes.as_ptr().offset(0x38) as *mut u16) = 1 as u16;
|
||||
}
|
||||
}
|
||||
245
crates/debug/src/read_debuginfo.rs
Normal file
245
crates/debug/src/read_debuginfo.rs
Normal file
@@ -0,0 +1,245 @@
|
||||
use gimli::{
|
||||
DebugAbbrev, DebugAddr, DebugInfo, DebugLine, DebugLineStr, DebugLoc, DebugLocLists,
|
||||
DebugRanges, DebugRngLists, DebugStr, DebugStrOffsets, DebugTypes, EndianSlice, LittleEndian,
|
||||
LocationLists, RangeLists,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use wasmparser::{self, ModuleReader, SectionCode};
|
||||
|
||||
trait Reader: gimli::Reader<Offset = usize, Endian = LittleEndian> {}
|
||||
|
||||
impl<'input> Reader for gimli::EndianSlice<'input, LittleEndian> {}
|
||||
|
||||
pub use wasmparser::Type as WasmType;
|
||||
|
||||
pub type Dwarf<'input> = gimli::Dwarf<gimli::EndianSlice<'input, LittleEndian>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FunctionMetadata {
|
||||
pub params: Box<[WasmType]>,
|
||||
pub locals: Box<[(u32, WasmType)]>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WasmFileInfo {
|
||||
pub path: Option<PathBuf>,
|
||||
pub code_section_offset: u64,
|
||||
pub funcs: Box<[FunctionMetadata]>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NameSection {
|
||||
pub module_name: Option<String>,
|
||||
pub func_names: HashMap<u32, String>,
|
||||
pub locals_names: HashMap<u32, HashMap<u32, String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DebugInfoData<'a> {
|
||||
pub dwarf: Dwarf<'a>,
|
||||
pub name_section: Option<NameSection>,
|
||||
pub wasm_file: WasmFileInfo,
|
||||
}
|
||||
|
||||
fn convert_sections<'a>(sections: HashMap<&str, &'a [u8]>) -> Dwarf<'a> {
|
||||
const EMPTY_SECTION: &[u8] = &[];
|
||||
|
||||
let endian = LittleEndian;
|
||||
let debug_str = DebugStr::new(sections.get(".debug_str").unwrap_or(&EMPTY_SECTION), endian);
|
||||
let debug_abbrev = DebugAbbrev::new(
|
||||
sections.get(".debug_abbrev").unwrap_or(&EMPTY_SECTION),
|
||||
endian,
|
||||
);
|
||||
let debug_info = DebugInfo::new(
|
||||
sections.get(".debug_info").unwrap_or(&EMPTY_SECTION),
|
||||
endian,
|
||||
);
|
||||
let debug_line = DebugLine::new(
|
||||
sections.get(".debug_line").unwrap_or(&EMPTY_SECTION),
|
||||
endian,
|
||||
);
|
||||
|
||||
if sections.contains_key(".debug_addr") {
|
||||
panic!("Unexpected .debug_addr");
|
||||
}
|
||||
|
||||
let debug_addr = DebugAddr::from(EndianSlice::new(EMPTY_SECTION, endian));
|
||||
|
||||
if sections.contains_key(".debug_line_str") {
|
||||
panic!("Unexpected .debug_line_str");
|
||||
}
|
||||
|
||||
let debug_line_str = DebugLineStr::from(EndianSlice::new(EMPTY_SECTION, endian));
|
||||
let debug_str_sup = DebugStr::from(EndianSlice::new(EMPTY_SECTION, endian));
|
||||
|
||||
if sections.contains_key(".debug_rnglists") {
|
||||
panic!("Unexpected .debug_rnglists");
|
||||
}
|
||||
|
||||
let debug_ranges = match sections.get(".debug_ranges") {
|
||||
Some(section) => DebugRanges::new(section, endian),
|
||||
None => DebugRanges::new(EMPTY_SECTION, endian),
|
||||
};
|
||||
let debug_rnglists = DebugRngLists::new(EMPTY_SECTION, endian);
|
||||
let ranges = RangeLists::new(debug_ranges, debug_rnglists);
|
||||
|
||||
if sections.contains_key(".debug_loclists") {
|
||||
panic!("Unexpected .debug_loclists");
|
||||
}
|
||||
|
||||
let debug_loc = match sections.get(".debug_loc") {
|
||||
Some(section) => DebugLoc::new(section, endian),
|
||||
None => DebugLoc::new(EMPTY_SECTION, endian),
|
||||
};
|
||||
let debug_loclists = DebugLocLists::new(EMPTY_SECTION, endian);
|
||||
let locations = LocationLists::new(debug_loc, debug_loclists);
|
||||
|
||||
if sections.contains_key(".debug_str_offsets") {
|
||||
panic!("Unexpected .debug_str_offsets");
|
||||
}
|
||||
|
||||
let debug_str_offsets = DebugStrOffsets::from(EndianSlice::new(EMPTY_SECTION, endian));
|
||||
|
||||
if sections.contains_key(".debug_types") {
|
||||
panic!("Unexpected .debug_types");
|
||||
}
|
||||
|
||||
let debug_types = DebugTypes::from(EndianSlice::new(EMPTY_SECTION, endian));
|
||||
|
||||
Dwarf {
|
||||
debug_abbrev,
|
||||
debug_addr,
|
||||
debug_info,
|
||||
debug_line,
|
||||
debug_line_str,
|
||||
debug_str,
|
||||
debug_str_offsets,
|
||||
debug_str_sup,
|
||||
debug_types,
|
||||
locations,
|
||||
ranges,
|
||||
}
|
||||
}
|
||||
|
||||
fn read_name_section(reader: wasmparser::NameSectionReader) -> wasmparser::Result<NameSection> {
|
||||
let mut module_name = None;
|
||||
let mut func_names = HashMap::new();
|
||||
let mut locals_names = HashMap::new();
|
||||
for i in reader.into_iter() {
|
||||
match i? {
|
||||
wasmparser::Name::Module(m) => {
|
||||
module_name = Some(String::from(m.get_name()?));
|
||||
}
|
||||
wasmparser::Name::Function(f) => {
|
||||
let mut reader = f.get_map()?;
|
||||
while let Ok(naming) = reader.read() {
|
||||
func_names.insert(naming.index, String::from(naming.name));
|
||||
}
|
||||
}
|
||||
wasmparser::Name::Local(l) => {
|
||||
let mut reader = l.get_function_local_reader()?;
|
||||
while let Ok(f) = reader.read() {
|
||||
let mut names = HashMap::new();
|
||||
let mut reader = f.get_map()?;
|
||||
while let Ok(naming) = reader.read() {
|
||||
names.insert(naming.index, String::from(naming.name));
|
||||
}
|
||||
locals_names.insert(f.func_index, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let result = NameSection {
|
||||
module_name,
|
||||
func_names,
|
||||
locals_names,
|
||||
};
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn read_debuginfo(data: &[u8]) -> DebugInfoData {
|
||||
let mut reader = ModuleReader::new(data).expect("reader");
|
||||
let mut sections = HashMap::new();
|
||||
let mut name_section = None;
|
||||
let mut code_section_offset = 0;
|
||||
|
||||
let mut signatures_params: Vec<Box<[WasmType]>> = Vec::new();
|
||||
let mut func_params_refs: Vec<usize> = Vec::new();
|
||||
let mut func_locals: Vec<Box<[(u32, WasmType)]>> = Vec::new();
|
||||
|
||||
while !reader.eof() {
|
||||
let section = reader.read().expect("section");
|
||||
match section.code {
|
||||
SectionCode::Custom { name, .. } => {
|
||||
if name.starts_with(".debug_") {
|
||||
let mut reader = section.get_binary_reader();
|
||||
let len = reader.bytes_remaining();
|
||||
sections.insert(name, reader.read_bytes(len).expect("bytes"));
|
||||
}
|
||||
if name == "name" {
|
||||
if let Ok(reader) = section.get_name_section_reader() {
|
||||
if let Ok(section) = read_name_section(reader) {
|
||||
name_section = Some(section);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SectionCode::Type => {
|
||||
signatures_params = section
|
||||
.get_type_section_reader()
|
||||
.expect("type section")
|
||||
.into_iter()
|
||||
.map(|ft| ft.expect("type").params)
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
SectionCode::Function => {
|
||||
func_params_refs = section
|
||||
.get_function_section_reader()
|
||||
.expect("function section")
|
||||
.into_iter()
|
||||
.map(|index| index.expect("func index") as usize)
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
SectionCode::Code => {
|
||||
code_section_offset = section.range().start as u64;
|
||||
func_locals = section
|
||||
.get_code_section_reader()
|
||||
.expect("code section")
|
||||
.into_iter()
|
||||
.map(|body| {
|
||||
let locals = body
|
||||
.expect("body")
|
||||
.get_locals_reader()
|
||||
.expect("locals reader");
|
||||
locals
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.expect("locals data")
|
||||
.into_boxed_slice()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
let func_meta = func_params_refs
|
||||
.into_iter()
|
||||
.zip(func_locals.into_iter())
|
||||
.map(|(params_index, locals)| FunctionMetadata {
|
||||
params: signatures_params[params_index].clone(),
|
||||
locals,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
DebugInfoData {
|
||||
dwarf: convert_sections(sections),
|
||||
name_section,
|
||||
wasm_file: WasmFileInfo {
|
||||
path: None,
|
||||
code_section_offset,
|
||||
funcs: func_meta.into_boxed_slice(),
|
||||
},
|
||||
}
|
||||
}
|
||||
655
crates/debug/src/transform/address_transform.rs
Normal file
655
crates/debug/src/transform/address_transform.rs
Normal file
@@ -0,0 +1,655 @@
|
||||
use crate::WasmFileInfo;
|
||||
use gimli::write;
|
||||
use more_asserts::assert_le;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::iter::FromIterator;
|
||||
use wasmtime_environ::entity::{EntityRef, PrimaryMap};
|
||||
use wasmtime_environ::ir::SourceLoc;
|
||||
use wasmtime_environ::wasm::DefinedFuncIndex;
|
||||
use wasmtime_environ::{FunctionAddressMap, ModuleAddressMap};
|
||||
|
||||
pub type GeneratedAddress = usize;
|
||||
pub type WasmAddress = u64;
|
||||
|
||||
/// Contains mapping of the generated address to its original
|
||||
/// source location.
|
||||
#[derive(Debug)]
|
||||
pub struct AddressMap {
|
||||
pub generated: GeneratedAddress,
|
||||
pub wasm: WasmAddress,
|
||||
}
|
||||
|
||||
/// Information about generated function code: its body start,
|
||||
/// length, and instructions addresses.
|
||||
#[derive(Debug)]
|
||||
pub struct FunctionMap {
|
||||
pub offset: GeneratedAddress,
|
||||
pub len: GeneratedAddress,
|
||||
pub wasm_start: WasmAddress,
|
||||
pub wasm_end: WasmAddress,
|
||||
pub addresses: Box<[AddressMap]>,
|
||||
}
|
||||
|
||||
/// Mapping of the source location to its generated code range.
|
||||
#[derive(Debug)]
|
||||
struct Position {
|
||||
wasm_pos: WasmAddress,
|
||||
gen_start: GeneratedAddress,
|
||||
gen_end: GeneratedAddress,
|
||||
}
|
||||
|
||||
/// Mapping of continuous range of source location to its generated
|
||||
/// code. The positions are always in accending order for search.
|
||||
#[derive(Debug)]
|
||||
struct Range {
|
||||
wasm_start: WasmAddress,
|
||||
wasm_end: WasmAddress,
|
||||
gen_start: GeneratedAddress,
|
||||
gen_end: GeneratedAddress,
|
||||
positions: Box<[Position]>,
|
||||
}
|
||||
|
||||
/// Helper function address lookup data. Contains ranges start positions
|
||||
/// index and ranges data. The multiple ranges can include the same
|
||||
/// original source position. The index (B-Tree) uses range start
|
||||
/// position as a key.
|
||||
#[derive(Debug)]
|
||||
struct FuncLookup {
|
||||
index: Vec<(WasmAddress, Box<[usize]>)>,
|
||||
ranges: Box<[Range]>,
|
||||
}
|
||||
|
||||
/// Mapping of original functions to generated code locations/ranges.
|
||||
#[derive(Debug)]
|
||||
struct FuncTransform {
|
||||
start: WasmAddress,
|
||||
end: WasmAddress,
|
||||
index: DefinedFuncIndex,
|
||||
lookup: FuncLookup,
|
||||
}
|
||||
|
||||
/// Module functions mapping to generated code.
|
||||
#[derive(Debug)]
|
||||
pub struct AddressTransform {
|
||||
map: PrimaryMap<DefinedFuncIndex, FunctionMap>,
|
||||
func: Vec<(WasmAddress, FuncTransform)>,
|
||||
}
|
||||
|
||||
/// Returns a wasm bytecode offset in the code section from SourceLoc.
|
||||
pub fn get_wasm_code_offset(loc: SourceLoc, code_section_offset: u64) -> WasmAddress {
|
||||
// Code section size <= 4GB, allow wrapped SourceLoc to recover the overflow.
|
||||
loc.bits().wrapping_sub(code_section_offset as u32) as WasmAddress
|
||||
}
|
||||
|
||||
fn build_function_lookup(
|
||||
ft: &FunctionAddressMap,
|
||||
code_section_offset: u64,
|
||||
) -> (WasmAddress, WasmAddress, FuncLookup) {
|
||||
assert_le!(code_section_offset, ft.start_srcloc.bits() as u64);
|
||||
let fn_start = get_wasm_code_offset(ft.start_srcloc, code_section_offset);
|
||||
let fn_end = get_wasm_code_offset(ft.end_srcloc, code_section_offset);
|
||||
assert_le!(fn_start, fn_end);
|
||||
|
||||
// Build ranges of continuous source locations. The new ranges starts when
|
||||
// non-descending order is interrupted. Assuming the same origin location can
|
||||
// be present in multiple ranges.
|
||||
let mut range_wasm_start = fn_start;
|
||||
let mut range_gen_start = ft.body_offset;
|
||||
let mut last_wasm_pos = range_wasm_start;
|
||||
let mut ranges = Vec::new();
|
||||
let mut ranges_index = BTreeMap::new();
|
||||
let mut current_range = Vec::new();
|
||||
for t in &ft.instructions {
|
||||
if t.srcloc.is_default() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let offset = get_wasm_code_offset(t.srcloc, code_section_offset);
|
||||
assert_le!(fn_start, offset);
|
||||
assert_le!(offset, fn_end);
|
||||
|
||||
let inst_gen_start = t.code_offset;
|
||||
let inst_gen_end = t.code_offset + t.code_len;
|
||||
|
||||
if last_wasm_pos > offset {
|
||||
// Start new range.
|
||||
ranges_index.insert(range_wasm_start, ranges.len());
|
||||
ranges.push(Range {
|
||||
wasm_start: range_wasm_start,
|
||||
wasm_end: last_wasm_pos,
|
||||
gen_start: range_gen_start,
|
||||
gen_end: inst_gen_start,
|
||||
positions: current_range.into_boxed_slice(),
|
||||
});
|
||||
range_wasm_start = offset;
|
||||
range_gen_start = inst_gen_start;
|
||||
current_range = Vec::new();
|
||||
}
|
||||
// Continue existing range: add new wasm->generated code position.
|
||||
current_range.push(Position {
|
||||
wasm_pos: offset,
|
||||
gen_start: inst_gen_start,
|
||||
gen_end: inst_gen_end,
|
||||
});
|
||||
last_wasm_pos = offset;
|
||||
}
|
||||
let last_gen_addr = ft.body_offset + ft.body_len;
|
||||
ranges_index.insert(range_wasm_start, ranges.len());
|
||||
ranges.push(Range {
|
||||
wasm_start: range_wasm_start,
|
||||
wasm_end: fn_end,
|
||||
gen_start: range_gen_start,
|
||||
gen_end: last_gen_addr,
|
||||
positions: current_range.into_boxed_slice(),
|
||||
});
|
||||
|
||||
// Making ranges lookup faster by building index: B-tree with every range
|
||||
// start position that maps into list of active ranges at this position.
|
||||
let ranges = ranges.into_boxed_slice();
|
||||
let mut active_ranges = Vec::new();
|
||||
let mut index = BTreeMap::new();
|
||||
let mut last_wasm_pos = None;
|
||||
for (wasm_start, range_index) in ranges_index {
|
||||
if Some(wasm_start) == last_wasm_pos {
|
||||
active_ranges.push(range_index);
|
||||
continue;
|
||||
}
|
||||
if let Some(position) = last_wasm_pos {
|
||||
index.insert(position, active_ranges.clone().into_boxed_slice());
|
||||
}
|
||||
active_ranges.retain(|r| ranges[*r].wasm_end.cmp(&wasm_start) != std::cmp::Ordering::Less);
|
||||
active_ranges.push(range_index);
|
||||
last_wasm_pos = Some(wasm_start);
|
||||
}
|
||||
index.insert(last_wasm_pos.unwrap(), active_ranges.into_boxed_slice());
|
||||
let index = Vec::from_iter(index.into_iter());
|
||||
(fn_start, fn_end, FuncLookup { index, ranges })
|
||||
}
|
||||
|
||||
fn build_function_addr_map(
|
||||
at: &ModuleAddressMap,
|
||||
code_section_offset: u64,
|
||||
) -> PrimaryMap<DefinedFuncIndex, FunctionMap> {
|
||||
let mut map = PrimaryMap::new();
|
||||
for (_, ft) in at {
|
||||
let mut fn_map = Vec::new();
|
||||
for t in &ft.instructions {
|
||||
if t.srcloc.is_default() {
|
||||
continue;
|
||||
}
|
||||
let offset = get_wasm_code_offset(t.srcloc, code_section_offset);
|
||||
fn_map.push(AddressMap {
|
||||
generated: t.code_offset,
|
||||
wasm: offset,
|
||||
});
|
||||
}
|
||||
|
||||
if cfg!(debug) {
|
||||
// fn_map is sorted by the generated field -- see FunctionAddressMap::instructions.
|
||||
for i in 1..fn_map.len() {
|
||||
assert_le!(fn_map[i - 1].generated, fn_map[i].generated);
|
||||
}
|
||||
}
|
||||
|
||||
map.push(FunctionMap {
|
||||
offset: ft.body_offset,
|
||||
len: ft.body_len,
|
||||
wasm_start: get_wasm_code_offset(ft.start_srcloc, code_section_offset),
|
||||
wasm_end: get_wasm_code_offset(ft.end_srcloc, code_section_offset),
|
||||
addresses: fn_map.into_boxed_slice(),
|
||||
});
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
struct TransformRangeIter<'a> {
|
||||
addr: u64,
|
||||
indicies: &'a [usize],
|
||||
ranges: &'a [Range],
|
||||
}
|
||||
|
||||
impl<'a> TransformRangeIter<'a> {
|
||||
fn new(func: &'a FuncTransform, addr: u64) -> Self {
|
||||
let found = match func
|
||||
.lookup
|
||||
.index
|
||||
.binary_search_by(|entry| entry.0.cmp(&addr))
|
||||
{
|
||||
Ok(i) => Some(&func.lookup.index[i].1),
|
||||
Err(i) => {
|
||||
if i > 0 {
|
||||
Some(&func.lookup.index[i - 1].1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Some(range_indices) = found {
|
||||
TransformRangeIter {
|
||||
addr,
|
||||
indicies: range_indices,
|
||||
ranges: &func.lookup.ranges,
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'a> Iterator for TransformRangeIter<'a> {
|
||||
type Item = (usize, usize);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some((first, tail)) = self.indicies.split_first() {
|
||||
let range_index = *first;
|
||||
let range = &self.ranges[range_index];
|
||||
self.indicies = tail;
|
||||
let address = match range
|
||||
.positions
|
||||
.binary_search_by(|a| a.wasm_pos.cmp(&self.addr))
|
||||
{
|
||||
Ok(i) => range.positions[i].gen_start,
|
||||
Err(i) => {
|
||||
if i == 0 {
|
||||
range.gen_start
|
||||
} else {
|
||||
range.positions[i - 1].gen_end
|
||||
}
|
||||
}
|
||||
};
|
||||
Some((address, range_index))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TransformRangeEndIter<'a> {
|
||||
addr: u64,
|
||||
indicies: &'a [usize],
|
||||
ranges: &'a [Range],
|
||||
}
|
||||
|
||||
impl<'a> TransformRangeEndIter<'a> {
|
||||
fn new(func: &'a FuncTransform, addr: u64) -> Self {
|
||||
let found = match func
|
||||
.lookup
|
||||
.index
|
||||
.binary_search_by(|entry| entry.0.cmp(&addr))
|
||||
{
|
||||
Ok(i) => Some(&func.lookup.index[i].1),
|
||||
Err(i) => {
|
||||
if i > 0 {
|
||||
Some(&func.lookup.index[i - 1].1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Some(range_indices) = found {
|
||||
TransformRangeEndIter {
|
||||
addr,
|
||||
indicies: range_indices,
|
||||
ranges: &func.lookup.ranges,
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for TransformRangeEndIter<'a> {
|
||||
type Item = (usize, usize);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some((first, tail)) = self.indicies.split_first() {
|
||||
let range_index = *first;
|
||||
let range = &self.ranges[range_index];
|
||||
if range.wasm_start >= self.addr {
|
||||
continue;
|
||||
}
|
||||
self.indicies = tail;
|
||||
let address = match range
|
||||
.positions
|
||||
.binary_search_by(|a| a.wasm_pos.cmp(&self.addr))
|
||||
{
|
||||
Ok(i) => range.positions[i].gen_end,
|
||||
Err(i) => {
|
||||
if i == range.positions.len() {
|
||||
range.gen_end
|
||||
} else {
|
||||
range.positions[i].gen_start
|
||||
}
|
||||
}
|
||||
};
|
||||
return Some((address, range_index));
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl AddressTransform {
|
||||
pub fn new(at: &ModuleAddressMap, wasm_file: &WasmFileInfo) -> Self {
|
||||
let code_section_offset = wasm_file.code_section_offset;
|
||||
|
||||
let mut func = BTreeMap::new();
|
||||
for (i, ft) in at {
|
||||
let (fn_start, fn_end, lookup) = build_function_lookup(ft, code_section_offset);
|
||||
|
||||
func.insert(
|
||||
fn_start,
|
||||
FuncTransform {
|
||||
start: fn_start,
|
||||
end: fn_end,
|
||||
index: i,
|
||||
lookup,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let map = build_function_addr_map(at, code_section_offset);
|
||||
let func = Vec::from_iter(func.into_iter());
|
||||
AddressTransform { map, func }
|
||||
}
|
||||
|
||||
fn find_func(&self, addr: u64) -> Option<&FuncTransform> {
|
||||
// TODO check if we need to include end address
|
||||
let func = match self.func.binary_search_by(|entry| entry.0.cmp(&addr)) {
|
||||
Ok(i) => &self.func[i].1,
|
||||
Err(i) => {
|
||||
if i > 0 {
|
||||
&self.func[i - 1].1
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
if addr >= func.start {
|
||||
return Some(func);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn find_func_index(&self, addr: u64) -> Option<DefinedFuncIndex> {
|
||||
self.find_func(addr).map(|f| f.index)
|
||||
}
|
||||
|
||||
pub fn translate_raw(&self, addr: u64) -> Option<(DefinedFuncIndex, GeneratedAddress)> {
|
||||
if addr == 0 {
|
||||
// It's normally 0 for debug info without the linked code.
|
||||
return None;
|
||||
}
|
||||
if let Some(func) = self.find_func(addr) {
|
||||
if addr == func.end {
|
||||
// Clamp last address to the end to extend translation to the end
|
||||
// of the function.
|
||||
let map = &self.map[func.index];
|
||||
return Some((func.index, map.len));
|
||||
}
|
||||
let first_result = TransformRangeIter::new(func, addr).next();
|
||||
first_result.map(|(address, _)| (func.index, address))
|
||||
} else {
|
||||
// Address was not found: function was not compiled?
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_translate_address(&self, addr: u64) -> bool {
|
||||
self.translate(addr).is_some()
|
||||
}
|
||||
|
||||
pub fn translate(&self, addr: u64) -> Option<write::Address> {
|
||||
self.translate_raw(addr)
|
||||
.map(|(func_index, address)| write::Address::Symbol {
|
||||
symbol: func_index.index(),
|
||||
addend: address as i64,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn translate_ranges_raw(
|
||||
&self,
|
||||
start: u64,
|
||||
end: u64,
|
||||
) -> Option<(DefinedFuncIndex, Vec<(GeneratedAddress, GeneratedAddress)>)> {
|
||||
if start == 0 {
|
||||
// It's normally 0 for debug info without the linked code.
|
||||
return None;
|
||||
}
|
||||
if let Some(func) = self.find_func(start) {
|
||||
let mut starts: HashMap<usize, usize> =
|
||||
HashMap::from_iter(TransformRangeIter::new(func, start).map(|(a, r)| (r, a)));
|
||||
let mut result = Vec::new();
|
||||
TransformRangeEndIter::new(func, end).for_each(|(a, r)| {
|
||||
let range_start = if let Some(range_start) = starts.get(&r) {
|
||||
let range_start = *range_start;
|
||||
starts.remove(&r);
|
||||
range_start
|
||||
} else {
|
||||
let range = &func.lookup.ranges[r];
|
||||
range.gen_start
|
||||
};
|
||||
result.push((range_start, a));
|
||||
});
|
||||
for (r, range_start) in starts {
|
||||
let range = &func.lookup.ranges[r];
|
||||
result.push((range_start, range.gen_end));
|
||||
}
|
||||
return Some((func.index, result));
|
||||
}
|
||||
// Address was not found: function was not compiled?
|
||||
None
|
||||
}
|
||||
|
||||
pub fn translate_ranges(&self, start: u64, end: u64) -> Vec<(write::Address, u64)> {
|
||||
self.translate_ranges_raw(start, end)
|
||||
.map_or(vec![], |(func_index, ranges)| {
|
||||
ranges
|
||||
.iter()
|
||||
.map(|(start, end)| {
|
||||
(
|
||||
write::Address::Symbol {
|
||||
symbol: func_index.index(),
|
||||
addend: *start as i64,
|
||||
},
|
||||
(*end - *start) as u64,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn map(&self) -> &PrimaryMap<DefinedFuncIndex, FunctionMap> {
|
||||
&self.map
|
||||
}
|
||||
|
||||
pub fn func_range(&self, index: DefinedFuncIndex) -> (GeneratedAddress, GeneratedAddress) {
|
||||
let map = &self.map[index];
|
||||
(map.offset, map.offset + map.len)
|
||||
}
|
||||
|
||||
pub fn func_source_range(&self, index: DefinedFuncIndex) -> (WasmAddress, WasmAddress) {
|
||||
let map = &self.map[index];
|
||||
(map.wasm_start, map.wasm_end)
|
||||
}
|
||||
|
||||
pub fn convert_to_code_range(
|
||||
&self,
|
||||
addr: write::Address,
|
||||
len: u64,
|
||||
) -> (GeneratedAddress, GeneratedAddress) {
|
||||
let start = if let write::Address::Symbol { addend, .. } = addr {
|
||||
// TODO subtract self.map[symbol].offset ?
|
||||
addend as GeneratedAddress
|
||||
} else {
|
||||
unreachable!();
|
||||
};
|
||||
(start, start + len as GeneratedAddress)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{build_function_lookup, get_wasm_code_offset, AddressTransform};
|
||||
use crate::read_debuginfo::WasmFileInfo;
|
||||
use gimli::write::Address;
|
||||
use std::iter::FromIterator;
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::ir::SourceLoc;
|
||||
use wasmtime_environ::{FunctionAddressMap, InstructionAddressMap, ModuleAddressMap};
|
||||
|
||||
#[test]
|
||||
fn test_get_wasm_code_offset() {
|
||||
let offset = get_wasm_code_offset(SourceLoc::new(3), 1);
|
||||
assert_eq!(2, offset);
|
||||
let offset = get_wasm_code_offset(SourceLoc::new(16), 0xF000_0000);
|
||||
assert_eq!(0x1000_0010, offset);
|
||||
let offset = get_wasm_code_offset(SourceLoc::new(1), 0x20_8000_0000);
|
||||
assert_eq!(0x8000_0001, offset);
|
||||
}
|
||||
|
||||
fn create_simple_func(wasm_offset: u32) -> FunctionAddressMap {
|
||||
FunctionAddressMap {
|
||||
instructions: vec![
|
||||
InstructionAddressMap {
|
||||
srcloc: SourceLoc::new(wasm_offset + 2),
|
||||
code_offset: 5,
|
||||
code_len: 3,
|
||||
},
|
||||
InstructionAddressMap {
|
||||
srcloc: SourceLoc::new(wasm_offset + 7),
|
||||
code_offset: 15,
|
||||
code_len: 8,
|
||||
},
|
||||
],
|
||||
start_srcloc: SourceLoc::new(wasm_offset),
|
||||
end_srcloc: SourceLoc::new(wasm_offset + 10),
|
||||
body_offset: 0,
|
||||
body_len: 30,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_simple_module(func: FunctionAddressMap) -> ModuleAddressMap {
|
||||
PrimaryMap::from_iter(vec![func])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_function_lookup_simple() {
|
||||
let input = create_simple_func(11);
|
||||
let (start, end, lookup) = build_function_lookup(&input, 1);
|
||||
assert_eq!(10, start);
|
||||
assert_eq!(20, end);
|
||||
|
||||
assert_eq!(1, lookup.index.len());
|
||||
let index_entry = lookup.index.into_iter().next().unwrap();
|
||||
assert_eq!((10u64, vec![0].into_boxed_slice()), index_entry);
|
||||
assert_eq!(1, lookup.ranges.len());
|
||||
let range = &lookup.ranges[0];
|
||||
assert_eq!(10, range.wasm_start);
|
||||
assert_eq!(20, range.wasm_end);
|
||||
assert_eq!(0, range.gen_start);
|
||||
assert_eq!(30, range.gen_end);
|
||||
let positions = &range.positions;
|
||||
assert_eq!(2, positions.len());
|
||||
assert_eq!(12, positions[0].wasm_pos);
|
||||
assert_eq!(5, positions[0].gen_start);
|
||||
assert_eq!(8, positions[0].gen_end);
|
||||
assert_eq!(17, positions[1].wasm_pos);
|
||||
assert_eq!(15, positions[1].gen_start);
|
||||
assert_eq!(23, positions[1].gen_end);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_function_lookup_two_ranges() {
|
||||
let mut input = create_simple_func(11);
|
||||
// append instruction with same srcloc as input.instructions[0]
|
||||
input.instructions.push(InstructionAddressMap {
|
||||
srcloc: SourceLoc::new(11 + 2),
|
||||
code_offset: 23,
|
||||
code_len: 3,
|
||||
});
|
||||
let (start, end, lookup) = build_function_lookup(&input, 1);
|
||||
assert_eq!(10, start);
|
||||
assert_eq!(20, end);
|
||||
|
||||
assert_eq!(2, lookup.index.len());
|
||||
let index_entries = Vec::from_iter(lookup.index.into_iter());
|
||||
assert_eq!((10u64, vec![0].into_boxed_slice()), index_entries[0]);
|
||||
assert_eq!((12u64, vec![0, 1].into_boxed_slice()), index_entries[1]);
|
||||
assert_eq!(2, lookup.ranges.len());
|
||||
|
||||
let range = &lookup.ranges[0];
|
||||
assert_eq!(10, range.wasm_start);
|
||||
assert_eq!(17, range.wasm_end);
|
||||
assert_eq!(0, range.gen_start);
|
||||
assert_eq!(23, range.gen_end);
|
||||
let positions = &range.positions;
|
||||
assert_eq!(2, positions.len());
|
||||
assert_eq!(12, positions[0].wasm_pos);
|
||||
assert_eq!(5, positions[0].gen_start);
|
||||
assert_eq!(8, positions[0].gen_end);
|
||||
assert_eq!(17, positions[1].wasm_pos);
|
||||
assert_eq!(15, positions[1].gen_start);
|
||||
assert_eq!(23, positions[1].gen_end);
|
||||
|
||||
let range = &lookup.ranges[1];
|
||||
assert_eq!(12, range.wasm_start);
|
||||
assert_eq!(20, range.wasm_end);
|
||||
assert_eq!(23, range.gen_start);
|
||||
assert_eq!(30, range.gen_end);
|
||||
let positions = &range.positions;
|
||||
assert_eq!(1, positions.len());
|
||||
assert_eq!(12, positions[0].wasm_pos);
|
||||
assert_eq!(23, positions[0].gen_start);
|
||||
assert_eq!(26, positions[0].gen_end);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addr_translate() {
|
||||
let input = create_simple_module(create_simple_func(11));
|
||||
let at = AddressTransform::new(
|
||||
&input,
|
||||
&WasmFileInfo {
|
||||
path: None,
|
||||
code_section_offset: 1,
|
||||
funcs: Box::new([]),
|
||||
},
|
||||
);
|
||||
|
||||
let addr = at.translate(10);
|
||||
assert_eq!(
|
||||
Some(Address::Symbol {
|
||||
symbol: 0,
|
||||
addend: 0,
|
||||
}),
|
||||
addr
|
||||
);
|
||||
|
||||
let addr = at.translate(20);
|
||||
assert_eq!(
|
||||
Some(Address::Symbol {
|
||||
symbol: 0,
|
||||
addend: 30,
|
||||
}),
|
||||
addr
|
||||
);
|
||||
|
||||
let addr = at.translate(0);
|
||||
assert_eq!(None, addr);
|
||||
|
||||
let addr = at.translate(12);
|
||||
assert_eq!(
|
||||
Some(Address::Symbol {
|
||||
symbol: 0,
|
||||
addend: 5,
|
||||
}),
|
||||
addr
|
||||
);
|
||||
|
||||
let addr = at.translate(18);
|
||||
assert_eq!(
|
||||
Some(Address::Symbol {
|
||||
symbol: 0,
|
||||
addend: 23,
|
||||
}),
|
||||
addr
|
||||
);
|
||||
}
|
||||
}
|
||||
283
crates/debug/src/transform/attr.rs
Normal file
283
crates/debug/src/transform/attr.rs
Normal file
@@ -0,0 +1,283 @@
|
||||
use super::address_transform::AddressTransform;
|
||||
use super::expression::{compile_expression, CompiledExpression, FunctionFrameInfo};
|
||||
use super::range_info_builder::RangeInfoBuilder;
|
||||
use super::refs::{PendingDebugInfoRefs, PendingUnitRefs};
|
||||
use super::{DebugInputContext, Reader, TransformError};
|
||||
use anyhow::Error;
|
||||
use gimli::{write, AttributeValue, DebugLineOffset, DebugStr, DebuggingInformationEntry};
|
||||
|
||||
pub(crate) enum FileAttributeContext<'a> {
|
||||
Root(Option<DebugLineOffset>),
|
||||
Children(&'a Vec<write::FileId>, Option<&'a CompiledExpression>),
|
||||
}
|
||||
|
||||
fn is_exprloc_to_loclist_allowed(attr_name: gimli::constants::DwAt) -> bool {
|
||||
match attr_name {
|
||||
gimli::DW_AT_location
|
||||
| gimli::DW_AT_string_length
|
||||
| gimli::DW_AT_return_addr
|
||||
| gimli::DW_AT_data_member_location
|
||||
| gimli::DW_AT_frame_base
|
||||
| gimli::DW_AT_segment
|
||||
| gimli::DW_AT_static_link
|
||||
| gimli::DW_AT_use_location
|
||||
| gimli::DW_AT_vtable_elem_location => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn clone_die_attributes<'a, R>(
|
||||
entry: &DebuggingInformationEntry<R>,
|
||||
context: &DebugInputContext<R>,
|
||||
addr_tr: &'a AddressTransform,
|
||||
frame_info: Option<&FunctionFrameInfo>,
|
||||
unit_encoding: gimli::Encoding,
|
||||
out_unit: &mut write::Unit,
|
||||
current_scope_id: write::UnitEntryId,
|
||||
subprogram_range_builder: Option<RangeInfoBuilder>,
|
||||
scope_ranges: Option<&Vec<(u64, u64)>>,
|
||||
cu_low_pc: u64,
|
||||
out_strings: &mut write::StringTable,
|
||||
pending_die_refs: &mut PendingUnitRefs,
|
||||
pending_di_refs: &mut PendingDebugInfoRefs,
|
||||
file_context: FileAttributeContext<'a>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
let _tag = &entry.tag();
|
||||
let endian = gimli::RunTimeEndian::Little;
|
||||
|
||||
let range_info = if let Some(subprogram_range_builder) = subprogram_range_builder {
|
||||
subprogram_range_builder
|
||||
} else if entry.tag() == gimli::DW_TAG_compile_unit {
|
||||
// FIXME currently address_transform operate on a single func range,
|
||||
// once it is fixed we can properly set DW_AT_ranges attribute.
|
||||
// Using for now DW_AT_low_pc = 0.
|
||||
RangeInfoBuilder::Position(0)
|
||||
} else {
|
||||
RangeInfoBuilder::from(entry, context, unit_encoding, cu_low_pc)?
|
||||
};
|
||||
range_info.build(addr_tr, out_unit, current_scope_id);
|
||||
|
||||
let mut attrs = entry.attrs();
|
||||
while let Some(attr) = attrs.next()? {
|
||||
let attr_value = match attr.value() {
|
||||
AttributeValue::Addr(_) if attr.name() == gimli::DW_AT_low_pc => {
|
||||
continue;
|
||||
}
|
||||
AttributeValue::Udata(_) if attr.name() == gimli::DW_AT_high_pc => {
|
||||
continue;
|
||||
}
|
||||
AttributeValue::RangeListsRef(_) if attr.name() == gimli::DW_AT_ranges => {
|
||||
continue;
|
||||
}
|
||||
AttributeValue::Exprloc(_) if attr.name() == gimli::DW_AT_frame_base => {
|
||||
continue;
|
||||
}
|
||||
|
||||
AttributeValue::Addr(u) => {
|
||||
let addr = addr_tr.translate(u).unwrap_or(write::Address::Constant(0));
|
||||
write::AttributeValue::Address(addr)
|
||||
}
|
||||
AttributeValue::Udata(u) => write::AttributeValue::Udata(u),
|
||||
AttributeValue::Data1(d) => write::AttributeValue::Data1(d),
|
||||
AttributeValue::Data2(d) => write::AttributeValue::Data2(d),
|
||||
AttributeValue::Data4(d) => write::AttributeValue::Data4(d),
|
||||
AttributeValue::Sdata(d) => write::AttributeValue::Sdata(d),
|
||||
AttributeValue::Flag(f) => write::AttributeValue::Flag(f),
|
||||
AttributeValue::DebugLineRef(line_program_offset) => {
|
||||
if let FileAttributeContext::Root(o) = file_context {
|
||||
if o != Some(line_program_offset) {
|
||||
return Err(TransformError("invalid debug_line offset").into());
|
||||
}
|
||||
write::AttributeValue::LineProgramRef
|
||||
} else {
|
||||
return Err(TransformError("unexpected debug_line index attribute").into());
|
||||
}
|
||||
}
|
||||
AttributeValue::FileIndex(i) => {
|
||||
if let FileAttributeContext::Children(file_map, _) = file_context {
|
||||
write::AttributeValue::FileIndex(Some(file_map[(i - 1) as usize]))
|
||||
} else {
|
||||
return Err(TransformError("unexpected file index attribute").into());
|
||||
}
|
||||
}
|
||||
AttributeValue::DebugStrRef(str_offset) => {
|
||||
let s = context.debug_str.get_str(str_offset)?.to_slice()?.to_vec();
|
||||
write::AttributeValue::StringRef(out_strings.add(s))
|
||||
}
|
||||
AttributeValue::RangeListsRef(r) => {
|
||||
let range_info =
|
||||
RangeInfoBuilder::from_ranges_ref(r, context, unit_encoding, cu_low_pc)?;
|
||||
let range_list_id = range_info.build_ranges(addr_tr, &mut out_unit.ranges);
|
||||
write::AttributeValue::RangeListRef(range_list_id)
|
||||
}
|
||||
AttributeValue::LocationListsRef(r) => {
|
||||
let low_pc = 0;
|
||||
let mut locs = context.loclists.locations(
|
||||
r,
|
||||
unit_encoding,
|
||||
low_pc,
|
||||
&context.debug_addr,
|
||||
context.debug_addr_base,
|
||||
)?;
|
||||
let frame_base = if let FileAttributeContext::Children(_, frame_base) = file_context
|
||||
{
|
||||
frame_base
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut result = None;
|
||||
while let Some(loc) = locs.next()? {
|
||||
if let Some(expr) = compile_expression(&loc.data, unit_encoding, frame_base)? {
|
||||
if result.is_none() {
|
||||
result = Some(Vec::new());
|
||||
}
|
||||
for (start, len, expr) in expr.build_with_locals(
|
||||
&[(loc.range.begin, loc.range.end)],
|
||||
addr_tr,
|
||||
frame_info,
|
||||
endian,
|
||||
) {
|
||||
if len == 0 {
|
||||
// Ignore empty range
|
||||
continue;
|
||||
}
|
||||
result.as_mut().unwrap().push(write::Location::StartLength {
|
||||
begin: start,
|
||||
length: len,
|
||||
data: expr,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// FIXME _expr contains invalid expression
|
||||
continue; // ignore entry
|
||||
}
|
||||
}
|
||||
if result.is_none() {
|
||||
continue; // no valid locations
|
||||
}
|
||||
let list_id = out_unit.locations.add(write::LocationList(result.unwrap()));
|
||||
write::AttributeValue::LocationListRef(list_id)
|
||||
}
|
||||
AttributeValue::Exprloc(ref expr) => {
|
||||
let frame_base = if let FileAttributeContext::Children(_, frame_base) = file_context
|
||||
{
|
||||
frame_base
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(expr) = compile_expression(expr, unit_encoding, frame_base)? {
|
||||
if expr.is_simple() {
|
||||
if let Some(expr) = expr.build() {
|
||||
write::AttributeValue::Exprloc(expr)
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// Conversion to loclist is required.
|
||||
if let Some(scope_ranges) = scope_ranges {
|
||||
let exprs =
|
||||
expr.build_with_locals(scope_ranges, addr_tr, frame_info, endian);
|
||||
if exprs.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let found_single_expr = {
|
||||
// Micro-optimization all expressions alike, use one exprloc.
|
||||
let mut found_expr: Option<write::Expression> = None;
|
||||
for (_, _, expr) in &exprs {
|
||||
if let Some(ref prev_expr) = found_expr {
|
||||
if expr.0.eq(&prev_expr.0) {
|
||||
continue; // the same expression
|
||||
}
|
||||
found_expr = None;
|
||||
break;
|
||||
}
|
||||
found_expr = Some(expr.clone())
|
||||
}
|
||||
found_expr
|
||||
};
|
||||
if let Some(expr) = found_single_expr {
|
||||
write::AttributeValue::Exprloc(expr)
|
||||
} else if is_exprloc_to_loclist_allowed(attr.name()) {
|
||||
// Converting exprloc to loclist.
|
||||
let mut locs = Vec::new();
|
||||
for (begin, length, data) in exprs {
|
||||
if length == 0 {
|
||||
// Ignore empty range
|
||||
continue;
|
||||
}
|
||||
locs.push(write::Location::StartLength {
|
||||
begin,
|
||||
length,
|
||||
data,
|
||||
});
|
||||
}
|
||||
let list_id = out_unit.locations.add(write::LocationList(locs));
|
||||
write::AttributeValue::LocationListRef(list_id)
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// FIXME _expr contains invalid expression
|
||||
continue; // ignore attribute
|
||||
}
|
||||
}
|
||||
AttributeValue::Encoding(e) => write::AttributeValue::Encoding(e),
|
||||
AttributeValue::DecimalSign(e) => write::AttributeValue::DecimalSign(e),
|
||||
AttributeValue::Endianity(e) => write::AttributeValue::Endianity(e),
|
||||
AttributeValue::Accessibility(e) => write::AttributeValue::Accessibility(e),
|
||||
AttributeValue::Visibility(e) => write::AttributeValue::Visibility(e),
|
||||
AttributeValue::Virtuality(e) => write::AttributeValue::Virtuality(e),
|
||||
AttributeValue::Language(e) => write::AttributeValue::Language(e),
|
||||
AttributeValue::AddressClass(e) => write::AttributeValue::AddressClass(e),
|
||||
AttributeValue::IdentifierCase(e) => write::AttributeValue::IdentifierCase(e),
|
||||
AttributeValue::CallingConvention(e) => write::AttributeValue::CallingConvention(e),
|
||||
AttributeValue::Inline(e) => write::AttributeValue::Inline(e),
|
||||
AttributeValue::Ordering(e) => write::AttributeValue::Ordering(e),
|
||||
AttributeValue::UnitRef(offset) => {
|
||||
pending_die_refs.insert(current_scope_id, attr.name(), offset);
|
||||
continue;
|
||||
}
|
||||
AttributeValue::DebugInfoRef(offset) => {
|
||||
pending_di_refs.insert(current_scope_id, attr.name(), offset);
|
||||
continue;
|
||||
}
|
||||
_ => panic!(), //write::AttributeValue::StringRef(out_strings.add("_")),
|
||||
};
|
||||
let current_scope = out_unit.get_mut(current_scope_id);
|
||||
current_scope.set(attr.name(), attr_value);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn clone_attr_string<R>(
|
||||
attr_value: &AttributeValue<R>,
|
||||
form: gimli::DwForm,
|
||||
debug_str: &DebugStr<R>,
|
||||
out_strings: &mut write::StringTable,
|
||||
) -> Result<write::LineString, gimli::Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
let content = match attr_value {
|
||||
AttributeValue::DebugStrRef(str_offset) => {
|
||||
debug_str.get_str(*str_offset)?.to_slice()?.to_vec()
|
||||
}
|
||||
AttributeValue::String(b) => b.to_slice()?.to_vec(),
|
||||
_ => panic!("Unexpected attribute value"),
|
||||
};
|
||||
Ok(match form {
|
||||
gimli::DW_FORM_strp => {
|
||||
let id = out_strings.add(content);
|
||||
write::LineString::StringRef(id)
|
||||
}
|
||||
gimli::DW_FORM_string => write::LineString::String(content),
|
||||
_ => panic!("DW_FORM_line_strp or other not supported"),
|
||||
})
|
||||
}
|
||||
564
crates/debug/src/transform/expression.rs
Normal file
564
crates/debug/src/transform/expression.rs
Normal file
@@ -0,0 +1,564 @@
|
||||
use super::address_transform::AddressTransform;
|
||||
use anyhow::Error;
|
||||
use gimli::{self, write, Expression, Operation, Reader, ReaderOffset, Register, X86_64};
|
||||
use more_asserts::{assert_le, assert_lt};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use wasmtime_environ::entity::EntityRef;
|
||||
use wasmtime_environ::ir::{StackSlots, ValueLabel, ValueLabelsRanges, ValueLoc};
|
||||
use wasmtime_environ::isa::RegUnit;
|
||||
use wasmtime_environ::wasm::{get_vmctx_value_label, DefinedFuncIndex};
|
||||
use wasmtime_environ::ModuleMemoryOffset;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FunctionFrameInfo<'a> {
|
||||
pub value_ranges: &'a ValueLabelsRanges,
|
||||
pub memory_offset: ModuleMemoryOffset,
|
||||
pub stack_slots: &'a StackSlots,
|
||||
}
|
||||
|
||||
impl<'a> FunctionFrameInfo<'a> {
|
||||
fn vmctx_memory_offset(&self) -> Option<i64> {
|
||||
match self.memory_offset {
|
||||
ModuleMemoryOffset::Defined(x) => Some(x as i64),
|
||||
ModuleMemoryOffset::Imported(_) => {
|
||||
// TODO implement memory offset for imported memory
|
||||
None
|
||||
}
|
||||
ModuleMemoryOffset::None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum CompiledExpressionPart {
|
||||
Code(Vec<u8>),
|
||||
Local(ValueLabel),
|
||||
Deref,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CompiledExpression {
|
||||
parts: Vec<CompiledExpressionPart>,
|
||||
need_deref: bool,
|
||||
}
|
||||
|
||||
impl Clone for CompiledExpressionPart {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
CompiledExpressionPart::Code(c) => CompiledExpressionPart::Code(c.clone()),
|
||||
CompiledExpressionPart::Local(i) => CompiledExpressionPart::Local(*i),
|
||||
CompiledExpressionPart::Deref => CompiledExpressionPart::Deref,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CompiledExpression {
|
||||
pub fn vmctx() -> CompiledExpression {
|
||||
CompiledExpression::from_label(get_vmctx_value_label())
|
||||
}
|
||||
|
||||
pub fn from_label(label: ValueLabel) -> CompiledExpression {
|
||||
CompiledExpression {
|
||||
parts: vec![
|
||||
CompiledExpressionPart::Local(label),
|
||||
CompiledExpressionPart::Code(vec![gimli::constants::DW_OP_stack_value.0 as u8]),
|
||||
],
|
||||
need_deref: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn map_reg(reg: RegUnit) -> Register {
|
||||
static mut REG_X86_MAP: Option<HashMap<RegUnit, Register>> = None;
|
||||
// FIXME lazy initialization?
|
||||
unsafe {
|
||||
if REG_X86_MAP.is_none() {
|
||||
REG_X86_MAP = Some(HashMap::new());
|
||||
}
|
||||
if let Some(val) = REG_X86_MAP.as_mut().unwrap().get(®) {
|
||||
return *val;
|
||||
}
|
||||
let result = match reg {
|
||||
0 => X86_64::RAX,
|
||||
1 => X86_64::RCX,
|
||||
2 => X86_64::RDX,
|
||||
3 => X86_64::RBX,
|
||||
4 => X86_64::RSP,
|
||||
5 => X86_64::RBP,
|
||||
6 => X86_64::RSI,
|
||||
7 => X86_64::RDI,
|
||||
8 => X86_64::R8,
|
||||
9 => X86_64::R9,
|
||||
10 => X86_64::R10,
|
||||
11 => X86_64::R11,
|
||||
12 => X86_64::R12,
|
||||
13 => X86_64::R13,
|
||||
14 => X86_64::R14,
|
||||
15 => X86_64::R15,
|
||||
16 => X86_64::XMM0,
|
||||
17 => X86_64::XMM1,
|
||||
18 => X86_64::XMM2,
|
||||
19 => X86_64::XMM3,
|
||||
20 => X86_64::XMM4,
|
||||
21 => X86_64::XMM5,
|
||||
22 => X86_64::XMM6,
|
||||
23 => X86_64::XMM7,
|
||||
24 => X86_64::XMM8,
|
||||
25 => X86_64::XMM9,
|
||||
26 => X86_64::XMM10,
|
||||
27 => X86_64::XMM11,
|
||||
28 => X86_64::XMM12,
|
||||
29 => X86_64::XMM13,
|
||||
30 => X86_64::XMM14,
|
||||
31 => X86_64::XMM15,
|
||||
_ => panic!("unknown x86_64 register {}", reg),
|
||||
};
|
||||
REG_X86_MAP.as_mut().unwrap().insert(reg, result);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
fn translate_loc(loc: ValueLoc, frame_info: Option<&FunctionFrameInfo>) -> Option<Vec<u8>> {
|
||||
use gimli::write::Writer;
|
||||
match loc {
|
||||
ValueLoc::Reg(reg) => {
|
||||
let machine_reg = map_reg(reg).0 as u8;
|
||||
Some(if machine_reg < 32 {
|
||||
vec![gimli::constants::DW_OP_reg0.0 + machine_reg]
|
||||
} else {
|
||||
let endian = gimli::RunTimeEndian::Little;
|
||||
let mut writer = write::EndianVec::new(endian);
|
||||
writer
|
||||
.write_u8(gimli::constants::DW_OP_regx.0 as u8)
|
||||
.expect("regx");
|
||||
writer
|
||||
.write_uleb128(machine_reg.into())
|
||||
.expect("machine_reg");
|
||||
writer.into_vec()
|
||||
})
|
||||
}
|
||||
ValueLoc::Stack(ss) => {
|
||||
if let Some(frame_info) = frame_info {
|
||||
if let Some(ss_offset) = frame_info.stack_slots[ss].offset {
|
||||
let endian = gimli::RunTimeEndian::Little;
|
||||
let mut writer = write::EndianVec::new(endian);
|
||||
writer
|
||||
.write_u8(gimli::constants::DW_OP_breg0.0 + X86_64::RBP.0 as u8)
|
||||
.expect("bp wr");
|
||||
writer.write_sleb128(ss_offset as i64 + 16).expect("ss wr");
|
||||
writer
|
||||
.write_u8(gimli::constants::DW_OP_deref.0 as u8)
|
||||
.expect("bp wr");
|
||||
let buf = writer.into_vec();
|
||||
return Some(buf);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn append_memory_deref(
|
||||
buf: &mut Vec<u8>,
|
||||
frame_info: &FunctionFrameInfo,
|
||||
vmctx_loc: ValueLoc,
|
||||
endian: gimli::RunTimeEndian,
|
||||
) -> write::Result<bool> {
|
||||
use gimli::write::Writer;
|
||||
let mut writer = write::EndianVec::new(endian);
|
||||
// FIXME for imported memory
|
||||
match vmctx_loc {
|
||||
ValueLoc::Reg(vmctx_reg) => {
|
||||
let reg = map_reg(vmctx_reg);
|
||||
writer.write_u8(gimli::constants::DW_OP_breg0.0 + reg.0 as u8)?;
|
||||
let memory_offset = match frame_info.vmctx_memory_offset() {
|
||||
Some(offset) => offset,
|
||||
None => {
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
writer.write_sleb128(memory_offset)?;
|
||||
}
|
||||
ValueLoc::Stack(ss) => {
|
||||
if let Some(ss_offset) = frame_info.stack_slots[ss].offset {
|
||||
writer.write_u8(gimli::constants::DW_OP_breg0.0 + X86_64::RBP.0 as u8)?;
|
||||
writer.write_sleb128(ss_offset as i64 + 16)?;
|
||||
writer.write_u8(gimli::constants::DW_OP_deref.0 as u8)?;
|
||||
writer.write_u8(gimli::constants::DW_OP_consts.0 as u8)?;
|
||||
let memory_offset = match frame_info.vmctx_memory_offset() {
|
||||
Some(offset) => offset,
|
||||
None => {
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
writer.write_sleb128(memory_offset)?;
|
||||
writer.write_u8(gimli::constants::DW_OP_plus.0 as u8)?;
|
||||
} else {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
writer.write_u8(gimli::constants::DW_OP_deref.0 as u8)?;
|
||||
writer.write_u8(gimli::constants::DW_OP_swap.0 as u8)?;
|
||||
writer.write_u8(gimli::constants::DW_OP_stack_value.0 as u8)?;
|
||||
writer.write_u8(gimli::constants::DW_OP_constu.0 as u8)?;
|
||||
writer.write_uleb128(0xffff_ffff)?;
|
||||
writer.write_u8(gimli::constants::DW_OP_and.0 as u8)?;
|
||||
writer.write_u8(gimli::constants::DW_OP_plus.0 as u8)?;
|
||||
buf.extend_from_slice(writer.slice());
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
impl CompiledExpression {
|
||||
pub fn is_simple(&self) -> bool {
|
||||
if let [CompiledExpressionPart::Code(_)] = self.parts.as_slice() {
|
||||
true
|
||||
} else {
|
||||
self.parts.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(&self) -> Option<write::Expression> {
|
||||
if let [CompiledExpressionPart::Code(code)] = self.parts.as_slice() {
|
||||
return Some(write::Expression(code.to_vec()));
|
||||
}
|
||||
// locals found, not supported
|
||||
None
|
||||
}
|
||||
|
||||
pub fn build_with_locals(
|
||||
&self,
|
||||
scope: &[(u64, u64)], // wasm ranges
|
||||
addr_tr: &AddressTransform,
|
||||
frame_info: Option<&FunctionFrameInfo>,
|
||||
endian: gimli::RunTimeEndian,
|
||||
) -> Vec<(write::Address, u64, write::Expression)> {
|
||||
if scope.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
if let [CompiledExpressionPart::Code(code)] = self.parts.as_slice() {
|
||||
let mut result_scope = Vec::new();
|
||||
for s in scope {
|
||||
for (addr, len) in addr_tr.translate_ranges(s.0, s.1) {
|
||||
result_scope.push((addr, len, write::Expression(code.to_vec())));
|
||||
}
|
||||
}
|
||||
return result_scope;
|
||||
}
|
||||
|
||||
let vmctx_label = get_vmctx_value_label();
|
||||
|
||||
// Some locals are present, preparing and divided ranges based on the scope
|
||||
// and frame_info data.
|
||||
let mut ranges_builder = ValueLabelRangesBuilder::new(scope, addr_tr, frame_info);
|
||||
for p in &self.parts {
|
||||
match p {
|
||||
CompiledExpressionPart::Code(_) => (),
|
||||
CompiledExpressionPart::Local(label) => ranges_builder.process_label(*label),
|
||||
CompiledExpressionPart::Deref => ranges_builder.process_label(vmctx_label),
|
||||
}
|
||||
}
|
||||
if self.need_deref {
|
||||
ranges_builder.process_label(vmctx_label);
|
||||
}
|
||||
ranges_builder.remove_incomplete_ranges();
|
||||
let ranges = ranges_builder.ranges;
|
||||
|
||||
let mut result = Vec::new();
|
||||
'range: for CachedValueLabelRange {
|
||||
func_index,
|
||||
start,
|
||||
end,
|
||||
label_location,
|
||||
} in ranges
|
||||
{
|
||||
// build expression
|
||||
let mut code_buf = Vec::new();
|
||||
for part in &self.parts {
|
||||
match part {
|
||||
CompiledExpressionPart::Code(c) => code_buf.extend_from_slice(c.as_slice()),
|
||||
CompiledExpressionPart::Local(label) => {
|
||||
let loc = *label_location.get(&label).expect("loc");
|
||||
if let Some(expr) = translate_loc(loc, frame_info) {
|
||||
code_buf.extend_from_slice(&expr)
|
||||
} else {
|
||||
continue 'range;
|
||||
}
|
||||
}
|
||||
CompiledExpressionPart::Deref => {
|
||||
if let (Some(vmctx_loc), Some(frame_info)) =
|
||||
(label_location.get(&vmctx_label), frame_info)
|
||||
{
|
||||
if !append_memory_deref(&mut code_buf, frame_info, *vmctx_loc, endian)
|
||||
.expect("append_memory_deref")
|
||||
{
|
||||
continue 'range;
|
||||
}
|
||||
} else {
|
||||
continue 'range;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.need_deref {
|
||||
if let (Some(vmctx_loc), Some(frame_info)) =
|
||||
(label_location.get(&vmctx_label), frame_info)
|
||||
{
|
||||
if !append_memory_deref(&mut code_buf, frame_info, *vmctx_loc, endian)
|
||||
.expect("append_memory_deref")
|
||||
{
|
||||
continue 'range;
|
||||
}
|
||||
} else {
|
||||
continue 'range;
|
||||
};
|
||||
}
|
||||
result.push((
|
||||
write::Address::Symbol {
|
||||
symbol: func_index.index(),
|
||||
addend: start as i64,
|
||||
},
|
||||
(end - start) as u64,
|
||||
write::Expression(code_buf),
|
||||
));
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
fn is_old_expression_format(buf: &[u8]) -> bool {
|
||||
// Heuristic to detect old variable expression format without DW_OP_fbreg:
|
||||
// DW_OP_plus_uconst op must be present, but not DW_OP_fbreg.
|
||||
if buf.contains(&(gimli::constants::DW_OP_fbreg.0 as u8)) {
|
||||
// Stop check if DW_OP_fbreg exist.
|
||||
return false;
|
||||
}
|
||||
buf.contains(&(gimli::constants::DW_OP_plus_uconst.0 as u8))
|
||||
}
|
||||
|
||||
pub fn compile_expression<R>(
|
||||
expr: &Expression<R>,
|
||||
encoding: gimli::Encoding,
|
||||
frame_base: Option<&CompiledExpression>,
|
||||
) -> Result<Option<CompiledExpression>, Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
let mut pc = expr.0.clone();
|
||||
let buf = expr.0.to_slice()?;
|
||||
let mut parts = Vec::new();
|
||||
let mut need_deref = false;
|
||||
if is_old_expression_format(&buf) && frame_base.is_some() {
|
||||
// Still supporting old DWARF variable expressions without fbreg.
|
||||
parts.extend_from_slice(&frame_base.unwrap().parts);
|
||||
need_deref = frame_base.unwrap().need_deref;
|
||||
}
|
||||
let base_len = parts.len();
|
||||
let mut code_chunk = Vec::new();
|
||||
while !pc.is_empty() {
|
||||
let next = buf[pc.offset_from(&expr.0).into_u64() as usize];
|
||||
need_deref = true;
|
||||
if next == 0xED {
|
||||
// WebAssembly DWARF extension
|
||||
pc.read_u8()?;
|
||||
let ty = pc.read_uleb128()?;
|
||||
assert_eq!(ty, 0);
|
||||
let index = pc.read_sleb128()?;
|
||||
pc.read_u8()?; // consume 159
|
||||
if !code_chunk.is_empty() {
|
||||
parts.push(CompiledExpressionPart::Code(code_chunk));
|
||||
code_chunk = Vec::new();
|
||||
}
|
||||
let label = ValueLabel::from_u32(index as u32);
|
||||
parts.push(CompiledExpressionPart::Local(label));
|
||||
} else {
|
||||
let pos = pc.offset_from(&expr.0).into_u64() as usize;
|
||||
let op = Operation::parse(&mut pc, &expr.0, encoding)?;
|
||||
match op {
|
||||
Operation::FrameOffset { offset } => {
|
||||
// Expand DW_OP_fpreg into frame location and DW_OP_plus_uconst.
|
||||
use gimli::write::Writer;
|
||||
if frame_base.is_some() {
|
||||
// Add frame base expressions.
|
||||
if !code_chunk.is_empty() {
|
||||
parts.push(CompiledExpressionPart::Code(code_chunk));
|
||||
code_chunk = Vec::new();
|
||||
}
|
||||
parts.extend_from_slice(&frame_base.unwrap().parts);
|
||||
need_deref = frame_base.unwrap().need_deref;
|
||||
}
|
||||
// Append DW_OP_plus_uconst part.
|
||||
let endian = gimli::RunTimeEndian::Little;
|
||||
let mut writer = write::EndianVec::new(endian);
|
||||
writer.write_u8(gimli::constants::DW_OP_plus_uconst.0 as u8)?;
|
||||
writer.write_uleb128(offset as u64)?;
|
||||
code_chunk.extend(writer.into_vec());
|
||||
continue;
|
||||
}
|
||||
Operation::Literal { .. } | Operation::PlusConstant { .. } => (),
|
||||
Operation::StackValue => {
|
||||
need_deref = false;
|
||||
}
|
||||
Operation::Deref { .. } => {
|
||||
if !code_chunk.is_empty() {
|
||||
parts.push(CompiledExpressionPart::Code(code_chunk));
|
||||
code_chunk = Vec::new();
|
||||
}
|
||||
parts.push(CompiledExpressionPart::Deref);
|
||||
}
|
||||
_ => {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
let chunk = &buf[pos..pc.offset_from(&expr.0).into_u64() as usize];
|
||||
code_chunk.extend_from_slice(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
if !code_chunk.is_empty() {
|
||||
parts.push(CompiledExpressionPart::Code(code_chunk));
|
||||
}
|
||||
|
||||
if base_len > 0 && base_len + 1 < parts.len() {
|
||||
// see if we can glue two code chunks
|
||||
if let [CompiledExpressionPart::Code(cc1), CompiledExpressionPart::Code(cc2)] =
|
||||
&parts[base_len..=base_len]
|
||||
{
|
||||
let mut combined = cc1.clone();
|
||||
combined.extend_from_slice(cc2);
|
||||
parts[base_len] = CompiledExpressionPart::Code(combined);
|
||||
parts.remove(base_len + 1);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(CompiledExpression { parts, need_deref }))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct CachedValueLabelRange {
|
||||
func_index: DefinedFuncIndex,
|
||||
start: usize,
|
||||
end: usize,
|
||||
label_location: HashMap<ValueLabel, ValueLoc>,
|
||||
}
|
||||
|
||||
struct ValueLabelRangesBuilder<'a, 'b> {
|
||||
ranges: Vec<CachedValueLabelRange>,
|
||||
addr_tr: &'a AddressTransform,
|
||||
frame_info: Option<&'a FunctionFrameInfo<'b>>,
|
||||
processed_labels: HashSet<ValueLabel>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> ValueLabelRangesBuilder<'a, 'b> {
|
||||
fn new(
|
||||
scope: &[(u64, u64)], // wasm ranges
|
||||
addr_tr: &'a AddressTransform,
|
||||
frame_info: Option<&'a FunctionFrameInfo<'b>>,
|
||||
) -> Self {
|
||||
let mut ranges = Vec::new();
|
||||
for s in scope {
|
||||
if let Some((func_index, tr)) = addr_tr.translate_ranges_raw(s.0, s.1) {
|
||||
for (start, end) in tr {
|
||||
ranges.push(CachedValueLabelRange {
|
||||
func_index,
|
||||
start,
|
||||
end,
|
||||
label_location: HashMap::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start));
|
||||
ValueLabelRangesBuilder {
|
||||
ranges,
|
||||
addr_tr,
|
||||
frame_info,
|
||||
processed_labels: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn process_label(&mut self, label: ValueLabel) {
|
||||
if self.processed_labels.contains(&label) {
|
||||
return;
|
||||
}
|
||||
self.processed_labels.insert(label);
|
||||
|
||||
let value_ranges = if let Some(frame_info) = self.frame_info {
|
||||
&frame_info.value_ranges
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let ranges = &mut self.ranges;
|
||||
if let Some(local_ranges) = value_ranges.get(&label) {
|
||||
for local_range in local_ranges {
|
||||
let wasm_start = local_range.start;
|
||||
let wasm_end = local_range.end;
|
||||
let loc = local_range.loc;
|
||||
// Find all native ranges for the value label ranges.
|
||||
for (addr, len) in self
|
||||
.addr_tr
|
||||
.translate_ranges(wasm_start as u64, wasm_end as u64)
|
||||
{
|
||||
let (range_start, range_end) = self.addr_tr.convert_to_code_range(addr, len);
|
||||
if range_start == range_end {
|
||||
continue;
|
||||
}
|
||||
assert_lt!(range_start, range_end);
|
||||
// Find acceptable scope of ranges to intersect with.
|
||||
let i = match ranges.binary_search_by(|s| s.start.cmp(&range_start)) {
|
||||
Ok(i) => i,
|
||||
Err(i) => {
|
||||
if i > 0 && range_start < ranges[i - 1].end {
|
||||
i - 1
|
||||
} else {
|
||||
i
|
||||
}
|
||||
}
|
||||
};
|
||||
let j = match ranges.binary_search_by(|s| s.start.cmp(&range_end)) {
|
||||
Ok(i) | Err(i) => i,
|
||||
};
|
||||
// Starting for the end, intersect (range_start..range_end) with
|
||||
// self.ranges array.
|
||||
for i in (i..j).rev() {
|
||||
if range_end <= ranges[i].start || ranges[i].end <= range_start {
|
||||
continue;
|
||||
}
|
||||
if range_end < ranges[i].end {
|
||||
// Cutting some of the range from the end.
|
||||
let mut tail = ranges[i].clone();
|
||||
ranges[i].end = range_end;
|
||||
tail.start = range_end;
|
||||
ranges.insert(i + 1, tail);
|
||||
}
|
||||
assert_le!(ranges[i].end, range_end);
|
||||
if range_start <= ranges[i].start {
|
||||
ranges[i].label_location.insert(label, loc);
|
||||
continue;
|
||||
}
|
||||
// Cutting some of the range from the start.
|
||||
let mut tail = ranges[i].clone();
|
||||
ranges[i].end = range_start;
|
||||
tail.start = range_start;
|
||||
tail.label_location.insert(label, loc);
|
||||
ranges.insert(i + 1, tail);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_incomplete_ranges(&mut self) {
|
||||
// Ranges with not-enough labels are discarded.
|
||||
let processed_labels_len = self.processed_labels.len();
|
||||
self.ranges
|
||||
.retain(|r| r.label_location.len() == processed_labels_len);
|
||||
}
|
||||
}
|
||||
230
crates/debug/src/transform/line_program.rs
Normal file
230
crates/debug/src/transform/line_program.rs
Normal file
@@ -0,0 +1,230 @@
|
||||
use super::address_transform::AddressTransform;
|
||||
use super::attr::clone_attr_string;
|
||||
use super::{Reader, TransformError};
|
||||
use anyhow::Error;
|
||||
use gimli::{
|
||||
write, DebugLine, DebugLineOffset, DebugStr, DebuggingInformationEntry, LineEncoding, Unit,
|
||||
};
|
||||
use more_asserts::assert_le;
|
||||
use std::collections::BTreeMap;
|
||||
use std::iter::FromIterator;
|
||||
use wasmtime_environ::entity::EntityRef;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SavedLineProgramRow {
|
||||
Normal {
|
||||
address: u64,
|
||||
op_index: u64,
|
||||
file_index: u64,
|
||||
line: u64,
|
||||
column: u64,
|
||||
discriminator: u64,
|
||||
is_stmt: bool,
|
||||
basic_block: bool,
|
||||
prologue_end: bool,
|
||||
epilogue_begin: bool,
|
||||
isa: u64,
|
||||
},
|
||||
EndOfSequence(u64),
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
enum ReadLineProgramState {
|
||||
SequenceEnded,
|
||||
ReadSequence,
|
||||
IgnoreSequence,
|
||||
}
|
||||
|
||||
pub(crate) fn clone_line_program<R>(
|
||||
unit: &Unit<R, R::Offset>,
|
||||
root: &DebuggingInformationEntry<R>,
|
||||
addr_tr: &AddressTransform,
|
||||
out_encoding: gimli::Encoding,
|
||||
debug_str: &DebugStr<R>,
|
||||
debug_line: &DebugLine<R>,
|
||||
out_strings: &mut write::StringTable,
|
||||
) -> Result<(write::LineProgram, DebugLineOffset, Vec<write::FileId>), Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
let offset = match root.attr_value(gimli::DW_AT_stmt_list)? {
|
||||
Some(gimli::AttributeValue::DebugLineRef(offset)) => offset,
|
||||
_ => {
|
||||
return Err(TransformError("Debug line offset is not found").into());
|
||||
}
|
||||
};
|
||||
let comp_dir = root.attr_value(gimli::DW_AT_comp_dir)?;
|
||||
let comp_name = root.attr_value(gimli::DW_AT_name)?;
|
||||
let out_comp_dir = clone_attr_string(
|
||||
comp_dir.as_ref().expect("comp_dir"),
|
||||
gimli::DW_FORM_strp,
|
||||
debug_str,
|
||||
out_strings,
|
||||
)?;
|
||||
let out_comp_name = clone_attr_string(
|
||||
comp_name.as_ref().expect("comp_name"),
|
||||
gimli::DW_FORM_strp,
|
||||
debug_str,
|
||||
out_strings,
|
||||
)?;
|
||||
|
||||
let program = debug_line.program(
|
||||
offset,
|
||||
unit.header.address_size(),
|
||||
comp_dir.and_then(|val| val.string_value(&debug_str)),
|
||||
comp_name.and_then(|val| val.string_value(&debug_str)),
|
||||
);
|
||||
if let Ok(program) = program {
|
||||
let header = program.header();
|
||||
assert_le!(header.version(), 4, "not supported 5");
|
||||
let line_encoding = LineEncoding {
|
||||
minimum_instruction_length: header.minimum_instruction_length(),
|
||||
maximum_operations_per_instruction: header.maximum_operations_per_instruction(),
|
||||
default_is_stmt: header.default_is_stmt(),
|
||||
line_base: header.line_base(),
|
||||
line_range: header.line_range(),
|
||||
};
|
||||
let mut out_program = write::LineProgram::new(
|
||||
out_encoding,
|
||||
line_encoding,
|
||||
out_comp_dir,
|
||||
out_comp_name,
|
||||
None,
|
||||
);
|
||||
let mut dirs = Vec::new();
|
||||
dirs.push(out_program.default_directory());
|
||||
for dir_attr in header.include_directories() {
|
||||
let dir_id = out_program.add_directory(clone_attr_string(
|
||||
dir_attr,
|
||||
gimli::DW_FORM_string,
|
||||
debug_str,
|
||||
out_strings,
|
||||
)?);
|
||||
dirs.push(dir_id);
|
||||
}
|
||||
let mut files = Vec::new();
|
||||
for file_entry in header.file_names() {
|
||||
let dir_id = dirs[file_entry.directory_index() as usize];
|
||||
let file_id = out_program.add_file(
|
||||
clone_attr_string(
|
||||
&file_entry.path_name(),
|
||||
gimli::DW_FORM_string,
|
||||
debug_str,
|
||||
out_strings,
|
||||
)?,
|
||||
dir_id,
|
||||
None,
|
||||
);
|
||||
files.push(file_id);
|
||||
}
|
||||
|
||||
let mut rows = program.rows();
|
||||
let mut saved_rows = BTreeMap::new();
|
||||
let mut state = ReadLineProgramState::SequenceEnded;
|
||||
while let Some((_header, row)) = rows.next_row()? {
|
||||
if state == ReadLineProgramState::IgnoreSequence {
|
||||
if row.end_sequence() {
|
||||
state = ReadLineProgramState::SequenceEnded;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let saved_row = if row.end_sequence() {
|
||||
state = ReadLineProgramState::SequenceEnded;
|
||||
SavedLineProgramRow::EndOfSequence(row.address())
|
||||
} else {
|
||||
if state == ReadLineProgramState::SequenceEnded {
|
||||
// Discard sequences for non-existent code.
|
||||
if row.address() == 0 {
|
||||
state = ReadLineProgramState::IgnoreSequence;
|
||||
continue;
|
||||
}
|
||||
state = ReadLineProgramState::ReadSequence;
|
||||
}
|
||||
SavedLineProgramRow::Normal {
|
||||
address: row.address(),
|
||||
op_index: row.op_index(),
|
||||
file_index: row.file_index(),
|
||||
line: row.line().unwrap_or(0),
|
||||
column: match row.column() {
|
||||
gimli::ColumnType::LeftEdge => 0,
|
||||
gimli::ColumnType::Column(val) => val,
|
||||
},
|
||||
discriminator: row.discriminator(),
|
||||
is_stmt: row.is_stmt(),
|
||||
basic_block: row.basic_block(),
|
||||
prologue_end: row.prologue_end(),
|
||||
epilogue_begin: row.epilogue_begin(),
|
||||
isa: row.isa(),
|
||||
}
|
||||
};
|
||||
saved_rows.insert(row.address(), saved_row);
|
||||
}
|
||||
|
||||
let saved_rows = Vec::from_iter(saved_rows.into_iter());
|
||||
for (i, map) in addr_tr.map() {
|
||||
if map.len == 0 {
|
||||
continue; // no code generated
|
||||
}
|
||||
let symbol = i.index();
|
||||
let base_addr = map.offset;
|
||||
out_program.begin_sequence(Some(write::Address::Symbol { symbol, addend: 0 }));
|
||||
// TODO track and place function declaration line here
|
||||
let mut last_address = None;
|
||||
for addr_map in map.addresses.iter() {
|
||||
let saved_row = match saved_rows.binary_search_by_key(&addr_map.wasm, |i| i.0) {
|
||||
Ok(i) => Some(&saved_rows[i].1),
|
||||
Err(i) => {
|
||||
if i > 0 {
|
||||
Some(&saved_rows[i - 1].1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Some(SavedLineProgramRow::Normal {
|
||||
address,
|
||||
op_index,
|
||||
file_index,
|
||||
line,
|
||||
column,
|
||||
discriminator,
|
||||
is_stmt,
|
||||
basic_block,
|
||||
prologue_end,
|
||||
epilogue_begin,
|
||||
isa,
|
||||
}) = saved_row
|
||||
{
|
||||
// Ignore duplicates
|
||||
if Some(*address) != last_address {
|
||||
let address_offset = if last_address.is_none() {
|
||||
// Extend first entry to the function declaration
|
||||
// TODO use the function declaration line instead
|
||||
0
|
||||
} else {
|
||||
(addr_map.generated - base_addr) as u64
|
||||
};
|
||||
out_program.row().address_offset = address_offset;
|
||||
out_program.row().op_index = *op_index;
|
||||
out_program.row().file = files[(file_index - 1) as usize];
|
||||
out_program.row().line = *line;
|
||||
out_program.row().column = *column;
|
||||
out_program.row().discriminator = *discriminator;
|
||||
out_program.row().is_statement = *is_stmt;
|
||||
out_program.row().basic_block = *basic_block;
|
||||
out_program.row().prologue_end = *prologue_end;
|
||||
out_program.row().epilogue_begin = *epilogue_begin;
|
||||
out_program.row().isa = *isa;
|
||||
out_program.generate_row();
|
||||
last_address = Some(*address);
|
||||
}
|
||||
}
|
||||
}
|
||||
let end_addr = (map.offset + map.len - 1) as u64;
|
||||
out_program.end_sequence(end_addr);
|
||||
}
|
||||
Ok((out_program, offset, files))
|
||||
} else {
|
||||
Err(TransformError("Valid line program not found").into())
|
||||
}
|
||||
}
|
||||
122
crates/debug/src/transform/mod.rs
Normal file
122
crates/debug/src/transform/mod.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
use self::refs::DebugInfoRefsMap;
|
||||
use self::simulate::generate_simulated_dwarf;
|
||||
use self::unit::clone_unit;
|
||||
use crate::gc::build_dependencies;
|
||||
use crate::DebugInfoData;
|
||||
use anyhow::Error;
|
||||
use gimli::{
|
||||
write, DebugAddr, DebugAddrBase, DebugLine, DebugStr, LocationLists, RangeLists,
|
||||
UnitSectionOffset,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
use thiserror::Error;
|
||||
use wasmtime_environ::isa::TargetFrontendConfig;
|
||||
use wasmtime_environ::{ModuleAddressMap, ModuleVmctxInfo, ValueLabelsRanges};
|
||||
|
||||
pub use address_transform::AddressTransform;
|
||||
|
||||
mod address_transform;
|
||||
mod attr;
|
||||
mod expression;
|
||||
mod line_program;
|
||||
mod range_info_builder;
|
||||
mod refs;
|
||||
mod simulate;
|
||||
mod unit;
|
||||
mod utils;
|
||||
|
||||
pub(crate) trait Reader: gimli::Reader<Offset = usize> {}
|
||||
|
||||
impl<'input, Endian> Reader for gimli::EndianSlice<'input, Endian> where Endian: gimli::Endianity {}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Debug info transform error: {0}")]
|
||||
pub struct TransformError(&'static str);
|
||||
|
||||
pub(crate) struct DebugInputContext<'a, R>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
debug_str: &'a DebugStr<R>,
|
||||
debug_line: &'a DebugLine<R>,
|
||||
debug_addr: &'a DebugAddr<R>,
|
||||
debug_addr_base: DebugAddrBase<R::Offset>,
|
||||
rnglists: &'a RangeLists<R>,
|
||||
loclists: &'a LocationLists<R>,
|
||||
reachable: &'a HashSet<UnitSectionOffset>,
|
||||
}
|
||||
|
||||
pub fn transform_dwarf(
|
||||
target_config: TargetFrontendConfig,
|
||||
di: &DebugInfoData,
|
||||
at: &ModuleAddressMap,
|
||||
vmctx_info: &ModuleVmctxInfo,
|
||||
ranges: &ValueLabelsRanges,
|
||||
) -> Result<write::Dwarf, Error> {
|
||||
let addr_tr = AddressTransform::new(at, &di.wasm_file);
|
||||
let reachable = build_dependencies(&di.dwarf, &addr_tr)?.get_reachable();
|
||||
|
||||
let context = DebugInputContext {
|
||||
debug_str: &di.dwarf.debug_str,
|
||||
debug_line: &di.dwarf.debug_line,
|
||||
debug_addr: &di.dwarf.debug_addr,
|
||||
debug_addr_base: DebugAddrBase(0),
|
||||
rnglists: &di.dwarf.ranges,
|
||||
loclists: &di.dwarf.locations,
|
||||
reachable: &reachable,
|
||||
};
|
||||
|
||||
let out_encoding = gimli::Encoding {
|
||||
format: gimli::Format::Dwarf32,
|
||||
// TODO: this should be configurable
|
||||
// macOS doesn't seem to support DWARF > 3
|
||||
version: 3,
|
||||
address_size: target_config.pointer_bytes(),
|
||||
};
|
||||
|
||||
let mut out_strings = write::StringTable::default();
|
||||
let mut out_units = write::UnitTable::default();
|
||||
|
||||
let out_line_strings = write::LineStringTable::default();
|
||||
let mut pending_di_refs = Vec::new();
|
||||
let mut di_ref_map = DebugInfoRefsMap::new();
|
||||
|
||||
let mut translated = HashSet::new();
|
||||
let mut iter = di.dwarf.debug_info.units();
|
||||
while let Some(header) = iter.next().unwrap_or(None) {
|
||||
let unit = di.dwarf.unit(header)?;
|
||||
if let Some((id, ref_map, pending_refs)) = clone_unit(
|
||||
unit,
|
||||
&context,
|
||||
&addr_tr,
|
||||
&ranges,
|
||||
out_encoding,
|
||||
&vmctx_info,
|
||||
&mut out_units,
|
||||
&mut out_strings,
|
||||
&mut translated,
|
||||
)? {
|
||||
di_ref_map.insert(&header, id, ref_map);
|
||||
pending_di_refs.push((id, pending_refs));
|
||||
}
|
||||
}
|
||||
di_ref_map.patch(pending_di_refs.into_iter(), &mut out_units);
|
||||
|
||||
generate_simulated_dwarf(
|
||||
&addr_tr,
|
||||
di,
|
||||
&vmctx_info,
|
||||
&ranges,
|
||||
&translated,
|
||||
out_encoding,
|
||||
&mut out_units,
|
||||
&mut out_strings,
|
||||
)?;
|
||||
|
||||
Ok(write::Dwarf {
|
||||
units: out_units,
|
||||
line_programs: vec![],
|
||||
line_strings: out_line_strings,
|
||||
strings: out_strings,
|
||||
})
|
||||
}
|
||||
219
crates/debug/src/transform/range_info_builder.rs
Normal file
219
crates/debug/src/transform/range_info_builder.rs
Normal file
@@ -0,0 +1,219 @@
|
||||
use super::address_transform::AddressTransform;
|
||||
use super::{DebugInputContext, Reader};
|
||||
use anyhow::Error;
|
||||
use gimli::{write, AttributeValue, DebuggingInformationEntry, RangeListsOffset};
|
||||
use more_asserts::assert_lt;
|
||||
use wasmtime_environ::entity::EntityRef;
|
||||
use wasmtime_environ::wasm::DefinedFuncIndex;
|
||||
|
||||
pub(crate) enum RangeInfoBuilder {
|
||||
Undefined,
|
||||
Position(u64),
|
||||
Ranges(Vec<(u64, u64)>),
|
||||
Function(DefinedFuncIndex),
|
||||
}
|
||||
|
||||
impl RangeInfoBuilder {
|
||||
pub(crate) fn from<R>(
|
||||
entry: &DebuggingInformationEntry<R>,
|
||||
context: &DebugInputContext<R>,
|
||||
unit_encoding: gimli::Encoding,
|
||||
cu_low_pc: u64,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
if let Some(AttributeValue::RangeListsRef(r)) = entry.attr_value(gimli::DW_AT_ranges)? {
|
||||
return RangeInfoBuilder::from_ranges_ref(r, context, unit_encoding, cu_low_pc);
|
||||
};
|
||||
|
||||
let low_pc =
|
||||
if let Some(AttributeValue::Addr(addr)) = entry.attr_value(gimli::DW_AT_low_pc)? {
|
||||
addr
|
||||
} else {
|
||||
return Ok(RangeInfoBuilder::Undefined);
|
||||
};
|
||||
|
||||
Ok(
|
||||
if let Some(AttributeValue::Udata(u)) = entry.attr_value(gimli::DW_AT_high_pc)? {
|
||||
RangeInfoBuilder::Ranges(vec![(low_pc, low_pc + u)])
|
||||
} else {
|
||||
RangeInfoBuilder::Position(low_pc)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn from_ranges_ref<R>(
|
||||
ranges: RangeListsOffset,
|
||||
context: &DebugInputContext<R>,
|
||||
unit_encoding: gimli::Encoding,
|
||||
cu_low_pc: u64,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
let mut ranges = context.rnglists.ranges(
|
||||
ranges,
|
||||
unit_encoding,
|
||||
cu_low_pc,
|
||||
&context.debug_addr,
|
||||
context.debug_addr_base,
|
||||
)?;
|
||||
let mut result = Vec::new();
|
||||
while let Some(range) = ranges.next()? {
|
||||
if range.begin >= range.end {
|
||||
// ignore empty ranges
|
||||
}
|
||||
result.push((range.begin, range.end));
|
||||
}
|
||||
|
||||
Ok(if result.is_empty() {
|
||||
RangeInfoBuilder::Undefined
|
||||
} else {
|
||||
RangeInfoBuilder::Ranges(result)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn from_subprogram_die<R>(
|
||||
entry: &DebuggingInformationEntry<R>,
|
||||
context: &DebugInputContext<R>,
|
||||
unit_encoding: gimli::Encoding,
|
||||
addr_tr: &AddressTransform,
|
||||
cu_low_pc: u64,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
let addr =
|
||||
if let Some(AttributeValue::Addr(addr)) = entry.attr_value(gimli::DW_AT_low_pc)? {
|
||||
addr
|
||||
} else if let Some(AttributeValue::RangeListsRef(r)) =
|
||||
entry.attr_value(gimli::DW_AT_ranges)?
|
||||
{
|
||||
let mut ranges = context.rnglists.ranges(
|
||||
r,
|
||||
unit_encoding,
|
||||
cu_low_pc,
|
||||
&context.debug_addr,
|
||||
context.debug_addr_base,
|
||||
)?;
|
||||
if let Some(range) = ranges.next()? {
|
||||
range.begin
|
||||
} else {
|
||||
return Ok(RangeInfoBuilder::Undefined);
|
||||
}
|
||||
} else {
|
||||
return Ok(RangeInfoBuilder::Undefined);
|
||||
};
|
||||
|
||||
let index = addr_tr.find_func_index(addr);
|
||||
if index.is_none() {
|
||||
return Ok(RangeInfoBuilder::Undefined);
|
||||
}
|
||||
Ok(RangeInfoBuilder::Function(index.unwrap()))
|
||||
}
|
||||
|
||||
pub(crate) fn build(
|
||||
&self,
|
||||
addr_tr: &AddressTransform,
|
||||
out_unit: &mut write::Unit,
|
||||
current_scope_id: write::UnitEntryId,
|
||||
) {
|
||||
match self {
|
||||
RangeInfoBuilder::Undefined => (),
|
||||
RangeInfoBuilder::Position(pc) => {
|
||||
let addr = addr_tr
|
||||
.translate(*pc)
|
||||
.unwrap_or(write::Address::Constant(0));
|
||||
let current_scope = out_unit.get_mut(current_scope_id);
|
||||
current_scope.set(gimli::DW_AT_low_pc, write::AttributeValue::Address(addr));
|
||||
}
|
||||
RangeInfoBuilder::Ranges(ranges) => {
|
||||
let mut result = Vec::new();
|
||||
for (begin, end) in ranges {
|
||||
for tr in addr_tr.translate_ranges(*begin, *end) {
|
||||
if tr.1 == 0 {
|
||||
// Ignore empty range
|
||||
continue;
|
||||
}
|
||||
result.push(tr);
|
||||
}
|
||||
}
|
||||
if result.len() != 1 {
|
||||
let range_list = result
|
||||
.iter()
|
||||
.map(|tr| write::Range::StartLength {
|
||||
begin: tr.0,
|
||||
length: tr.1,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let range_list_id = out_unit.ranges.add(write::RangeList(range_list));
|
||||
let current_scope = out_unit.get_mut(current_scope_id);
|
||||
current_scope.set(
|
||||
gimli::DW_AT_ranges,
|
||||
write::AttributeValue::RangeListRef(range_list_id),
|
||||
);
|
||||
} else {
|
||||
let current_scope = out_unit.get_mut(current_scope_id);
|
||||
current_scope.set(
|
||||
gimli::DW_AT_low_pc,
|
||||
write::AttributeValue::Address(result[0].0),
|
||||
);
|
||||
current_scope.set(
|
||||
gimli::DW_AT_high_pc,
|
||||
write::AttributeValue::Udata(result[0].1),
|
||||
);
|
||||
}
|
||||
}
|
||||
RangeInfoBuilder::Function(index) => {
|
||||
let range = addr_tr.func_range(*index);
|
||||
let symbol = index.index();
|
||||
let addr = write::Address::Symbol {
|
||||
symbol,
|
||||
addend: range.0 as i64,
|
||||
};
|
||||
let len = (range.1 - range.0) as u64;
|
||||
let current_scope = out_unit.get_mut(current_scope_id);
|
||||
current_scope.set(gimli::DW_AT_low_pc, write::AttributeValue::Address(addr));
|
||||
current_scope.set(gimli::DW_AT_high_pc, write::AttributeValue::Udata(len));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_ranges(&self, addr_tr: &AddressTransform) -> Vec<(u64, u64)> {
|
||||
match self {
|
||||
RangeInfoBuilder::Undefined | RangeInfoBuilder::Position(_) => vec![],
|
||||
RangeInfoBuilder::Ranges(ranges) => ranges.clone(),
|
||||
RangeInfoBuilder::Function(index) => {
|
||||
let range = addr_tr.func_source_range(*index);
|
||||
vec![(range.0, range.1)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build_ranges(
|
||||
&self,
|
||||
addr_tr: &AddressTransform,
|
||||
out_range_lists: &mut write::RangeListTable,
|
||||
) -> write::RangeListId {
|
||||
if let RangeInfoBuilder::Ranges(ranges) = self {
|
||||
let mut range_list = Vec::new();
|
||||
for (begin, end) in ranges {
|
||||
assert_lt!(begin, end);
|
||||
for tr in addr_tr.translate_ranges(*begin, *end) {
|
||||
if tr.1 == 0 {
|
||||
// Ignore empty range
|
||||
continue;
|
||||
}
|
||||
range_list.push(write::Range::StartLength {
|
||||
begin: tr.0,
|
||||
length: tr.1,
|
||||
});
|
||||
}
|
||||
}
|
||||
out_range_lists.add(write::RangeList(range_list))
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
111
crates/debug/src/transform/refs.rs
Normal file
111
crates/debug/src/transform/refs.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
//! Helper utils for tracking and patching intra unit or section references.
|
||||
|
||||
use gimli::write;
|
||||
use gimli::{CompilationUnitHeader, DebugInfoOffset, Reader, UnitOffset};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Stores compiled unit references: UnitEntryId+DwAt denotes a patch location
|
||||
/// and UnitOffset is a location in original DWARF.
|
||||
pub struct PendingUnitRefs {
|
||||
refs: Vec<(write::UnitEntryId, gimli::DwAt, UnitOffset)>,
|
||||
}
|
||||
|
||||
impl PendingUnitRefs {
|
||||
pub fn new() -> Self {
|
||||
Self { refs: Vec::new() }
|
||||
}
|
||||
pub fn insert(&mut self, entry_id: write::UnitEntryId, attr: gimli::DwAt, offset: UnitOffset) {
|
||||
self.refs.push((entry_id, attr, offset));
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores .debug_info references: UnitEntryId+DwAt denotes a patch location
|
||||
/// and DebugInfoOffset is a location in original DWARF.
|
||||
pub struct PendingDebugInfoRefs {
|
||||
refs: Vec<(write::UnitEntryId, gimli::DwAt, DebugInfoOffset)>,
|
||||
}
|
||||
|
||||
impl PendingDebugInfoRefs {
|
||||
pub fn new() -> Self {
|
||||
Self { refs: Vec::new() }
|
||||
}
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
entry_id: write::UnitEntryId,
|
||||
attr: gimli::DwAt,
|
||||
offset: DebugInfoOffset,
|
||||
) {
|
||||
self.refs.push((entry_id, attr, offset));
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores map between read and written references of DWARF entries of
|
||||
/// a compiled unit.
|
||||
pub struct UnitRefsMap {
|
||||
map: HashMap<UnitOffset, write::UnitEntryId>,
|
||||
}
|
||||
|
||||
impl UnitRefsMap {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn insert(&mut self, offset: UnitOffset, entry_id: write::UnitEntryId) {
|
||||
self.map.insert(offset, entry_id);
|
||||
}
|
||||
pub fn patch(&self, refs: PendingUnitRefs, comp_unit: &mut write::Unit) {
|
||||
for (die_id, attr_name, offset) in refs.refs {
|
||||
let die = comp_unit.get_mut(die_id);
|
||||
if let Some(unit_id) = self.map.get(&offset) {
|
||||
die.set(attr_name, write::AttributeValue::ThisUnitEntryRef(*unit_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores map between read and written references of DWARF entries of
|
||||
/// the entire .debug_info.
|
||||
pub struct DebugInfoRefsMap {
|
||||
map: HashMap<DebugInfoOffset, (write::UnitId, write::UnitEntryId)>,
|
||||
}
|
||||
|
||||
impl DebugInfoRefsMap {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn insert<R>(
|
||||
&mut self,
|
||||
unit: &CompilationUnitHeader<R>,
|
||||
unit_id: write::UnitId,
|
||||
unit_map: UnitRefsMap,
|
||||
) where
|
||||
R: Reader<Offset = usize>,
|
||||
{
|
||||
self.map
|
||||
.extend(unit_map.map.into_iter().map(|(off, entry_id)| {
|
||||
let off = off.to_debug_info_offset(unit);
|
||||
(off, (unit_id, entry_id))
|
||||
}));
|
||||
}
|
||||
pub fn patch(
|
||||
&self,
|
||||
refs: impl Iterator<Item = (write::UnitId, PendingDebugInfoRefs)>,
|
||||
units: &mut write::UnitTable,
|
||||
) {
|
||||
for (id, refs) in refs {
|
||||
let unit = units.get_mut(id);
|
||||
for (die_id, attr_name, offset) in refs.refs {
|
||||
let die = unit.get_mut(die_id);
|
||||
if let Some((id, entry_id)) = self.map.get(&offset) {
|
||||
die.set(
|
||||
attr_name,
|
||||
write::AttributeValue::AnyUnitEntryRef((*id, *entry_id)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
373
crates/debug/src/transform/simulate.rs
Normal file
373
crates/debug/src/transform/simulate.rs
Normal file
@@ -0,0 +1,373 @@
|
||||
use super::expression::{CompiledExpression, FunctionFrameInfo};
|
||||
use super::utils::{add_internal_types, append_vmctx_info, get_function_frame_info};
|
||||
use super::AddressTransform;
|
||||
use crate::read_debuginfo::WasmFileInfo;
|
||||
use anyhow::Error;
|
||||
use gimli::write;
|
||||
use gimli::{self, LineEncoding};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::PathBuf;
|
||||
use wasmtime_environ::entity::EntityRef;
|
||||
use wasmtime_environ::wasm::get_vmctx_value_label;
|
||||
use wasmtime_environ::{ModuleVmctxInfo, ValueLabelsRanges};
|
||||
|
||||
pub use crate::read_debuginfo::{DebugInfoData, FunctionMetadata, WasmType};
|
||||
|
||||
const PRODUCER_NAME: &str = "wasmtime";
|
||||
|
||||
fn generate_line_info(
|
||||
addr_tr: &AddressTransform,
|
||||
translated: &HashSet<u32>,
|
||||
out_encoding: gimli::Encoding,
|
||||
w: &WasmFileInfo,
|
||||
comp_dir_id: write::StringId,
|
||||
name_id: write::StringId,
|
||||
name: &str,
|
||||
) -> Result<write::LineProgram, Error> {
|
||||
let out_comp_dir = write::LineString::StringRef(comp_dir_id);
|
||||
let out_comp_name = write::LineString::StringRef(name_id);
|
||||
|
||||
let line_encoding = LineEncoding::default();
|
||||
|
||||
let mut out_program = write::LineProgram::new(
|
||||
out_encoding,
|
||||
line_encoding,
|
||||
out_comp_dir,
|
||||
out_comp_name,
|
||||
None,
|
||||
);
|
||||
|
||||
let file_index = out_program.add_file(
|
||||
write::LineString::String(name.as_bytes().to_vec()),
|
||||
out_program.default_directory(),
|
||||
None,
|
||||
);
|
||||
|
||||
for (i, map) in addr_tr.map() {
|
||||
let symbol = i.index();
|
||||
if translated.contains(&(symbol as u32)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let base_addr = map.offset;
|
||||
out_program.begin_sequence(Some(write::Address::Symbol { symbol, addend: 0 }));
|
||||
for addr_map in map.addresses.iter() {
|
||||
let address_offset = (addr_map.generated - base_addr) as u64;
|
||||
out_program.row().address_offset = address_offset;
|
||||
out_program.row().op_index = 0;
|
||||
out_program.row().file = file_index;
|
||||
let wasm_offset = w.code_section_offset + addr_map.wasm as u64;
|
||||
out_program.row().line = wasm_offset;
|
||||
out_program.row().column = 0;
|
||||
out_program.row().discriminator = 1;
|
||||
out_program.row().is_statement = true;
|
||||
out_program.row().basic_block = false;
|
||||
out_program.row().prologue_end = false;
|
||||
out_program.row().epilogue_begin = false;
|
||||
out_program.row().isa = 0;
|
||||
out_program.generate_row();
|
||||
}
|
||||
let end_addr = (map.offset + map.len - 1) as u64;
|
||||
out_program.end_sequence(end_addr);
|
||||
}
|
||||
|
||||
Ok(out_program)
|
||||
}
|
||||
|
||||
fn autogenerate_dwarf_wasm_path(di: &DebugInfoData) -> PathBuf {
|
||||
let module_name = di
|
||||
.name_section
|
||||
.as_ref()
|
||||
.and_then(|ns| ns.module_name.to_owned())
|
||||
.unwrap_or_else(|| unsafe {
|
||||
static mut GEN_ID: u32 = 0;
|
||||
GEN_ID += 1;
|
||||
format!("<gen-{}>", GEN_ID)
|
||||
});
|
||||
let path = format!("/<wasm-module>/{}.wasm", module_name);
|
||||
PathBuf::from(path)
|
||||
}
|
||||
|
||||
struct WasmTypesDieRefs {
|
||||
vmctx: write::UnitEntryId,
|
||||
i32: write::UnitEntryId,
|
||||
i64: write::UnitEntryId,
|
||||
f32: write::UnitEntryId,
|
||||
f64: write::UnitEntryId,
|
||||
}
|
||||
|
||||
fn add_wasm_types(
|
||||
unit: &mut write::Unit,
|
||||
root_id: write::UnitEntryId,
|
||||
out_strings: &mut write::StringTable,
|
||||
vmctx_info: &ModuleVmctxInfo,
|
||||
) -> WasmTypesDieRefs {
|
||||
let (_wp_die_id, vmctx_die_id) = add_internal_types(unit, root_id, out_strings, vmctx_info);
|
||||
|
||||
macro_rules! def_type {
|
||||
($id:literal, $size:literal, $enc:path) => {{
|
||||
let die_id = unit.add(root_id, gimli::DW_TAG_base_type);
|
||||
let die = unit.get_mut(die_id);
|
||||
die.set(
|
||||
gimli::DW_AT_name,
|
||||
write::AttributeValue::StringRef(out_strings.add($id)),
|
||||
);
|
||||
die.set(gimli::DW_AT_byte_size, write::AttributeValue::Data1($size));
|
||||
die.set(gimli::DW_AT_encoding, write::AttributeValue::Encoding($enc));
|
||||
die_id
|
||||
}};
|
||||
}
|
||||
|
||||
let i32_die_id = def_type!("i32", 4, gimli::DW_ATE_signed);
|
||||
let i64_die_id = def_type!("i64", 8, gimli::DW_ATE_signed);
|
||||
let f32_die_id = def_type!("f32", 4, gimli::DW_ATE_float);
|
||||
let f64_die_id = def_type!("f64", 8, gimli::DW_ATE_float);
|
||||
|
||||
WasmTypesDieRefs {
|
||||
vmctx: vmctx_die_id,
|
||||
i32: i32_die_id,
|
||||
i64: i64_die_id,
|
||||
f32: f32_die_id,
|
||||
f64: f64_die_id,
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_var_type(
|
||||
index: usize,
|
||||
wasm_types: &WasmTypesDieRefs,
|
||||
func_meta: &FunctionMetadata,
|
||||
) -> Option<(write::UnitEntryId, bool)> {
|
||||
let (ty, is_param) = if index < func_meta.params.len() {
|
||||
(func_meta.params[index], true)
|
||||
} else {
|
||||
let mut i = (index - func_meta.params.len()) as u32;
|
||||
let mut j = 0;
|
||||
while j < func_meta.locals.len() && i >= func_meta.locals[j].0 {
|
||||
i -= func_meta.locals[j].0;
|
||||
j += 1;
|
||||
}
|
||||
if j >= func_meta.locals.len() {
|
||||
// Ignore the var index out of bound.
|
||||
return None;
|
||||
}
|
||||
(func_meta.locals[j].1, false)
|
||||
};
|
||||
let type_die_id = match ty {
|
||||
WasmType::I32 => wasm_types.i32,
|
||||
WasmType::I64 => wasm_types.i64,
|
||||
WasmType::F32 => wasm_types.f32,
|
||||
WasmType::F64 => wasm_types.f64,
|
||||
_ => {
|
||||
// Ignore unsupported types.
|
||||
return None;
|
||||
}
|
||||
};
|
||||
Some((type_die_id, is_param))
|
||||
}
|
||||
|
||||
fn generate_vars(
|
||||
unit: &mut write::Unit,
|
||||
die_id: write::UnitEntryId,
|
||||
addr_tr: &AddressTransform,
|
||||
frame_info: &FunctionFrameInfo,
|
||||
scope_ranges: &[(u64, u64)],
|
||||
wasm_types: &WasmTypesDieRefs,
|
||||
func_meta: &FunctionMetadata,
|
||||
locals_names: Option<&HashMap<u32, String>>,
|
||||
out_strings: &mut write::StringTable,
|
||||
) {
|
||||
let vmctx_label = get_vmctx_value_label();
|
||||
|
||||
// Normalize order of ValueLabelsRanges keys to have reproducable results.
|
||||
let mut vars = frame_info.value_ranges.keys().collect::<Vec<_>>();
|
||||
vars.sort_by(|a, b| a.index().cmp(&b.index()));
|
||||
|
||||
for label in vars {
|
||||
if label.index() == vmctx_label.index() {
|
||||
append_vmctx_info(
|
||||
unit,
|
||||
die_id,
|
||||
wasm_types.vmctx,
|
||||
addr_tr,
|
||||
Some(frame_info),
|
||||
scope_ranges,
|
||||
out_strings,
|
||||
)
|
||||
.expect("append_vmctx_info success");
|
||||
} else {
|
||||
let var_index = label.index();
|
||||
let (type_die_id, is_param) =
|
||||
if let Some(result) = resolve_var_type(var_index, wasm_types, func_meta) {
|
||||
result
|
||||
} else {
|
||||
// Skipping if type of local cannot be detected.
|
||||
continue;
|
||||
};
|
||||
|
||||
let loc_list_id = {
|
||||
let endian = gimli::RunTimeEndian::Little;
|
||||
|
||||
let expr = CompiledExpression::from_label(*label);
|
||||
let mut locs = Vec::new();
|
||||
for (begin, length, data) in
|
||||
expr.build_with_locals(scope_ranges, addr_tr, Some(frame_info), endian)
|
||||
{
|
||||
locs.push(write::Location::StartLength {
|
||||
begin,
|
||||
length,
|
||||
data,
|
||||
});
|
||||
}
|
||||
unit.locations.add(write::LocationList(locs))
|
||||
};
|
||||
|
||||
let var_id = unit.add(
|
||||
die_id,
|
||||
if is_param {
|
||||
gimli::DW_TAG_formal_parameter
|
||||
} else {
|
||||
gimli::DW_TAG_variable
|
||||
},
|
||||
);
|
||||
let var = unit.get_mut(var_id);
|
||||
|
||||
let name_id = match locals_names.and_then(|m| m.get(&(var_index as u32))) {
|
||||
Some(n) => out_strings.add(n.to_owned()),
|
||||
None => out_strings.add(format!("var{}", var_index)),
|
||||
};
|
||||
|
||||
var.set(gimli::DW_AT_name, write::AttributeValue::StringRef(name_id));
|
||||
var.set(
|
||||
gimli::DW_AT_type,
|
||||
write::AttributeValue::ThisUnitEntryRef(type_die_id),
|
||||
);
|
||||
var.set(
|
||||
gimli::DW_AT_location,
|
||||
write::AttributeValue::LocationListRef(loc_list_id),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_simulated_dwarf(
|
||||
addr_tr: &AddressTransform,
|
||||
di: &DebugInfoData,
|
||||
vmctx_info: &ModuleVmctxInfo,
|
||||
ranges: &ValueLabelsRanges,
|
||||
translated: &HashSet<u32>,
|
||||
out_encoding: gimli::Encoding,
|
||||
out_units: &mut write::UnitTable,
|
||||
out_strings: &mut write::StringTable,
|
||||
) -> Result<(), Error> {
|
||||
let path = di
|
||||
.wasm_file
|
||||
.path
|
||||
.to_owned()
|
||||
.unwrap_or_else(|| autogenerate_dwarf_wasm_path(di));
|
||||
|
||||
let (func_names, locals_names) = if let Some(ref name_section) = di.name_section {
|
||||
(
|
||||
Some(&name_section.func_names),
|
||||
Some(&name_section.locals_names),
|
||||
)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let (unit, root_id, name_id) = {
|
||||
let comp_dir_id = out_strings.add(path.parent().expect("path dir").to_str().unwrap());
|
||||
let name = path.file_name().expect("path name").to_str().unwrap();
|
||||
let name_id = out_strings.add(name);
|
||||
|
||||
let out_program = generate_line_info(
|
||||
addr_tr,
|
||||
translated,
|
||||
out_encoding,
|
||||
&di.wasm_file,
|
||||
comp_dir_id,
|
||||
name_id,
|
||||
name,
|
||||
)?;
|
||||
|
||||
let unit_id = out_units.add(write::Unit::new(out_encoding, out_program));
|
||||
let unit = out_units.get_mut(unit_id);
|
||||
|
||||
let root_id = unit.root();
|
||||
let root = unit.get_mut(root_id);
|
||||
|
||||
let id = out_strings.add(PRODUCER_NAME);
|
||||
root.set(gimli::DW_AT_producer, write::AttributeValue::StringRef(id));
|
||||
root.set(gimli::DW_AT_name, write::AttributeValue::StringRef(name_id));
|
||||
root.set(
|
||||
gimli::DW_AT_stmt_list,
|
||||
write::AttributeValue::LineProgramRef,
|
||||
);
|
||||
root.set(
|
||||
gimli::DW_AT_comp_dir,
|
||||
write::AttributeValue::StringRef(comp_dir_id),
|
||||
);
|
||||
(unit, root_id, name_id)
|
||||
};
|
||||
|
||||
let wasm_types = add_wasm_types(unit, root_id, out_strings, vmctx_info);
|
||||
|
||||
for (i, map) in addr_tr.map().iter() {
|
||||
let index = i.index();
|
||||
if translated.contains(&(index as u32)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let start = map.offset as u64;
|
||||
let end = start + map.len as u64;
|
||||
let die_id = unit.add(root_id, gimli::DW_TAG_subprogram);
|
||||
let die = unit.get_mut(die_id);
|
||||
die.set(
|
||||
gimli::DW_AT_low_pc,
|
||||
write::AttributeValue::Address(write::Address::Symbol {
|
||||
symbol: index,
|
||||
addend: start as i64,
|
||||
}),
|
||||
);
|
||||
die.set(
|
||||
gimli::DW_AT_high_pc,
|
||||
write::AttributeValue::Udata((end - start) as u64),
|
||||
);
|
||||
|
||||
let id = match func_names.and_then(|m| m.get(&(index as u32))) {
|
||||
Some(n) => out_strings.add(n.to_owned()),
|
||||
None => out_strings.add(format!("wasm-function[{}]", index)),
|
||||
};
|
||||
|
||||
die.set(gimli::DW_AT_name, write::AttributeValue::StringRef(id));
|
||||
|
||||
die.set(
|
||||
gimli::DW_AT_decl_file,
|
||||
write::AttributeValue::StringRef(name_id),
|
||||
);
|
||||
|
||||
let f = addr_tr.map().get(i).unwrap();
|
||||
let f_start = f.addresses[0].wasm;
|
||||
let wasm_offset = di.wasm_file.code_section_offset + f_start as u64;
|
||||
die.set(
|
||||
gimli::DW_AT_decl_file,
|
||||
write::AttributeValue::Udata(wasm_offset),
|
||||
);
|
||||
|
||||
if let Some(frame_info) = get_function_frame_info(vmctx_info, i, ranges) {
|
||||
let source_range = addr_tr.func_source_range(i);
|
||||
generate_vars(
|
||||
unit,
|
||||
die_id,
|
||||
addr_tr,
|
||||
&frame_info,
|
||||
&[(source_range.0, source_range.1)],
|
||||
&wasm_types,
|
||||
&di.wasm_file.funcs[index],
|
||||
locals_names.and_then(|m| m.get(&(index as u32))),
|
||||
out_strings,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
362
crates/debug/src/transform/unit.rs
Normal file
362
crates/debug/src/transform/unit.rs
Normal file
@@ -0,0 +1,362 @@
|
||||
use super::address_transform::AddressTransform;
|
||||
use super::attr::{clone_die_attributes, FileAttributeContext};
|
||||
use super::expression::compile_expression;
|
||||
use super::line_program::clone_line_program;
|
||||
use super::range_info_builder::RangeInfoBuilder;
|
||||
use super::refs::{PendingDebugInfoRefs, PendingUnitRefs, UnitRefsMap};
|
||||
use super::utils::{add_internal_types, append_vmctx_info, get_function_frame_info};
|
||||
use super::{DebugInputContext, Reader, TransformError};
|
||||
use anyhow::Error;
|
||||
use gimli::write;
|
||||
use gimli::{AttributeValue, DebuggingInformationEntry, Unit};
|
||||
use std::collections::HashSet;
|
||||
use wasmtime_environ::entity::EntityRef;
|
||||
use wasmtime_environ::{ModuleVmctxInfo, ValueLabelsRanges};
|
||||
|
||||
struct InheritedAttr<T> {
|
||||
stack: Vec<(usize, T)>,
|
||||
}
|
||||
|
||||
impl<T> InheritedAttr<T> {
|
||||
fn new() -> Self {
|
||||
InheritedAttr { stack: Vec::new() }
|
||||
}
|
||||
|
||||
fn update(&mut self, depth: usize) {
|
||||
while !self.stack.is_empty() && self.stack.last().unwrap().0 >= depth {
|
||||
self.stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, depth: usize, value: T) {
|
||||
self.stack.push((depth, value));
|
||||
}
|
||||
|
||||
fn top(&self) -> Option<&T> {
|
||||
self.stack.last().map(|entry| &entry.1)
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.stack.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_base_type_name<R>(
|
||||
type_entry: &DebuggingInformationEntry<R>,
|
||||
unit: &Unit<R, R::Offset>,
|
||||
context: &DebugInputContext<R>,
|
||||
) -> Result<String, Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
// FIXME remove recursion.
|
||||
if let Some(AttributeValue::UnitRef(ref offset)) = type_entry.attr_value(gimli::DW_AT_type)? {
|
||||
let mut entries = unit.entries_at_offset(*offset)?;
|
||||
entries.next_entry()?;
|
||||
if let Some(die) = entries.current() {
|
||||
if let Some(AttributeValue::DebugStrRef(str_offset)) =
|
||||
die.attr_value(gimli::DW_AT_name)?
|
||||
{
|
||||
return Ok(String::from(
|
||||
context.debug_str.get_str(str_offset)?.to_string()?,
|
||||
));
|
||||
}
|
||||
match die.tag() {
|
||||
gimli::DW_TAG_const_type => {
|
||||
return Ok(format!("const {}", get_base_type_name(die, unit, context)?));
|
||||
}
|
||||
gimli::DW_TAG_pointer_type => {
|
||||
return Ok(format!("{}*", get_base_type_name(die, unit, context)?));
|
||||
}
|
||||
gimli::DW_TAG_reference_type => {
|
||||
return Ok(format!("{}&", get_base_type_name(die, unit, context)?));
|
||||
}
|
||||
gimli::DW_TAG_array_type => {
|
||||
return Ok(format!("{}[]", get_base_type_name(die, unit, context)?));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(String::from("??"))
|
||||
}
|
||||
|
||||
fn replace_pointer_type<R>(
|
||||
parent_id: write::UnitEntryId,
|
||||
comp_unit: &mut write::Unit,
|
||||
wp_die_id: write::UnitEntryId,
|
||||
entry: &DebuggingInformationEntry<R>,
|
||||
unit: &Unit<R, R::Offset>,
|
||||
context: &DebugInputContext<R>,
|
||||
out_strings: &mut write::StringTable,
|
||||
pending_die_refs: &mut PendingUnitRefs,
|
||||
) -> Result<write::UnitEntryId, Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
let die_id = comp_unit.add(parent_id, gimli::DW_TAG_structure_type);
|
||||
let die = comp_unit.get_mut(die_id);
|
||||
|
||||
let name = format!(
|
||||
"WebAssemblyPtrWrapper<{}>",
|
||||
get_base_type_name(entry, unit, context)?
|
||||
);
|
||||
die.set(
|
||||
gimli::DW_AT_name,
|
||||
write::AttributeValue::StringRef(out_strings.add(name.as_str())),
|
||||
);
|
||||
die.set(gimli::DW_AT_byte_size, write::AttributeValue::Data1(4));
|
||||
|
||||
let p_die_id = comp_unit.add(die_id, gimli::DW_TAG_template_type_parameter);
|
||||
let p_die = comp_unit.get_mut(p_die_id);
|
||||
p_die.set(
|
||||
gimli::DW_AT_name,
|
||||
write::AttributeValue::StringRef(out_strings.add("T")),
|
||||
);
|
||||
p_die.set(
|
||||
gimli::DW_AT_type,
|
||||
write::AttributeValue::ThisUnitEntryRef(wp_die_id),
|
||||
);
|
||||
if let Some(AttributeValue::UnitRef(ref offset)) = entry.attr_value(gimli::DW_AT_type)? {
|
||||
pending_die_refs.insert(p_die_id, gimli::DW_AT_type, *offset);
|
||||
}
|
||||
|
||||
let m_die_id = comp_unit.add(die_id, gimli::DW_TAG_member);
|
||||
let m_die = comp_unit.get_mut(m_die_id);
|
||||
m_die.set(
|
||||
gimli::DW_AT_name,
|
||||
write::AttributeValue::StringRef(out_strings.add("__ptr")),
|
||||
);
|
||||
m_die.set(
|
||||
gimli::DW_AT_type,
|
||||
write::AttributeValue::ThisUnitEntryRef(wp_die_id),
|
||||
);
|
||||
m_die.set(
|
||||
gimli::DW_AT_data_member_location,
|
||||
write::AttributeValue::Data1(0),
|
||||
);
|
||||
Ok(die_id)
|
||||
}
|
||||
|
||||
pub(crate) fn clone_unit<'a, R>(
|
||||
unit: Unit<R, R::Offset>,
|
||||
context: &DebugInputContext<R>,
|
||||
addr_tr: &'a AddressTransform,
|
||||
value_ranges: &'a ValueLabelsRanges,
|
||||
out_encoding: gimli::Encoding,
|
||||
module_info: &ModuleVmctxInfo,
|
||||
out_units: &mut write::UnitTable,
|
||||
out_strings: &mut write::StringTable,
|
||||
translated: &mut HashSet<u32>,
|
||||
) -> Result<Option<(write::UnitId, UnitRefsMap, PendingDebugInfoRefs)>, Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
let mut die_ref_map = UnitRefsMap::new();
|
||||
let mut pending_die_refs = PendingUnitRefs::new();
|
||||
let mut pending_di_refs = PendingDebugInfoRefs::new();
|
||||
let mut stack = Vec::new();
|
||||
|
||||
// Iterate over all of this compilation unit's entries.
|
||||
let mut entries = unit.entries();
|
||||
let (mut comp_unit, unit_id, file_map, cu_low_pc, wp_die_id, vmctx_die_id) =
|
||||
if let Some((depth_delta, entry)) = entries.next_dfs()? {
|
||||
assert_eq!(depth_delta, 0);
|
||||
let (out_line_program, debug_line_offset, file_map) = clone_line_program(
|
||||
&unit,
|
||||
entry,
|
||||
addr_tr,
|
||||
out_encoding,
|
||||
context.debug_str,
|
||||
context.debug_line,
|
||||
out_strings,
|
||||
)?;
|
||||
|
||||
if entry.tag() == gimli::DW_TAG_compile_unit {
|
||||
let unit_id = out_units.add(write::Unit::new(out_encoding, out_line_program));
|
||||
let comp_unit = out_units.get_mut(unit_id);
|
||||
|
||||
let root_id = comp_unit.root();
|
||||
die_ref_map.insert(entry.offset(), root_id);
|
||||
|
||||
let cu_low_pc = if let Some(AttributeValue::Addr(addr)) =
|
||||
entry.attr_value(gimli::DW_AT_low_pc)?
|
||||
{
|
||||
addr
|
||||
} else {
|
||||
// FIXME? return Err(TransformError("No low_pc for unit header").into());
|
||||
0
|
||||
};
|
||||
|
||||
clone_die_attributes(
|
||||
entry,
|
||||
context,
|
||||
addr_tr,
|
||||
None,
|
||||
unit.encoding(),
|
||||
comp_unit,
|
||||
root_id,
|
||||
None,
|
||||
None,
|
||||
cu_low_pc,
|
||||
out_strings,
|
||||
&mut pending_die_refs,
|
||||
&mut pending_di_refs,
|
||||
FileAttributeContext::Root(Some(debug_line_offset)),
|
||||
)?;
|
||||
|
||||
let (wp_die_id, vmctx_die_id) =
|
||||
add_internal_types(comp_unit, root_id, out_strings, module_info);
|
||||
|
||||
stack.push(root_id);
|
||||
(
|
||||
comp_unit,
|
||||
unit_id,
|
||||
file_map,
|
||||
cu_low_pc,
|
||||
wp_die_id,
|
||||
vmctx_die_id,
|
||||
)
|
||||
} else {
|
||||
return Err(TransformError("Unexpected unit header").into());
|
||||
}
|
||||
} else {
|
||||
return Ok(None); // empty
|
||||
};
|
||||
let mut skip_at_depth = None;
|
||||
let mut current_frame_base = InheritedAttr::new();
|
||||
let mut current_value_range = InheritedAttr::new();
|
||||
let mut current_scope_ranges = InheritedAttr::new();
|
||||
while let Some((depth_delta, entry)) = entries.next_dfs()? {
|
||||
let depth_delta = if let Some((depth, cached)) = skip_at_depth {
|
||||
let new_depth = depth + depth_delta;
|
||||
if new_depth > 0 {
|
||||
skip_at_depth = Some((new_depth, cached));
|
||||
continue;
|
||||
}
|
||||
skip_at_depth = None;
|
||||
new_depth + cached
|
||||
} else {
|
||||
depth_delta
|
||||
};
|
||||
|
||||
if !context
|
||||
.reachable
|
||||
.contains(&entry.offset().to_unit_section_offset(&unit))
|
||||
{
|
||||
// entry is not reachable: discarding all its info.
|
||||
skip_at_depth = Some((0, depth_delta));
|
||||
continue;
|
||||
}
|
||||
|
||||
let new_stack_len = stack.len().wrapping_add(depth_delta as usize);
|
||||
current_frame_base.update(new_stack_len);
|
||||
current_scope_ranges.update(new_stack_len);
|
||||
current_value_range.update(new_stack_len);
|
||||
let range_builder = if entry.tag() == gimli::DW_TAG_subprogram {
|
||||
let range_builder = RangeInfoBuilder::from_subprogram_die(
|
||||
entry,
|
||||
context,
|
||||
unit.encoding(),
|
||||
addr_tr,
|
||||
cu_low_pc,
|
||||
)?;
|
||||
if let RangeInfoBuilder::Function(func_index) = range_builder {
|
||||
if let Some(frame_info) =
|
||||
get_function_frame_info(module_info, func_index, value_ranges)
|
||||
{
|
||||
current_value_range.push(new_stack_len, frame_info);
|
||||
}
|
||||
translated.insert(func_index.index() as u32);
|
||||
current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr));
|
||||
Some(range_builder)
|
||||
} else {
|
||||
// FIXME current_scope_ranges.push()
|
||||
None
|
||||
}
|
||||
} else {
|
||||
let high_pc = entry.attr_value(gimli::DW_AT_high_pc)?;
|
||||
let ranges = entry.attr_value(gimli::DW_AT_ranges)?;
|
||||
if high_pc.is_some() || ranges.is_some() {
|
||||
let range_builder =
|
||||
RangeInfoBuilder::from(entry, context, unit.encoding(), cu_low_pc)?;
|
||||
current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr));
|
||||
Some(range_builder)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if depth_delta <= 0 {
|
||||
for _ in depth_delta..1 {
|
||||
stack.pop();
|
||||
}
|
||||
} else {
|
||||
assert_eq!(depth_delta, 1);
|
||||
}
|
||||
|
||||
if let Some(AttributeValue::Exprloc(expr)) = entry.attr_value(gimli::DW_AT_frame_base)? {
|
||||
if let Some(expr) = compile_expression(&expr, unit.encoding(), None)? {
|
||||
current_frame_base.push(new_stack_len, expr);
|
||||
}
|
||||
}
|
||||
|
||||
let parent = stack.last().unwrap();
|
||||
|
||||
if entry.tag() == gimli::DW_TAG_pointer_type {
|
||||
// Wrap pointer types.
|
||||
// TODO reference types?
|
||||
let die_id = replace_pointer_type(
|
||||
*parent,
|
||||
comp_unit,
|
||||
wp_die_id,
|
||||
entry,
|
||||
&unit,
|
||||
context,
|
||||
out_strings,
|
||||
&mut pending_die_refs,
|
||||
)?;
|
||||
stack.push(die_id);
|
||||
assert_eq!(stack.len(), new_stack_len);
|
||||
die_ref_map.insert(entry.offset(), die_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
let die_id = comp_unit.add(*parent, entry.tag());
|
||||
|
||||
stack.push(die_id);
|
||||
assert_eq!(stack.len(), new_stack_len);
|
||||
die_ref_map.insert(entry.offset(), die_id);
|
||||
|
||||
clone_die_attributes(
|
||||
entry,
|
||||
context,
|
||||
addr_tr,
|
||||
current_value_range.top(),
|
||||
unit.encoding(),
|
||||
&mut comp_unit,
|
||||
die_id,
|
||||
range_builder,
|
||||
current_scope_ranges.top(),
|
||||
cu_low_pc,
|
||||
out_strings,
|
||||
&mut pending_die_refs,
|
||||
&mut pending_di_refs,
|
||||
FileAttributeContext::Children(&file_map, current_frame_base.top()),
|
||||
)?;
|
||||
|
||||
if entry.tag() == gimli::DW_TAG_subprogram && !current_scope_ranges.is_empty() {
|
||||
append_vmctx_info(
|
||||
comp_unit,
|
||||
die_id,
|
||||
vmctx_die_id,
|
||||
addr_tr,
|
||||
current_value_range.top(),
|
||||
current_scope_ranges.top().expect("range"),
|
||||
out_strings,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
die_ref_map.patch(pending_die_refs, comp_unit);
|
||||
Ok(Some((unit_id, die_ref_map, pending_di_refs)))
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user