diff --git a/.cargo/config b/.cargo/config index d592b9764..52e8b726c 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,2 +1,2 @@ [build] -rustflags = ["-C", "link-args=-Wl,--export-dynamic"] +rustflags = ["-C", "link-args=-rdynamic"] diff --git a/.dockerignore b/.dockerignore index eb5a316cb..72e8ffc0d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1 @@ -target +* diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..67a786058 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[Makefile] +indent_style = tab diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..c652b4d86 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +# Auto detect text and normalize to LF on commit regardless of "core.autocrlf". +# See: https://help.github.com/en/articles/dealing-with-line-endings#example +* text=auto eol=lf + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.jpg binary +*.wasm binary diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml new file mode 100644 index 000000000..550fce474 --- /dev/null +++ b/.github/actions/test/action.yml @@ -0,0 +1,18 @@ +name: 'Test Lucet' +description: 'run tests using standardized lucet development environment' +inputs: + target: + description: "Makefile target to execute" + required: true +runs: + using: 'docker' + image: '../../../Dockerfile' + # The Dockerfile does not specify an entrypoint. + entrypoint: "/bin/sh" + args: + # Next arg is a command for sh to execute. + - '-c' + # rustup expects $HOME to be set to /root during `docker run` because thats what + # it was set to during container creation. Actions clears $HOME so we set it here. + # The test target of the Makefile is our standard CI. + - 'export HOME=/root; make ${{ inputs.target }}' diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml new file mode 100644 index 000000000..6eaa49be4 --- /dev/null +++ b/.github/workflows/fuzz.yml @@ -0,0 +1,50 @@ +name: Fuzz +on: + schedule: + - cron: "0 0 * * *" + +jobs: + fuzz: + name: Fuzz lucet-wasi + + runs-on: ubuntu-16.04 + steps: + - uses: actions/checkout@v1 + with: + submodules: true + + - name: Install Rust (rustup) + run: rustup update + + - name: Install wasi-sdk (ubuntu) + run: | + curl -sS -L -O https://github.com/CraneStation/wasi-sdk/releases/download/wasi-sdk-7/wasi-sdk_7.0_amd64.deb + sudo dpkg -i wasi-sdk_7.0_amd64.deb + + - name: Install native clang, csmith tools + run: | + sudo apt-get install -y --no-install-recommends \ + software-properties-common \ + clang-6.0 \ + gcc-multilib \ + csmith \ + libcsmith-dev \ + creduce + sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-6.0 100 + + - name: Build Binaryen tools + run: | + curl -sS -L "https://github.com/WebAssembly/binaryen/archive/version_${BINARYEN_VERSION}.tar.gz" | tar xzf - + mkdir -p binaryen-build + cd binaryen-build && cmake "../binaryen-version_${BINARYEN_VERSION}" && make wasm-opt wasm-reduce + echo "##[add-path]$PWD/binaryen-build/bin" + env: + BINARYEN_VERSION: 86 + + - name: Test lucet-wasi-fuzz with known seed + run: make test-fuzz + + - name: Fuzz + env: + FUZZ_NUM_TESTS: 100000 + run: make fuzz diff --git a/.github/workflows/mac-ci.yml.disabled b/.github/workflows/mac-ci.yml.disabled new file mode 100644 index 000000000..fee6f873d --- /dev/null +++ b/.github/workflows/mac-ci.yml.disabled @@ -0,0 +1,29 @@ +name: Mac OS CI +on: [push, pull_request] + +jobs: + test: + name: Test + runs-on: macos-latest + steps: + - uses: actions/checkout@master + with: + submodules: true + + - name: Install Rust (macos) + run: | + curl https://sh.rustup.rs | sh -s -- -y + rustup update + echo "##[add-path]$HOME/.cargo/bin" + + - name: Install wasi-sdk (macos) + run: | + curl -sS -L -O https://github.com/CraneStation/wasi-sdk/releases/download/wasi-sdk-7/wasi-sdk-7.0-macos.tar.gz + tar xf wasi-sdk-7.0-macos.tar.gz + sudo mv wasi-sdk-7.0/opt /opt + + - name: Test Lucet + run: make test-except-fuzz + + - name: Ensure testing did not change sources + run: git diff --exit-code diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..7d2d79392 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,51 @@ +name: CI +on: [push, pull_request] + +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + with: + submodules: true + + # Testing uses the development environment Docker container. + # This action builds the container and executes the test suite inside it. + - uses: ./.github/actions/test + with: + target: test-full + + - name: Ensure testing did not change sources + run: git diff --exit-code + + package: + name: Package + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + with: + submodules: true + + # Testing uses the development environment Docker container. + # This action builds the container and executes the test suite inside it. + - uses: ./.github/actions/test + with: + target: package + + - name: Ensure testing did not change sources + run: git diff --exit-code + + + rustfmt: + name: Rustfmt + runs-on: ubuntu-16.04 + steps: + - uses: actions/checkout@v1 + with: + submodules: true + - name: Install Rust (rustup) + run: | + rustup update + rustup component add rustfmt + - run: make indent-check diff --git a/.gitignore b/.gitignore index 6e0f37c3c..d0ba2d0ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ target/ *.rs.bk *.pyc -host + +# devenv-installed directory +/host + +core.* diff --git a/.gitmodules b/.gitmodules index 91aa6c61b..aa307e5b0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,15 +1,12 @@ [submodule "cranelift"] path = cranelift url = https://github.com/cranestation/cranelift -[submodule "faerie"] - path = faerie - url = https://github.com/m4b/faerie [submodule "lucet-spectest/spec"] path = lucet-spectest/spec url = https://github.com/webassembly/spec -[submodule "pwasm-validation"] - path = pwasm-validation - url = https://github.com/pchickey/pwasm-validator [submodule "sightglass"] path = sightglass url = https://github.com/fastly/sightglass +[submodule "wasi"] + path = wasi + url = https://github.com/webassembly/wasi diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 000000000..8148fc634 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1 @@ +# This file tells tools we use rustfmt. We use the default settings. diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7d8d027e1..000000000 --- a/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -dist: xenial - -env: - - DEVENV_NO_INSTALL=1 - -services: - - docker - -before_install: - - ./devenv_build_container.sh - - ./devenv_start.sh - -script: - - ./devenv_run.sh make indent-check test audit - - git diff --exit-code - -notifications: - email: false diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 59777fc6e..5c5ebdd25 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,18 +1,14 @@ # 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, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. +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: +Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences @@ -22,56 +18,32 @@ include: Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or - advances +* 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 +* 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 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. +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. +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 project team at labs@fastly.com, or tyler@fastly.com -if the message is sensitive. All complaints will be reviewed and investigated -and will result in a response that is deemed necessary and appropriate to the -circumstances. The project team is obligated to maintain confidentiality with -regard to the reporter of an incident. Further details of specific enforcement -policies may be posted separately. +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 project's leadership. +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 https://www.contributor-covenant.org/version/1/4/code-of-conduct.html +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]: ORG_CODE_OF_CONDUCT.md [homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq +[version]: https://www.contributor-covenant.org/version/1/4/ diff --git a/Cargo.lock b/Cargo.lock index 80ccc074d..37eeedd40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,9 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "adler32" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "aho-corasick" -version = "0.6.10" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -11,51 +18,59 @@ name = "ansi_term" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "assert_matches" -version = "1.3.0" +name = "anyhow" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "atty" -version = "0.2.11" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "autocfg" -version = "0.1.2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "autocfg" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "backtrace" -version = "0.3.14" +version = "0.3.42" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "backtrace-sys" -version = "0.1.28" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bencher" version = "0.1.5" @@ -63,37 +78,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "bimap" -version = "0.1.5" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "bincode" -version = "1.0.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "bindgen" -version = "0.47.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "cexpr 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "clang-sys 0.26.4 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cexpr 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "clang-sys 0.28.1 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "which 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "which 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -103,58 +122,104 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "bitflags" -version = "1.0.4" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bstr" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "byte-tools" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "byteorder" -version = "1.3.1" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "c2-chacha" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "cast" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "cc" -version = "1.0.31" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cexpr" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "nom 4.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cfg-if" -version = "0.1.7" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "clang-sys" -version = "0.26.4" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "clap" -version = "2.32.0" +version = "2.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -163,106 +228,294 @@ name = "cloudabi" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cmake" -version = "0.1.35" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "colored" -version = "1.7.0" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core_affinity" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cpu-time" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cranelift-bforest" -version = "0.29.0" +version = "0.51.0" dependencies = [ - "cranelift-entity 0.29.0", + "cranelift-entity 0.51.0", ] [[package]] name = "cranelift-codegen" -version = "0.29.0" +version = "0.51.0" dependencies = [ - "cranelift-bforest 0.29.0", - "cranelift-codegen-meta 0.29.0", - "cranelift-entity 0.29.0", - "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "target-lexicon 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-bforest 0.51.0", + "cranelift-codegen-meta 0.51.0", + "cranelift-codegen-shared 0.51.0", + "cranelift-entity 0.51.0", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cranelift-codegen-meta" -version = "0.29.0" +version = "0.51.0" +dependencies = [ + "cranelift-codegen-shared 0.51.0", + "cranelift-entity 0.51.0", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.51.0" dependencies = [ - "cranelift-entity 0.29.0", + "packed_struct 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "packed_struct_codegen 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cranelift-entity" -version = "0.29.0" +version = "0.51.0" [[package]] name = "cranelift-faerie" -version = "0.29.0" +version = "0.51.0" dependencies = [ - "cranelift-codegen 0.29.0", - "cranelift-module 0.29.0", - "faerie 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "goblin 0.0.21 (registry+https://github.com/rust-lang/crates.io-index)", - "target-lexicon 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-codegen 0.51.0", + "cranelift-module 0.51.0", + "faerie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "goblin 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cranelift-frontend" -version = "0.29.0" +version = "0.51.0" dependencies = [ - "cranelift-codegen 0.29.0", - "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "target-lexicon 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-codegen 0.51.0", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cranelift-module" -version = "0.29.0" +version = "0.51.0" dependencies = [ - "cranelift-codegen 0.29.0", - "cranelift-entity 0.29.0", - "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-codegen 0.51.0", + "cranelift-entity 0.51.0", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cranelift-native" -version = "0.29.0" +version = "0.51.0" dependencies = [ - "cranelift-codegen 0.29.0", - "raw-cpuid 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "target-lexicon 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-codegen 0.51.0", + "raw-cpuid 7.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cranelift-wasm" +version = "0.51.0" +dependencies = [ + "cranelift-codegen 0.51.0", + "cranelift-entity 0.51.0", + "cranelift-frontend 0.51.0", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmparser 0.39.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crc32fast" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "criterion" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "criterion-plot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "csv 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_xoshiro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", + "tinytemplate 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "criterion-plot" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-deque" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-queue" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "csv" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bstr 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "csv-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cvt" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "derivative" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "either" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "env_logger" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -271,8 +524,8 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "errno-dragonfly 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -281,49 +534,68 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "faerie" -version = "0.9.1" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "goblin 0.0.21 (registry+https://github.com/rust-lang/crates.io-index)", - "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "scroll 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", - "string-interner 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt-derive 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", - "target-lexicon 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "goblin 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "indexmap 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "scroll 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "string-interner 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "faerie" -version = "0.9.1" +name = "failure" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -replace = "faerie 0.9.1" +dependencies = [ + "backtrace 0.3.42 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "failure" -version = "0.1.5" +name = "failure_derive" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "backtrace 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "failure_derive" -version = "0.1.5" +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "filetime" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "flate2" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", - "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz_oxide 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -336,28 +608,53 @@ name = "gcc" version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "getrandom" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "glob" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "goblin" -version = "0.0.21" +version = "0.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "plain 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "scroll 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "hashbrown" -version = "0.1.8" +name = "goblin" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "plain 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "scroll 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -365,7 +662,24 @@ name = "heck" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hermit-abi" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hmac" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -375,10 +689,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "humantime" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -389,20 +703,31 @@ dependencies = [ "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "indexmap" -version = "1.0.2" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itertools" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "itoa" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -416,205 +741,328 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.3.0" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "leb128" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.50" +version = "0.2.66" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libloading" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "log" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "lucet-analyze" -version = "0.1.0" +name = "lucet-benchmarks" +version = "0.4.1" dependencies = [ - "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "colored 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "goblin 0.0.21 (registry+https://github.com/rust-lang/crates.io-index)", + "criterion 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lucet-module 0.5.0", + "lucet-runtime 0.5.0", + "lucet-runtime-internals 0.5.0", + "lucet-wasi 0.5.0", + "lucet-wasi-sdk 0.5.0", + "lucetc 0.5.0", + "nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "lucet-idl" -version = "0.1.0" +name = "lucet-module" +version = "0.5.0" +dependencies = [ + "anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", + "bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-entity 0.51.0", + "derivative 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "minisign 0.5.14 (registry+https://github.com/rust-lang/crates.io-index)", + "num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "object 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde-big-array 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "lucet-module-data" -version = "0.1.0" +name = "lucet-objdump" +version = "0.5.0" dependencies = [ - "bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "colored 1.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "goblin 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)", + "lucet-module 0.5.0", ] [[package]] name = "lucet-runtime" -version = "0.1.0" +version = "0.5.0" dependencies = [ - "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "lucet-module-data 0.1.0", - "lucet-runtime-internals 0.1.0", - "lucet-runtime-tests 0.1.0", - "nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-derive 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "lucet-module 0.5.0", + "lucet-runtime-internals 0.5.0", + "lucet-runtime-tests 0.5.0", + "lucet-wasi-sdk 0.5.0", + "lucetc 0.5.0", + "nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "lucet-runtime-internals" -version = "0.1.0" +version = "0.5.0" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lucet-module-data 0.1.0", - "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-derive 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "xfailure 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", + "bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lucet-module 0.5.0", + "lucet-runtime-macros 0.5.0", + "memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "raw-cpuid 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lucet-runtime-macros" +version = "0.5.0" +dependencies = [ + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "lucet-runtime-tests" -version = "0.1.0" +version = "0.5.0" dependencies = [ - "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lucet-runtime-internals 0.1.0", - "lucet-wasi-sdk 0.1.0", - "lucetc 0.1.0", - "tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lucet-module 0.5.0", + "lucet-runtime-internals 0.5.0", + "lucet-wasi-sdk 0.5.0", + "lucetc 0.5.0", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "lucet-spectest" -version = "0.1.0" +version = "0.5.0" dependencies = [ - "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "lucet-runtime 0.1.0", - "lucetc 0.1.0", - "parity-wasm 0.35.7 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "wabt 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", + "anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lucet-module 0.5.0", + "lucet-runtime 0.5.0", + "lucetc 0.5.0", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "wabt 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lucet-validate" +version = "0.5.0" +dependencies = [ + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-entity 0.51.0", + "lucet-wasi-sdk 0.5.0", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "wabt 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmparser 0.39.3 (registry+https://github.com/rust-lang/crates.io-index)", + "witx 0.6.0", ] [[package]] name = "lucet-wasi" -version = "0.1.0" +version = "0.5.0" dependencies = [ - "bindgen 0.47.3 (registry+https://github.com/rust-lang/crates.io-index)", - "cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", + "bindgen 0.51.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "human-size 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "lucet-runtime 0.1.0", - "lucet-runtime-internals 0.1.0", - "lucet-wasi-sdk 0.1.0", - "lucetc 0.1.0", - "nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "lucet-module 0.5.0", + "lucet-runtime 0.5.0", + "lucet-runtime-internals 0.5.0", + "lucet-wasi-sdk 0.5.0", + "lucetc 0.5.0", + "nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasi-common 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lucet-wasi-fuzz" +version = "0.5.0" +dependencies = [ + "anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "lucet-module 0.5.0", + "lucet-runtime 0.5.0", + "lucet-wasi 0.5.0", + "lucet-wasi-sdk 0.5.0", + "lucetc 0.5.0", + "nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "progress 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "lucet-wasi-sdk" -version = "0.1.0" +version = "0.5.0" dependencies = [ - "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", + "lucet-module 0.5.0", + "lucet-validate 0.5.0", + "lucetc 0.5.0", + "target-lexicon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "lucetc" -version = "0.1.0" +version = "0.5.0" dependencies = [ - "bimap 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cranelift-codegen 0.29.0", - "cranelift-faerie 0.29.0", - "cranelift-frontend 0.29.0", - "cranelift-module 0.29.0", - "cranelift-native 0.29.0", - "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "faerie 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", + "bimap 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-codegen 0.51.0", + "cranelift-entity 0.51.0", + "cranelift-faerie 0.51.0", + "cranelift-frontend 0.51.0", + "cranelift-module 0.51.0", + "cranelift-native 0.51.0", + "cranelift-wasm 0.51.0", + "env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "faerie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "goblin 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)", "human-size 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "lucet-module-data 0.1.0", - "lucet-wasi-sdk 0.1.0", - "parity-wasm 0.35.7 (registry+https://github.com/rust-lang/crates.io-index)", - "pwasm-validation 0.1.0", - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", - "target-lexicon 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "wabt 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmonkey 0.1.4", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "lucet-module 0.5.0", + "lucet-validate 0.5.0", + "memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "minisign 0.5.14 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-wasm 0.41.0 (registry+https://github.com/rust-lang/crates.io-index)", + "raw-cpuid 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "wabt 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmonkey 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmparser 0.39.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "memchr" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "memoffset" -version = "0.2.1" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "minisign" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rpassword 4.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "scrypt 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "memory_units" -version = "0.4.0" +name = "miniz_oxide" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "nix" -version = "0.13.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "nom" -version = "4.2.2" +version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -623,49 +1071,142 @@ name = "num" version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", - "num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "num-derive" +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-complex 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "num-rational 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-complex" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-integer" -version = "0.1.39" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-iter" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-rational" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-traits" -version = "0.2.6" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num_cpus" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "object" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", + "goblin 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-wasm 0.40.3 (registry+https://github.com/rust-lang/crates.io-index)", + "scroll 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "packed_struct" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "packed_struct_codegen" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "packed_struct 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parity-wasm" +version = "0.40.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "parity-wasm" -version = "0.35.7" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pbkdf2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -675,7 +1216,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "pkg-config" -version = "0.3.14" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -683,13 +1224,18 @@ name = "plain" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ppv-lite86" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "precision" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -698,35 +1244,78 @@ name = "printtable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "proc-macro-error" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-error-attr 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rustversion 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro-error-attr" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rustversion 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "syn-mid 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "proc-macro2" -version = "0.4.27" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "pwasm-validation" -version = "0.1.0" +name = "proc-macro2" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "progress" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "assert_matches 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "memory_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-wasm 0.35.7 (registry+https://github.com/rust-lang/crates.io-index)", - "wabt 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", + "terminal_size 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "quick-error" -version = "1.2.2" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quote" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "quote" -version = "0.6.11" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -734,17 +1323,29 @@ name = "rand" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -752,22 +1353,39 @@ name = "rand_chacha" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand_chacha" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand_core" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rand_core" -version = "0.4.0" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand_core" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "rand_hc" @@ -777,6 +1395,14 @@ dependencies = [ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand_isaac" version = "0.1.1" @@ -787,12 +1413,12 @@ dependencies = [ [[package]] name = "rand_jitter" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -802,10 +1428,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_os" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -813,8 +1448,8 @@ name = "rand_pcg" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -825,16 +1460,56 @@ dependencies = [ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand_xoshiro" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "raw-cpuid" version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "raw-cpuid" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rayon" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rayon-core" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rdrand" version = "0.4.0" @@ -845,49 +1520,62 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.1.51" +version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "redox_termios" -version = "0.1.1" +name = "regex" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "regex" -version = "1.1.2" +name = "regex-automata" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex-syntax" -version = "0.6.5" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "remove_dir_all" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "remove_dir_all" -version = "0.5.1" +name = "rpassword" +version = "4.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rustc-demangle" -version = "0.1.13" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc-hash" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "rustc_version" @@ -897,14 +1585,32 @@ dependencies = [ "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rustversion" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ryu" -version = "0.2.7" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "scopeguard" -version = "0.3.3" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -916,14 +1622,44 @@ dependencies = [ "scroll_derive 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "scroll" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "scroll_derive 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "scroll_derive" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "scroll_derive" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "scrypt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pbkdf2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -941,199 +1677,301 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde" -version = "1.0.89" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde-big-array" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_derive" -version = "1.0.89" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_json" -version = "1.0.39" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sha2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "sightglass" version = "0.1.0" dependencies = [ "bencher 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "goblin 0.0.21 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "core_affinity 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "goblin 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)", "hwloc 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "precision 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "printtable 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", - "toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "xfailure 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "siphasher" -version = "0.2.3" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "smallvec" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "string-interner" -version = "0.6.3" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "strsim" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "structopt" -version = "0.2.15" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt-derive 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt-derive 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "structopt-derive" -version = "0.2.15" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-error 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "syn" -version = "0.15.29" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "synstructure" -version = "0.10.1" +name = "syn" +version = "0.15.44" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "target-lexicon" -version = "0.2.0" +name = "syn" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn-mid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synom" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synstructure" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "target-lexicon" -version = "0.3.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "target-lexicon" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "tempfile" -version = "3.0.7" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", - "remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "termcolor" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "termion" -version = "1.5.1" +name = "terminal_size" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "textwrap" -version = "0.10.0" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thiserror" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "thread_local" -version = "0.3.6" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tinytemplate" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "toml" -version = "0.4.10" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "ucd-util" -version = "0.1.3" +name = "typenum" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "unicode-segmentation" -version = "1.2.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "unicode-width" -version = "0.1.5" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-xid" +version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1142,8 +1980,13 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "utf8-ranges" -version = "1.0.2" +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "uuid" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1163,48 +2006,120 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "wabt" -version = "0.7.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", - "wabt-sys 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", + "wabt-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wabt-sys" -version = "0.5.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", - "cmake 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", + "cmake 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "walkdir" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasi-common" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cpu-time 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "filetime 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "wasi-common-cbindgen 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wig 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winx 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasi-common-cbindgen" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "wasmonkey" -version = "0.1.4" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "goblin 0.0.21 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-wasm 0.35.7 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", - "siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "goblin 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-wasm 0.41.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", + "siphasher 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "xfailure 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "wasmparser" +version = "0.39.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "wast" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "leb128 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "which" -version = "2.0.1" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wig" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "witx 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1214,7 +2129,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1233,10 +2148,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1245,12 +2160,32 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "wincolor" -version = "1.0.1" +name = "winx" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cvt 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "witx" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "wast 3.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "witx" +version = "0.6.0" +dependencies = [ + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "wast 3.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1259,129 +2194,220 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] -"checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" +"checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" +"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -"checksum assert_matches 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7deb0a829ca7bcfaf5da70b073a8d128619259a7be8216a355e23f00763059e5" -"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" -"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" -"checksum backtrace 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "cd5a90e2b463010cd0e0ce9a11d4a9d5d58d9f41d4a6ba3dcaf9e68b466e88b4" -"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" +"checksum anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c" +"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" +"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +"checksum backtrace 0.3.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b4b1549d804b6c73f4817df2ba073709e96e426f12987127c48e6745568c350b" +"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" +"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" "checksum bencher 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7dfdb4953a096c551ce9ace855a604d702e6e62d77fac690575ae347571717f5" -"checksum bimap 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6b282b982237078bfac61a948a2198f185aceea8b9a6e794b70b96fd31923d3d" -"checksum bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9f2fb9e29e72fd6bc12071533d5dc7664cb01480c59406f656d7ac25c7bd8ff7" -"checksum bindgen 0.47.3 (registry+https://github.com/rust-lang/crates.io-index)" = "df683a55b54b41d5ea8ebfaebb5aa7e6b84e3f3006a78f010dadc9ca88469260" +"checksum bimap 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "505e45beaf0a1462f5548fe885edf2d83e62022b2ce8b10fef0f7686b48c9266" +"checksum bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5753e2a71534719bf3f4e57006c3a4f0d2c672a4b676eec84161f763eca87dbf" +"checksum bindgen 0.51.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ebd71393f1ec0509b553aa012b9b58e81dadbdff7130bd3b8cba576e69b32f75" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" -"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" -"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" -"checksum cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "926013f2860c46252efceabb19f4a6b308197505082c609025aa6706c011d427" -"checksum cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "c9ce8bb087aacff865633f0bd5aeaed910fe2fe55b55f4739527f2e023a2e53d" -"checksum cexpr 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "644d693ecfa91955ed32dcc7eda4914e1be97a641fb6f0645a37348e20b230da" -"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" -"checksum clang-sys 0.26.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6ef0c1bcf2e99c649104bd7a7012d8f8802684400e03db0ec0af48583c6fa0e4" -"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" +"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +"checksum block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +"checksum bstr 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3ede750122d9d1f87919570cb2cccee38c84fbc8c5599b25c289af40625b7030" +"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" +"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" +"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" +"checksum cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" +"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" +"checksum cexpr 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fce5b5fb86b0c57c20c834c1b412fd09c77c8a59b9473f86272709e78874cd1d" +"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +"checksum clang-sys 0.28.1 (registry+https://github.com/rust-lang/crates.io-index)" = "81de550971c976f176130da4b2978d3b524eaa0fd9ac31f3ceb5ae1231fb4853" +"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum cmake 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "6ec65ee4f9c9d16f335091d23693457ed4928657ba4982289d7fafee03bc614a" -"checksum colored 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e9a455e156a4271e12fd0246238c380b1e223e3736663c7a18ed8b6362028a9" -"checksum env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b61fa891024a945da30a9581546e8cfaf5602c7b3f4c137a2805cf388f92075a" +"checksum cmake 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "81fb25b677f8bf1eb325017cb6bb8452f87969db0fedb4f757b297bee78a7c62" +"checksum colored 1.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8815e2ab78f3a59928fc32e141fbeece88320a240e43f47b2fd64ea3a88a5b3d" +"checksum core_affinity 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "7f8a03115cc34fb0d7c321dd154a3914b3ca082ccc5c11d91bf7117dbbe7171f" +"checksum cpu-time 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e9e393a7668fe1fad3075085b86c781883000b4ede868f43627b34a87c8b7ded" +"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +"checksum criterion 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "938703e165481c8d612ea3479ac8342e5615185db37765162e762ec3523e2fc6" +"checksum criterion-plot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eccdc6ce8bbe352ca89025bee672aa6d24f4eb8c53e3a8b5d1bc58011da072a2" +"checksum crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca" +"checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac" +"checksum crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" +"checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" +"checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +"checksum csv 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279" +"checksum csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9b5cadb6b25c77aeff80ba701712494213f4a8418fcda2ee11b6560c3ad0bf4c" +"checksum cvt 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "34ac344c7efccb80cd25bc61b2170aec26f2f693fd40e765a539a1243db48c71" +"checksum derivative 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "942ca430eef7a3806595a6737bc388bf51adb888d3fc0dd1b50f1c170167ee3a" +"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +"checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" "checksum errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2a071601ed01b988f896ab14b95e67335d1eeb50190932a1320f7fe3cadc84e" "checksum errno-dragonfly 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" -"checksum faerie 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f48412f92b56015a240e249847295b38b0a731435806c21a199403b2c317272c" -"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" -"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" +"checksum faerie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "01fed63609767c70e34203201032c249d60a24578a67ef0ce7cc13ff010e9cf2" +"checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" +"checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" +"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +"checksum filetime 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1ff6d4dab0aa0c8e6346d46052e93b13a16cf847b54ed357087c35011048cc7d" +"checksum flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6bd6d6f4752952feb71363cffc9ebac9411b75b87c6ab6058c40c8900cf43c0f" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" +"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +"checksum getrandom 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8d1dffef07351aafe6ef177e4dd2b8dcf503e6bc765dea3b0de9ed149a3db1ec" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" -"checksum goblin 0.0.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6a4013e9182f2345c6b7829b9ef6e670bce0dfca12c6f974457ed2160c2c7fe9" -"checksum hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3bae29b6653b3412c2e71e9d486db9f9df5d701941d86683005efb9f2d28e3da" +"checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +"checksum goblin 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "e3fa261d919c1ae9d1e4533c4a2f99e10938603c4208d56c05bec7a872b661b0" +"checksum goblin 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3081214398d39e4bd7f2c1975f0488ed04614ffdd976c6fc7a0708278552c0da" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +"checksum hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772" +"checksum hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" "checksum human-size 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5bec6e801ef7367625bd94ad7e2965e6027189f3e9deef422388d993af2814a0" -"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" +"checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" "checksum hwloc 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2934f84993b8b4bcae9b6a4e5f0aca638462dda9c7b4f26a570241494f21e0f4" -"checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" -"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" +"checksum indexmap 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b54058f0a6ff80b6803da8faf8997cde53872b38f4023728f6830b06cd3c0dc" +"checksum itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" +"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" -"checksum libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "aab692d7759f5cd8c859e169db98ae5b52c924add2af5fbbca11d12fefb567c1" -"checksum libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3ad660d7cb8c5822cd83d10897b0f1f1526792737a179e73896152f85b88c2" -"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" -"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" -"checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" -"checksum memory_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" -"checksum nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46f0f3210768d796e8fa79ec70ee6af172dacbe7147f5e69be5240a47778302b" -"checksum nom 4.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "22293d25d3f33a8567cc8a1dc20f40c7eeb761ce83d0fcca059858580790cac3" +"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +"checksum leb128 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" +"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" +"checksum libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" +"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +"checksum memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223" +"checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9" +"checksum minisign 0.5.14 (registry+https://github.com/rust-lang/crates.io-index)" = "55d7baea44770d41993f63733f07df59ae36a02cb1968292f3f625b1d1dcb780" +"checksum miniz_oxide 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6f3f74f726ae935c3f514300cc6773a0c9492abc5e972d42ba0c0ebb88757625" +"checksum nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229" +"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" "checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" -"checksum num-derive 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d9fe8fcafd1b86a37ce8a1cfa15ae504817e0c8c2e7ad42767371461ac1d316d" -"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" -"checksum num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "af3fdbbc3291a5464dc57b03860ec37ca6bf915ed6ee385e7c6c052c422b2124" -"checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" -"checksum parity-wasm 0.35.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3e1e076c4e01399b6cd0793a8df42f90bba3ae424671ef421d1608a943155d93" +"checksum num 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +"checksum num-complex 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +"checksum num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0c8b15b261814f992e33760b1fca9fe8b693d8a65299f20c9901688636cfb746" +"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +"checksum num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00" +"checksum num-rational 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "da4dc79f9e6c81bef96148c8f6b8e72ad4541caa4a24373e900a36da07de03a3" +"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +"checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" +"checksum object 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a411a7fd46b7ebc9849c80513c84280f41cbc3159f489cd77fb30ecefdd1218a" +"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +"checksum packed_struct 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "90caf80e74380d94f2aabc83edb900b49123b3132442fb147f9155c87a756281" +"checksum packed_struct_codegen 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9f6fda15ebe37b7b28889bd4aa75bb134652eaec9eb99d1bf02f806fca4357fc" +"checksum parity-wasm 0.40.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1e39faaa292a687ea15120b1ac31899b13586446521df6c149e46f1584671e0f" +"checksum parity-wasm 0.41.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865" +"checksum pbkdf2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9" "checksum peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" -"checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" +"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" "checksum plain 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" "checksum precision 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "83f0b3de90e9edd6382604f4f28509ce25272deb065b94045fc47abcde567d55" "checksum printtable 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "69967dae4cac71681361899d9905d3d2985fc989d7afb32a8dff3aed5461ecdf" -"checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" -"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" -"checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1" +"checksum proc-macro-error 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1b79a464461615532fcc8a6ed8296fa66cc12350c18460ab3f4594a6cee0fcb6" +"checksum proc-macro-error-attr 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "23832e5eae6bac56bbac190500eef1aaede63776b5cd131eaa4ee7fe120cd892" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +"checksum proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548" +"checksum progress 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b820305721858696053a7fd0215cfeeee16ecaaf96b7a209945428e02f1c44" +"checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" +"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" +"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" "checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" "checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -"checksum rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b9ea758282efe12823e0d952ddb269d2e1897227e464919a554f2a03ef1b832" +"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" "checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +"checksum rand_os 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a788ae3edb696cfcba1c19bfd388cc4b8c21f8a408432b199c072825084da58a" "checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" "checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +"checksum rand_xoshiro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0e18c91676f670f6f0312764c759405f13afb98d5d73819840cf72a518487bff" "checksum raw-cpuid 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "30a9d219c32c9132f7be513c18be77c9881c7107d2ab5569d205a6a0f0e6dc7d" +"checksum raw-cpuid 7.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b4a349ca83373cfa5d6dbb66fd76e58b2cca08da71a5f6400de0a0a6a9bceeaf" +"checksum rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098" +"checksum rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9" "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -"checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85" -"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" -"checksum regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53ee8cfdddb2e0291adfb9f13d31d3bbe0a03c9a402c01b1e24188d86c35b24f" -"checksum regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8c2f35eedad5295fdf00a63d7d4b238135723f92b434ec06774dad15c7ab0861" -"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" -"checksum rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "adacaae16d02b6ec37fdc7acfcddf365978de76d1983d3ee22afc260e1ca9619" +"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +"checksum regex 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b5508c1941e4e7cb19965abef075d35a9a8b5cdf0846f30b4050e9b55dc55e87" +"checksum regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "92b73c2a1770c255c240eaa4ee600df1704a38dc3feaa6e949e7fcd4f8dc09f9" +"checksum regex-syntax 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e734e891f5b408a29efbf8309e656876276f49ab6a6ac208600b4419bd893d90" +"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" +"checksum rpassword 4.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "99371657d3c8e4d816fb6221db98fa408242b0b53bac08f8676a41f8554fe99f" +"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +"checksum rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7540fc8b0c49f096ee9c961cda096467dce8084bec6bdca2fc83895fd9b28cb8" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7" -"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" +"checksum rustversion 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a0538bd897e17257b0128d2fd95c2ed6df939374073a36166051a79e2eb7986" +"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" +"checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" +"checksum scroll 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "abb2332cb595d33f7edd5700f4cbf94892e680c7f0ae56adab58a35190b66cb1" "checksum scroll 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2f84d114ef17fd144153d608fba7c446b0145d038985e7a8cc5d08bb0ce20383" +"checksum scroll_derive 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8584eea9b9ff42825b46faf46a8c24d2cff13ec152fa2a50df788b87c07ee28" "checksum scroll_derive 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8f1aa96c45e7f5a91cb7fabe7b279f02fea7126239fc40b732316e8b6a2d0fcb" +"checksum scrypt 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "656c79d0e90d0ab28ac86bf3c3d10bfbbac91450d3f190113b4e76d9fec3cfdd" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "92514fb95f900c9b5126e32d020f5c6d40564c27a5ea6d1d7d9f157a96623560" -"checksum serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6eabf4b5914e88e24eea240bb7c9f9a2cbc1bbbe8d961d381975ec3c6b806c" -"checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" -"checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" -"checksum string-interner 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "abb38a0d8fe673c40b10b6b75abcb076a958cc10fb894f14993d9737c4c87000" -"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" -"checksum structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "3d0760c312538987d363c36c42339b55f5ee176ea8808bbe4543d484a291c8d1" -"checksum structopt-derive 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "528aeb7351d042e6ffbc2a6fb76a86f9b622fdf7c25932798e7a82cb03bc94c6" -"checksum syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1825685f977249735d510a242a6727b46efe914bb67e38d30c071b1b72b1d5c2" -"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" -"checksum target-lexicon 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4af5e2227f0b887d591d3724b796a96eff04226104d872f5b3883fcd427d64b9" -"checksum target-lexicon 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d6923974ce4eb5bd28814756256d8ab71c28dd6e7483313fe7ab6614306bf633" -"checksum tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b86c784c88d98c801132806dadd3819ed29d8600836c4088e855cdf3e178ed8a" -"checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f" -"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" -"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" -"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" -"checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" -"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" -"checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" -"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" +"checksum serde-big-array 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "883eee5198ea51720eab8be52a36cf6c0164ac90eea0ed95b649d5e35382404e" +"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" +"checksum serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)" = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7" +"checksum sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0" +"checksum shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" +"checksum siphasher 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "83da420ee8d1a89e640d0948c646c1c088758d3a3c538f943bfa97bdac17929d" +"checksum smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4" +"checksum string-interner 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd710eadff449a1531351b0e43eb81ea404336fa2f56c777427ab0e32a4cf183" +"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +"checksum structopt 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "df136b42d76b1fbea72e2ab3057343977b04b4a2e00836c3c7c0673829572713" +"checksum structopt-derive 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd50a87d2f7b8958055f3e73a963d78feaccca3836767a9069844e34b5b03c0a" +"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" +"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +"checksum syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5" +"checksum syn-mid 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9fd3937748a7eccff61ba5b90af1a20dbf610858923a9192ea0ecb0cb77db1d0" +"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +"checksum target-lexicon 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7975cb2c6f37d77b190bc5004a2bb015971464756fde9514651a525ada2a741a" +"checksum target-lexicon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6f4c118a7a38378f305a9e111fcb2f7f838c0be324bfb31a77ea04f7f6e684b4" +"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +"checksum termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +"checksum terminal_size 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "e25a60e3024df9029a414be05f46318a77c22538861a22170077d0388c0e926e" +"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +"checksum thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6f357d1814b33bc2dc221243f8424104bfe72dbe911d5b71b3816a2dff1c977e" +"checksum thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2e25d25307eb8436894f727aba8f65d07adf02e5b35a13cebed48bd282bfef" +"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +"checksum tinytemplate 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "57a3c6667d3e65eb1bc3aed6fd14011c6cbc3a0665218ab7f5daf040b9ec371a" +"checksum toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" +"checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" +"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" +"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" +"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" -"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" +"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +"checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -"checksum wabt 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "74e463a508e390cc7447e70f640fbf44ad52e1bd095314ace1fdf99516d32add" -"checksum wabt-sys 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a6265b25719e82598d104b3717375e37661d41753e2c84cde3f51050c7ed7e3c" -"checksum which 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b57acb10231b9493c8472b20cb57317d0679a49e0bdbee44b3b803a6473af164" +"checksum wabt 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3c5c5c1286c6e578416982609f47594265f9d489f9b836157d403ad605a46693" +"checksum wabt-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "af5d153dc96aad7dc13ab90835b892c69867948112d95299e522d370c4e13a08" +"checksum wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +"checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +"checksum wasi-common 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2a703d47961a9c2bf1dbeec7612446692dd8b380839c3d05f7211f72919c96df" +"checksum wasi-common-cbindgen 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ecab99a12e72a7442e5a9ecfc819f42520e1e0122cf06c0ba64c6a0b6b5263ce" +"checksum wasmonkey 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "af7c4bc80224427965ba21d87982cfc07d12e859824f303272056fb19f571ef9" +"checksum wasmparser 0.39.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c702914acda5feeeffbc29e4d953e5b9ce79d8b98da4dbf18a77086e116c5470" +"checksum wast 3.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "233648f540f07fce9b972436f2fbcae8a750c1121b6d32d949e1a44b4d9fc7b1" +"checksum which 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5475d47078209a02e60614f7ba5e645ef3ed60f771920ac1906d7c1cc65024c8" +"checksum wig 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3856dfac1bfc00633ac5b3b9b65c9c56cb5014b27ca6dfdae17bb8177a8c19" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" -"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" +"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" +"checksum winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -"checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" +"checksum winx 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c95125d6dc28a246c02340956929f4e67ac4c06c589b87de076d0dc0ad421b91" +"checksum witx 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7f88e293d72e3bd74cc452f9c3e934958f075252324ea24bf40cc16f1f068a3d" "checksum xfailure 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "da90eac47bf1d7871b75004b9b631d107df15f37669383b23f0b5297bc7516b6" diff --git a/Cargo.toml b/Cargo.toml index b35341d9c..4d6dea704 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,18 @@ +# Lucet version 0.3.1 + [workspace] members = [ - "lucet-analyze", - "lucet-idl", - "lucet-module-data", + "benchmarks/lucet-benchmarks", + "lucet-module", + "lucet-objdump", "lucet-runtime", "lucet-runtime/lucet-runtime-internals", + "lucet-runtime/lucet-runtime-macros", "lucet-runtime/lucet-runtime-tests", "lucet-spectest", + "lucet-validate", "lucet-wasi", + "lucet-wasi-fuzz", "lucet-wasi-sdk", "lucetc", "sightglass", @@ -16,6 +21,3 @@ exclude = ["cranelift"] [profile.test] rpath = true - -[replace] -"faerie:0.9.1" = { path = "faerie" } diff --git a/Dockerfile b/Dockerfile index c6e3215de..73b182584 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,11 @@ FROM ubuntu:xenial RUN apt-get update \ - && apt-get install -y --no-install-recommends \ + && apt-get install -y --no-install-recommends \ build-essential \ curl \ git \ libbsd-dev \ - libhwloc-dev \ doxygen \ python-sphinx \ cmake \ @@ -15,22 +14,40 @@ RUN apt-get update \ software-properties-common \ libssl-dev \ pkg-config \ - && rm -rf /var/lib/apt/lists/* + csmith \ + libcsmith-dev \ + creduce \ + gcc-multilib \ + clang-6.0 \ + && rm -rf /var/lib/apt/lists/* + +RUN update-alternatives --install /usr/bin/clang clang /usr/bin/clang-6.0 100 # Setting a consistent LD_LIBRARY_PATH across the entire environment prevents unnecessary Cargo # rebuilds. ENV LD_LIBRARY_PATH=/usr/local/lib -RUN curl -sS -L -O https://static.rust-lang.org/dist/rust-1.33.0-x86_64-unknown-linux-gnu.tar.gz \ - && tar xzf rust-1.33.0-x86_64-unknown-linux-gnu.tar.gz \ - && cd rust-1.33.0-x86_64-unknown-linux-gnu \ - && ./install.sh \ - && cd .. \ - && rm -rf rust-1.33.0-x86_64-unknown-linux-gnu rust-1.33.0-x86_64-unknown-linux-gnu.tar.gz -ENV PATH=/usr/local/bin:$PATH -RUN cargo install --root /usr/local cargo-audit cargo-watch +RUN curl https://sh.rustup.rs -sSf | \ + sh -s -- --default-toolchain nightly-2019-09-25 -y && \ + /root/.cargo/bin/rustup update nightly +ENV PATH=/root/.cargo/bin:$PATH + +RUN rustup component add rustfmt --toolchain nightly-2019-09-25-x86_64-unknown-linux-gnu +RUN rustup target add wasm32-wasi -RUN curl -sS -L -O https://github.com/CraneStation/wasi-sdk/releases/download/wasi-sdk-3/wasi-sdk_3.0_amd64.deb \ - && dpkg -i wasi-sdk_3.0_amd64.deb +RUN cargo install --debug cargo-audit cargo-watch rsign2 cargo-deb + +RUN curl -sS -L -O https://github.com/CraneStation/wasi-sdk/releases/download/wasi-sdk-7/wasi-sdk_7.0_amd64.deb \ + && dpkg -i wasi-sdk_7.0_amd64.deb && rm -f wasi-sdk_7.0_amd64.deb ENV WASI_SDK=/opt/wasi-sdk + +ENV BINARYEN_DIR=/opt/binaryen +ENV BINARYEN_VERSION=86 +RUN curl -sS -L "https://github.com/WebAssembly/binaryen/archive/version_${BINARYEN_VERSION}.tar.gz" | tar xzf - && \ + mkdir -p binaryen-build && ( cd binaryen-build && cmake "../binaryen-version_${BINARYEN_VERSION}" && \ + make wasm-opt wasm-reduce ) && \ + install -d -v "${BINARYEN_DIR}/bin" && \ + for tool in wasm-opt wasm-reduce; do install -v "binaryen-build/bin/${tool}" "${BINARYEN_DIR}/bin/"; done && \ + rm -fr binaryen-build binaryen-version_${BINARYEN_VERSION} +ENV PATH=$BINARYEN_DIR:$PATH diff --git a/Dockerfile.toolchain b/Dockerfile.toolchain new file mode 100644 index 000000000..0ce351908 --- /dev/null +++ b/Dockerfile.toolchain @@ -0,0 +1,14 @@ +FROM ubuntu:disco + +ENV WASI_SDK=/opt/wasi-sdk +ENV LD_LIBRARY_PATH=/opt/lucet/lib:$LD_LIBRARY_PATH +ENV PATH=/opt/lucet/bin:$PATH + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates clang curl lld && \ + rm -rf /var/lib/apt/lists/* && \ + update-alternatives --install /usr/bin/wasm-ld wasm-ld /usr/bin/wasm-ld-8 100 + +RUN curl -sL https://github.com/CraneStation/wasi-sdk/releases/download/wasi-sdk-6/libclang_rt.builtins-wasm32-wasi-6.0.tar.gz | tar x -zf - -C /usr/lib/llvm-8/lib/clang/8.0.0 + diff --git a/Makefile b/Makefile index b703e2e84..dead1ebe1 100644 --- a/Makefile +++ b/Makefile @@ -16,19 +16,47 @@ build: install: build @helpers/install.sh +.PHONY: install-dev +install-dev: build-dev + @helpers/install.sh --unoptimized + .PHONY: test -test: indent-check +test: indent-check test-packages + +.PHONY: test-packages +test-packages: cargo test --no-fail-fast \ -p lucet-runtime-internals \ -p lucet-runtime \ - -p lucet-module-data \ + -p lucet-module \ -p lucetc \ - -p lucet-idl \ -p lucet-wasi-sdk \ - -p lucet-wasi + -p lucet-wasi \ + -p lucet-wasi-fuzz \ + -p lucet-validate + +.PHONY: test-full +test-full: indent-check test-except-fuzz test-fuzz + +.PHONY: test-except-fuzz +test-except-fuzz: test-packages + cargo build -p lucet-spectest # build but *not* run spectests to mitigate bitrot while spectests don't pass + cargo test --benches -p lucet-benchmarks -- --test # run the benchmarks in debug mode + helpers/lucet-toolchain-tests/signature.sh + +# run a single seed through the fuzzer to stave off bitrot +.PHONY: test-fuzz +test-fuzz: + cargo run -p lucet-wasi-fuzz -- test-seed 410757864950 + +FUZZ_NUM_TESTS?=1000 +.PHONY: fuzz +fuzz: + cargo run --release -p lucet-wasi-fuzz -- fuzz --num-tests=$(FUZZ_NUM_TESTS) .PHONY: bench bench: + cargo bench -p lucet-benchmarks make -C benchmarks/shootout clean make -C benchmarks/shootout bench @@ -49,3 +77,20 @@ indent: .PHONY: indent-check indent-check: helpers/indent.sh check + +.PHONY: package +package: + cargo deb -p lucet-validate + cargo deb -p lucetc + +.PHONY: watch +watch: + cargo watch --exec "test \ + -p lucet-runtime-internals \ + -p lucet-runtime \ + -p lucet-module \ + -p lucetc \ + -p lucet-wasi-sdk \ + -p lucet-wasi \ + -p lucet-benchmarks \ + -p lucet-validate" diff --git a/ORG_CODE_OF_CONDUCT.md b/ORG_CODE_OF_CONDUCT.md new file mode 100644 index 000000000..e05e40c3c --- /dev/null +++ b/ORG_CODE_OF_CONDUCT.md @@ -0,0 +1,139 @@ +# 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. + + * **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. + + 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. diff --git a/README.md b/README.md index 1514dfd2d..d4c2813c3 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ # Lucet   [![Build Status]][travis] +**A [Bytecode Alliance][BA] project** + +[BA]: https://bytecodealliance.org/ [Build Status]: https://travis-ci.org/fastly/lucet.svg?branch=master [travis]: https://travis-ci.org/fastly/lucet -**Lucet is a native WebAssembly compiler and runtime. It is designed to safely -execute untrusted WebAssembly programs inside your application.** +**Lucet is a native WebAssembly compiler and runtime. It is designed +to safely execute untrusted WebAssembly programs inside your application.** Check out our [announcement post on the Fastly blog](https://www.fastly.com/blog/announcing-lucet-fastly-native-webassembly-compiler-runtime). @@ -12,279 +15,8 @@ blog](https://www.fastly.com/blog/announcing-lucet-fastly-native-webassembly-com Lucet uses, and is developed in collaboration with, Mozilla's [Cranelift](http://github.com/cranestation/cranelift) code generator. -Lucet powers Fastly's [Terrarium](https://wasm.fastlylabs.com) platform. - ---- - -## Status - -Lucet supports running WebAssembly programs written in C (via `clang`), Rust, -and AssemblyScript. It does not yet support the entire WebAssembly spec, but -full support is [coming in the near future](#lucet-spectest). - -Lucet's runtime currently only supports x86-64 based Linux systems. - -## Contents - -### `lucetc` - -`lucetc` is the Lucet Compiler. - -The Rust crate `lucetc` provides an executable `lucetc`. It compiles -WebAssembly modules (`.wasm` or `.wat` files) into native code (`.o` or `.so` -files). - -### `lucet-runtime` - -`lucet-runtime` is the runtime for WebAssembly modules compiled through -`lucetc`. It is a Rust crate that provides the functionality to load modules -from shared object files, instantiate them, and call exported WebAssembly -functions. `lucet-runtime` manages the resources used by each WebAssembly -instance (linear memory & globals), and the exception mechanisms that detect -and recover from illegal operations. - -The bulk of the library is defined in the child crate -`lucet-runtime-internals`. The public API is exposed in `lucet-runtime`. Test -suites are defined in the child crate `lucet-runtime-tests`. Many of these -tests invoke `lucetc` and the `wasi-sdk` tools. - -`lucet-runtime` is usable as a Rust crate or as a C library. The C language -interface is found at `lucet-runtime/include/lucet.h`. - -### `lucet-wasi` - -`lucet-wasi` is a crate providing runtime support for the [WebAssembly System -Interface (WASI)](https://wasi.dev). It can be used as a library to support -WASI in another application, or as an executable, `lucet-wasi`, to execute WASI -programs compiled through `lucetc`. - -See ["Your first Lucet application"](#your-first-lucet-application) for an -example that builds a C program and executes it with `lucet-wasi`. - -For details on WASI's implementation, see -[`lucet-wasi/README.md`](lucet-wasi/README.md). - -### `lucet-wasi-sdk` - -[`wasi-sdk`](https://github.com/cranestation/wasi-sdk) is a Cranelift project -that packages a build of the Clang toolchain, the WASI reference sysroot, and a -libc based on WASI syscalls. `lucet-wasi-sdk` is a Rust crate that provides -wrappers around these tools for building C programs into Lucet modules. We use -this crate to build test cases in `lucet-runtime-tests` and `lucet-wasi`. - -### `lucet-module-data` - -`lucet-module-data` is a crate with data structure definitions and serialization -functions that we emit into shared objects with `lucetc`, and read with -`lucet-runtime`. - -### `lucet-analyze` - -`lucet-analyze` is a Rust executable for inspecting the contents of a shared -object generated by `lucetc`. - -### `lucet-idl` - -`lucet-idl` is a Rust executable that implements code generation via an -Interface Description Language (IDL). The generated code provides zero-copy -accessor and constructor functions for datatypes that have the same -representation in both the WebAssembly guest program and the host program. - -Functionality is incomplete at the time of writing, and not yet integrated with -other parts of the project. Rust code generator, definition of import and -export function interfaces, and opaque type definitions are planned for the -near future. - -### `lucet-spectest` - -`lucet-spectest` is a Rust crate that uses `lucetc` and `lucet-runtime`, as well -as the (external) `wabt` crate, to run the official WebAssembly spec test suite, -which is provided as a submodule in this directory. Lucet is not yet fully spec -compliant, and the implementation of `lucet-spectest` has not been maintained -very well during recent codebase evolutions. We expect to fix this up and reach -spec compliance in the near future. - -### `lucet-builtins` - -`lucet-builtins` is a C library that provides optimized native versions of libc -primitives. `lucetc` can substitute the implementations defined in this library -for the WebAssembly implementations. - -`lucet-builtins/wasmonkey` is the Rust crate that `lucetc` uses to transform -function definitions in a WebAssembly module into uses of an import function. - -### Vendor libraries - -Lucet is tightly coupled to several upstream dependencies, and Lucet -development often requires making changes to these dependencies which are -submitted upstream once fully baked. To reduce friction in this development -cycle, we use git submodules to vendor these modules into the Lucet source -tree. - -#### Cranelift - -We keep the primary Cranelift project repository as a submodule at -`/cranelift`. - -Cranelift provides the native code generator used by `lucetc`, and a ton of -supporting infrastructure. - -Cranelift was previously known as Cretonne. Project developers hang out in the -`#cranelift` channel on [`irc.mozilla.org:6697`](https://wiki.mozilla.org/IRC). - -#### Faerie - -`faerie` is a Rust crate for producing ELF files. Faerie is used by Cranelift -(through the module system's `cranelift-faerie` backend) and also directly by -`lucetc`, for places where the `cranelift-module` API can't do everything we -need. - -### Tests - -Most of the crates in this repository have some form of unit tests. In addition, -`lucet-runtime/lucet-runtime-tests` defines a number of integration tests for -the runtime, and `lucet-wasi` has a number of integration tests using WASI C -programs. - -### Benchmarks - -We created the `sightglass` benchmarking tool to measure the runtime of C code -compiled through a standard native toolchain against the Lucet toolchain. It -is provided as a submodule at `/sightglass`. - -Sightglass ships with a set of microbenchmarks called `shootout`. The scripts -to build the shootout tests with native and various versions of the Lucet -toolchain are in `/benchmarks/shootout`. - -## Development Environment - -### Operating System - -Lucet is developed and tested on Linux. We expect it to work on any POSIX -system which supports ELF. - -Experimentally, we have shown that supporting Mac OS (which uses the Mach-O -executable format instead of ELF) is possible, but it is not supported at this -time. - -### Dependencies - -Lucet requires: - -* Stable Rust, and `rustfmt`. We typically track the latest stable release. -* [`wasi-sdk`](https://github.com/CraneStation/wasi-sdk), providing a Clang - toolchain with wasm-ld, the WASI reference sysroot, and a libc based on WASI - syscalls. -* GNU Make, CMake, & various standard Unix utilities for the build system -* `libhwloc`, for sightglass to pin benchmarks to a single core - -### Getting started - -The easiest way to get started with the Lucet toolchain is by using the provided -Docker-based development environment. - -This repository includes a `Dockerfile` to build a complete environment for -compiling and running WebAssembly code with Lucet, but you shouldn't have to use -Docker commands directly. A set of shell scripts with the `devenv_` prefix are -used to manage the container. - -#### Setting up the environment - -0) The Lucet repository uses git submodules. Make sure they are checked out - by running `git submodule init && git submodule update`. - -1) Install and run the `docker` service. We do not support `podman` at this - time. On MacOS, [Docker for - Mac](https://docs.docker.com/docker-for-mac/install/) is an option. - -2) Once Docker is running, in a terminal, and at the root of the cloned - repository, run: `source devenv_setenv.sh`. (This command requires the - current shell to be `zsh`, `ksh` or `bash`). After a couple minutes, the - Docker image is built and a new container is run. - -3) Check that new commands are now available: - -```sh -lucetc --help -``` - -You're now all set! - -#### Your first Lucet application - -The `devenv_setenv.sh` shell script ensures the Lucet executables are available -in your shell. Under the hood, these commands are executed in the Docker -container. The container has limited visibility into the host's filesystem - it -can only see files under the `lucet` repository. - -Create a new work directory in the `lucet` directory: - -```sh -mkdir -p src/hello - -cd src/hello -``` - -Save the following C source code as `hello.c`: - -```c -#include - -int main(void) -{ - puts("Hello world"); - return 0; -} -``` - -Time to compile to WebAssembly! The development environment includes a version -of the Clang toolchain that is built to generate WebAssembly by default. The -related commands are accessible from your current shell, and are prefixed by -`wasm32-unknown-wasi-`. - -For example, to create a WebAssembly module `hello.wasm` from `hello.c`: - -```sh -wasm32-unknown-wasi-clang -Ofast -o hello.wasm hello.c -``` - -The next step is to use Lucet to build native `x86_64` code from that -WebAssembly file: - -```sh -lucetc-wasi -o hello.so hello.wasm -``` - -`lucetc` is the WebAssembly to native code compiler. The `lucetc-wasi` command -runs the same compiler, but automatically configures it to target WASI. - -`hello.so` is created and ready to be run: - -```sh -lucet-wasi hello.so -``` - -#### Additional shell commands - -* `./devenv_build_container.sh` rebuilds the container image. This is never - required unless you edit the `Dockerfile`. -* `./devenv_run.sh [] [...]` runs a command in the container. If - a command is not provided, an interactive shell is spawned. In this - container, Lucet tools are installed in `/opt/lucet` by default. The command - `source /opt/lucet/bin/lucet_setenv.sh` can be used to initialize the - environment. -* `./devenv_start.sh` and `./devenv_stop.sh` start and stop the container. - -## Security - -The lucet project aims to provide support for secure execution of untrusted code. Security is achieved through a combination of lucet-supplied security controls and user-supplied security controls. See [SECURITY.md](SECURITY.md) for more information on the lucet security model. +It powers Fastly's [Terrarium](https://wasm.fastlylabs.com) platform. -### Reporting Security Issues +Lucet's documentation is available in the Wiki: -The Lucet project team welcomes security reports and is committed to providing -prompt attention to security issues. Security issues should be reported -privately via [Fastly’s security issue reporting -process](https://www.fastly.com/security/report-security-issue). Remediation of -security vulnerabilities is prioritized. The project teams endeavors to -coordinate remediation with third-party stakeholders, and is committed to -transparency in the disclosure process. +# [Lucet documentation](https://github.com/fastly/lucet/wiki) diff --git a/assemblyscript/examples/signatures/README.md b/assemblyscript/examples/signatures/README.md index 9c69f0b02..e1e8738b0 100644 --- a/assemblyscript/examples/signatures/README.md +++ b/assemblyscript/examples/signatures/README.md @@ -24,6 +24,7 @@ npm install npm run asbuild:optimized lucetc -o example \ + --reserved-size=64MB \ --bindings /opt/lucet/share/assemblyscript/modules/wasa/bindings.json \ build/optimized.wasm ``` @@ -31,7 +32,7 @@ lucetc -o example \ ## Running the example using lucet-wasi ```sh -lucet-wasi --entrypoint main --dir /lucet:/lucet example help +lucet-wasi --entrypoint main --dir .:. example help ``` Unlike C and Rust applications, AssemblyScript requires an entry point to be explicitly defined. @@ -43,7 +44,7 @@ Since files are going to be read and written to, a descriptor to a pre-opened di This is the purpose of the `--dir` command-line option. Without this, the webassembly module cannot access the filesystem at all. -Here, the `/lucet` virtual directory, as seen by the application, maps to the `/lucet` directory in the container. +Here, the current virtual directory (`.`), as seen by the application, maps to the current directory in the container. ## What the example does @@ -54,7 +55,7 @@ The main code is in `assembly/index.ts`. Key pair creation: ```sh -lucet-wasi --entrypoint main --dir /lucet:/lucet example keypair +lucet-wasi --entrypoint main --dir .:. example keypair ``` ```text @@ -63,7 +64,7 @@ Key pair created and saved into [keypair.bin] Public key: [94b8eb14373eb245c1daaacb2c24e2cb554bdd723009423aae5a8ca5fa99fa16] ``` -A new file `keypair.bin` is created on the local filesystem, at the root of the first mount point (`/lucet`). +A new file `keypair.bin` is created on the local filesystem, at the root of the first mount point (the current directory). An environment variable called `KEYPAIR_FILE`, can be used in order to change the file name and location. @@ -72,21 +73,23 @@ The WASA wrapper automatically sets the minimum required WASI capabilities in or File signature: ```sh -lucet-wasi --entrypoint main --dir /lucet:/lucet example sign LICENSE +lucet-wasi --entrypoint main --dir .:. example sign README.md ``` ```text Signature for that file: [deedf3910d5b166ca17e0e307312a422cb50efcbcc90754cf0e2d528a9159c4ad3ac973e3cd9b2c2986fb2e467a0506bc9a5ceb9c7d6d30e360fb4d1cef3c50d] ``` -This command reads the `/lucet/LICENSE` file, as well as the key pair, and computes a signature of the file's content that can be verified using the public key. +This command reads the `README.md` file, as well as the key pair, and computes a signature of the file's content that can be verified using the public key. Signature verification: ```sh -lucet-wasi --entrypoint main --dir /lucet:/lucet example verify LICENSE 94b8eb14373eb245c1daaacb2c24e2cb554bdd723009423aae5a8ca5fa99fa16 deedf3910d5b166ca17e0e307312a422cb50efcbcc90754cf0e2d528a9159c4ad3ac973e3cd9b2c2986fb2e467a0506bc9a5ceb9c7d6d30e360fb4d1cef3c50d +lucet-wasi --entrypoint main --dir .:. example verify README.md ``` +`` and `` must be replaced with output from the previous commands. + ```text This is a valid signature for that file ``` diff --git a/assemblyscript/examples/signatures/assembly/index.ts b/assemblyscript/examples/signatures/assembly/index.ts index b5f0e0170..2644afd47 100644 --- a/assemblyscript/examples/signatures/assembly/index.ts +++ b/assemblyscript/examples/signatures/assembly/index.ts @@ -1,11 +1,8 @@ -import 'allocator/arena'; -export { memory }; - import { SIGN_RANDBYTES, SIGN_SEEDBYTES, SIGN_KEYPAIRBYTES, SIGN_BYTES, SIGN_PUBLICKEYBYTES, signKeypairFromSeed, signPublicKey, sign, signVerify, bin2hex, hex2bin } from "./wasm-crypto/crypto"; -import { Console, Random, CommandLine, Process, Filesystem, IO, Environ } +import { Console, Random, CommandLine, Process, FileSystem, Environ } from "../../../modules/wasa/assembly/wasa"; /** @@ -16,17 +13,17 @@ function createKeypair(keypair_file: string): void { Console.log("Creating a new keypair..."); let seed = Random.randomBytes(SIGN_SEEDBYTES); let keypair = signKeypairFromSeed(seed); - let fd = Filesystem.openForWrite(keypair_file); + let fd = FileSystem.open(keypair_file, "w"); if (fd === null) { Console.error("Unable to create the keypair file"); Process.exit(1); } - let keypair_array: Array = []; + let keypair_array = new Array(keypair.length); for (let i = 0; i < keypair.length; i++) { - keypair_array[i] = unchecked(keypair[i]); + keypair_array[i] = keypair[i]; } - IO.write(fd!, keypair_array); - IO.close(fd!); + fd!.write(keypair_array); + fd!.close(); Console.log("Key pair created and saved into [" + keypair_file + "]"); let pk_hex = bin2hex(signPublicKey(keypair)); Console.log("Public key: [" + pk_hex + "]"); @@ -38,37 +35,37 @@ function createKeypair(keypair_file: string): void { * @param keypair_file file containing a key pair */ function createSignature(file: string, keypair_file: string): void { - let fd = Filesystem.openForRead(keypair_file); + let fd = FileSystem.open(keypair_file, "r"); if (fd === null) { Console.error("Unable to read the keypair file"); Process.exit(1); } - let keypair_ = IO.readAll(fd!); - IO.close(fd!); - if (keypair_ === null || keypair_!.length !== SIGN_KEYPAIRBYTES) { + let keypair_ = fd!.readAll(); + fd!.close(); + if (keypair_ === null || keypair_.length !== SIGN_KEYPAIRBYTES) { Console.error("Invalid keypair file content"); Process.exit(1); } let keypair = new Uint8Array(SIGN_KEYPAIRBYTES); for (let i = 0; i < SIGN_KEYPAIRBYTES; i++) { - keypair[i] = unchecked(keypair_![i]); + keypair[i] = keypair_![i]; } - fd = Filesystem.openForRead(file); + fd = FileSystem.open(file, "r"); if (fd === null) { Console.error("Unable to open the file to sign"); Process.exit(1); } - let data_ = IO.readAll(fd!); + let data_ = fd!.readAll(); if (data_ === null) { Console.error("Error while reading the file to sign"); Process.exit(1); } - IO.close(fd!); + fd!.close(); let data = data_!; let data_len = data.length; let data_u8 = new Uint8Array(data.length); for (let i = 0; i < data_len; i++) { - data_u8[i] = unchecked(data[i]); + data_u8[i] = data[i]; } let z = Random.randomBytes(SIGN_RANDBYTES); let signature = sign(data_u8, keypair, z); @@ -84,31 +81,31 @@ function createSignature(file: string, keypair_file: string): void { */ function verifySignature(file: string, publickey_hex: string, signature_hex: string): void { let publickey = hex2bin(publickey_hex); - if (publickey === null || publickey!.length !== SIGN_PUBLICKEYBYTES) { + if (publickey === null || publickey.length !== SIGN_PUBLICKEYBYTES) { Console.error("Invalid public key"); Process.exit(1); } let signature = hex2bin(signature_hex); - if (signature === null || signature!.length !== SIGN_BYTES) { + if (signature === null || signature.length !== SIGN_BYTES) { Console.error("Invalid signature"); Process.exit(1); } - let fd = Filesystem.openForRead(file); + let fd = FileSystem.open(file, "r"); if (fd === null) { Console.error("Unable to open the file to sign"); Process.exit(1); } - let data_ = IO.readAll(fd!); + let data_ = fd!.readAll(); if (data_ === null) { Console.error("Error while reading the file to sign"); Process.exit(1); } - IO.close(fd!); + fd!.close(); let data = data_!; let data_len = data.length; let data_u8 = new Uint8Array(data.length); for (let i = 0; i < data_len; i++) { - data_u8[i] = unchecked(data[i]); + data_u8[i] = data[i]; } if (signVerify(signature!, data_u8, publickey!) === false) { Console.error("The signature didn't verify"); @@ -151,9 +148,9 @@ export function main(): void { keypair_file = "keypair.bin"; } if (command == "keypair") { - createKeypair(keypair_file); + createKeypair(keypair_file!); } else if (command == "sign" && args.length == 3) { - createSignature(args[2], keypair_file); + createSignature(args[2], keypair_file!); } else if (command == "verify" && args.length == 5) { verifySignature(args[2], args[3], args[4]); } else { diff --git a/assemblyscript/examples/signatures/assembly/wasm-crypto/__tests__/faRistrettoPoint.spec.ts b/assemblyscript/examples/signatures/assembly/wasm-crypto/__tests__/faRistrettoPoint.spec.ts index 5409b6b44..c3f9a659a 100644 --- a/assemblyscript/examples/signatures/assembly/wasm-crypto/__tests__/faRistrettoPoint.spec.ts +++ b/assemblyscript/examples/signatures/assembly/wasm-crypto/__tests__/faRistrettoPoint.spec.ts @@ -4,7 +4,7 @@ describe("Ristretto arithmetic", (): void => { for (let i = 0; i < 64; i++) { uniform[i] = i; } - let p = faPointFromUniform(uniform); + let p = faPointFromHash(uniform); expect(faPointValidate(p)).toBeTruthy(); p[0]++; expect(faPointValidate(p)).toBeFalsy(); @@ -14,7 +14,7 @@ describe("Ristretto arithmetic", (): void => { for (let i = 0; i < 64; i++) { uniform2[i] = ~i; } - let p2 = faPointFromUniform(uniform2); + let p2 = faPointFromHash(uniform2); expect(faPointValidate(p2)).toBeTruthy(); p2[0]++; expect(faPointValidate(p2)).toBeFalsy(); @@ -36,4 +36,4 @@ describe("Ristretto arithmetic", (): void => { let zero = faPointSub(p3, p); expect(faPointValidate(zero)).toBeFalsy(); }); -}); \ No newline at end of file +}); diff --git a/assemblyscript/examples/signatures/assembly/wasm-crypto/__tests__/hash.spec.ts b/assemblyscript/examples/signatures/assembly/wasm-crypto/__tests__/hash.spec.ts new file mode 100644 index 000000000..2a72c31a8 --- /dev/null +++ b/assemblyscript/examples/signatures/assembly/wasm-crypto/__tests__/hash.spec.ts @@ -0,0 +1,9 @@ +describe("hashing", (): void => { + it("should compute the hash of an empty string", (): void => { + let h = hashFinal(hashInit()); + let hex = bin2hex(h); + expect(hex).toBe( + "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e" + ); + }); +}); diff --git a/assemblyscript/examples/signatures/assembly/wasm-crypto/__tests__/utils.spec.ts b/assemblyscript/examples/signatures/assembly/wasm-crypto/__tests__/utils.spec.ts index 087331158..1a0b62586 100644 --- a/assemblyscript/examples/signatures/assembly/wasm-crypto/__tests__/utils.spec.ts +++ b/assemblyscript/examples/signatures/assembly/wasm-crypto/__tests__/utils.spec.ts @@ -37,11 +37,11 @@ describe("bin2hex", (): void => { describe("hex2bin", (): void => { it("shoud decode from hex", (): void => { let hex = "00050a0f14191e23282d32373c41464b50555a5f64696e7378"; - let bin = hex2bin(hex)!; + let bin = hex2bin(hex); let ref = new Uint8Array(25); for (let i = 0; i < 25; i++) { ref[i] = i * 5; } - expect(equals(ref, bin)).toBeTruthy(); + expect(equals(ref, bin!)).toBeTruthy(); }) -}) \ No newline at end of file +}) diff --git a/assemblyscript/examples/signatures/assembly/wasm-crypto/crypto.ts b/assemblyscript/examples/signatures/assembly/wasm-crypto/crypto.ts index 18c067e20..b5ba3ed27 100644 --- a/assemblyscript/examples/signatures/assembly/wasm-crypto/crypto.ts +++ b/assemblyscript/examples/signatures/assembly/wasm-crypto/crypto.ts @@ -1,11 +1,12 @@ // tslint:disable-next-line:no-reference /// -import { LOAD, STORE } from 'internal/arraybuffer'; import { precompBase } from './precomp'; const RELEASE: bool = true; +export const U8ARRAY_ID = idof(); + // Helpers @inline function setU8(t: Uint8Array, s: Uint8Array, o: isize = 0): void { @@ -40,12 +41,12 @@ const RELEASE: bool = true; return (x & y) ^ (x & z) ^ (y & z); } -function load64(x: Uint8Array, offset: isize): u64 { - return LOAD(x.buffer, 0, offset); +function load64_be(x: Uint8Array, offset: isize): u64 { + return bswap(load(changetype(x.buffer) + offset)); } -function store64(x: Uint8Array, offset: isize, u: u64): void { - STORE(x.buffer, 0, u, offset); +function store64_be(x: Uint8Array, offset: isize, u: u64): void { + store(changetype(x.buffer) + offset, bswap(u)); } const K: u64[] = [ @@ -79,12 +80,12 @@ function _hashblocks(st: Uint8Array, m: Uint8Array, n: isize): isize { t: u64; for (let i = 0; i < 8; ++i) { - z[i] = a[i] = load64(st, i << 3); + z[i] = a[i] = load64_be(st, i << 3); } let pos = 0; while (n >= 128) { for (let i = 0; i < 16; ++i) { - w[i] = load64(m, (i << 3) + pos); + w[i] = load64_be(m, (i << 3) + pos); } for (let i = 0; i < 80; ++i) { for (let j = 0; j < 8; ++j) { @@ -110,7 +111,7 @@ function _hashblocks(st: Uint8Array, m: Uint8Array, n: isize): isize { n -= 128; } for (let i = 0; i < 8; ++i) { - store64(st, i << 3, z[i]); + store64_be(st, i << 3, z[i]); } return n; } @@ -138,14 +139,13 @@ function _hashInit(): Uint8Array { function _hashUpdate(st: Uint8Array, m: Uint8Array, n: isize, r: isize): isize { let w = st.subarray(64); - let pos = 0; let av = 128 - r; let tc = min(n, av); setU8(w, m.subarray(0, tc), r); r += tc; n -= tc; - pos += tc; + let pos = tc; if (r === 128) { _hashblocks(st, w, 128); r = 0; @@ -153,8 +153,8 @@ function _hashUpdate(st: Uint8Array, m: Uint8Array, n: isize, r: isize): isize { if (r === 0 && n > 0) { let rb = _hashblocks(st, m.subarray(pos), n); if (rb > 0) { - setU8(w, m.subarray(pos + n - rb), r); - r += rb; + setU8(w, m.subarray(pos + n - rb)); + r = rb; } } return r; @@ -168,7 +168,7 @@ function _hashFinal(st: Uint8Array, out: Uint8Array, t: isize, r: isize): void { x[r] = 128; r = 256 - (isize(r < 112) << 7); x[r - 9] = 0; - store64(x, r - 8, t << 3); + store64_be(x, r - 8, t << 3); _hashblocks(st, x, r); for (let i = 0; i < 64; ++i) { out[i] = st[i]; @@ -574,9 +574,9 @@ function fe25519Pack(o: Fe25519Packed, n: Fe25519): void { } function fe25519Unpack(o: Fe25519, n: Fe25519Packed): void { - let nb = n.buffer; + let nb = changetype(n.buffer); for (let i = 0; i < 16; ++i) { - o[i] = LOAD(nb, i); + o[i] = load(nb + 2 * i) as i64; } o[15] &= 0x7fff; } @@ -832,7 +832,7 @@ function scalarmult(p: Ge, s: ScalarPacked, q: Ge): void { } } -@inline function fe25519CopyA(r: Fe25519, a: Array): void { +@inline function fe25519CopyPrecomp(r: Fe25519, a: Array): void { r[0] = unchecked(a[0]); r[1] = unchecked(a[1]); r[2] = unchecked(a[2]); @@ -867,10 +867,9 @@ function scalarmultBase(p: Ge, s: ScalarPacked): void { for (let i = 0; i <= 255; ++i) { b = (s[(i >>> 3)] >>> (i as u8 & 7)) & 1; let precomp = precomp_base[i]; - - fe25519CopyA(q.x, precomp[0]); - fe25519CopyA(q.y, precomp[1]); - fe25519CopyA(q.t, precomp[2]); + fe25519CopyPrecomp(q.x, precomp[0]); + fe25519CopyPrecomp(q.y, precomp[1]); + fe25519CopyPrecomp(q.t, precomp[2]); geCopy(t, p); add(t, q); cmov(p, t, b); @@ -1124,9 +1123,9 @@ function ristrettoElligator(p: Ge, t: Fe25519): void { fe25519Mult(p.t, w0, w2); } -type Uniform = Uint8Array(64); +type Hash512 = Uint8Array(64); -function ristrettoFromUniform(s: GePacked, r: Uniform): void { +function ristrettoFromHash(s: GePacked, r: Hash512): void { let r0 = newFe25519(), r1 = newFe25519(); let p0 = newGe(), p1 = newGe(); @@ -1613,14 +1612,14 @@ function _signVerifyDetached(sig: Signature, m: Uint8Array, pk: GePacked): bool * @param m (partial) message */ @global export function hashUpdate(st: Uint8Array, m: Uint8Array): void { - let r = load64(st, 64 + 128); - let t = load64(st, 64 + 128 + 8); + let r = load64_be(st, 64 + 128); + let t = load64_be(st, 64 + 128 + 8); let n = m.length; t += n; r = _hashUpdate(st, m, n, r as isize); - store64(st, 64 + 128, r as u64); - store64(st, 64 + 128 + 8, t as u64); + store64_be(st, 64 + 128, r as u64); + store64_be(st, 64 + 128 + 8, t as u64); } /** @@ -1630,8 +1629,8 @@ function _signVerifyDetached(sig: Signature, m: Uint8Array, pk: GePacked): bool */ @global export function hashFinal(st: Uint8Array): Uint8Array { let h = new Uint8Array(HASH_BYTES); - let r = load64(st, 64 + 128); - let t = load64(st, 64 + 128 + 8); + let r = load64_be(st, 64 + 128); + let t = load64_be(st, 64 + 128 + 8); _hashFinal(st, h, t as isize, r as isize); @@ -2024,10 +2023,10 @@ function _signVerifyDetached(sig: Signature, m: Uint8Array, pk: GePacked): bool * @param r 512 bit hash * @returns Ristretto-compressed EC point */ -@global export function faPointFromUniform(r: Uint8Array): Uint8Array { +@global export function faPointFromHash(r: Uint8Array): Uint8Array { let p = newGePacked(); - ristrettoFromUniform(p, r); + ristrettoFromHash(p, r); return p; } diff --git a/assemblyscript/examples/signatures/package-lock.json b/assemblyscript/examples/signatures/package-lock.json new file mode 100644 index 000000000..a95025b20 --- /dev/null +++ b/assemblyscript/examples/signatures/package-lock.json @@ -0,0 +1,153 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", + "dev": true + }, + "assemblyscript": { + "version": "github:AssemblyScript/assemblyscript#678593d7bd8ee9573eadbd43e3635e0bc0b8e15e", + "from": "github:AssemblyScript/assemblyscript", + "dev": true, + "requires": { + "@protobufjs/utf8": "^1.1.0", + "binaryen": "84.0.0-nightly.20190522", + "glob": "^7.1.4", + "long": "^4.0.0", + "opencollective-postinstall": "^2.0.0", + "source-map-support": "^0.5.12" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "binaryen": { + "version": "84.0.0-nightly.20190522", + "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-84.0.0-nightly.20190522.tgz", + "integrity": "sha512-bxSPi3MOkFmK5W6VIlqxnOc1nYzpUCzT/tHz3C7sgbz7jTR2lOBlZnKStTJlBt018xeZK9/JpK/jXdduH7eQFg==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "opencollective-postinstall": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz", + "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/assemblyscript/examples/signatures/package.json b/assemblyscript/examples/signatures/package.json index dc4162078..548cf87de 100644 --- a/assemblyscript/examples/signatures/package.json +++ b/assemblyscript/examples/signatures/package.json @@ -1,13 +1,11 @@ { - "scripts": { - "asbuild:untouched": "asc assembly/index.ts -b build/untouched.wasm", - "asbuild:small": "asc assembly/index.ts -b build/small.wasm -t build/small.wat -O3z --importMemory", - "asbuild:optimized": "asc assembly/index.ts -b build/optimized.wasm -t build/optimized.wat -O3 --importMemory", - "asbuild": "npm run asbuild:optimized", - "test": "asp" - }, - "devDependencies": { - "as-pect": "github:jtenner/as-pect", - "assemblyscript": "github:AssemblyScript/assemblyscript" - } -} \ No newline at end of file + "scripts": { + "asbuild:untouched": "asc assembly/index.ts -b build/untouched.wasm --runtime stub --use abort=wasi_abort", + "asbuild:small": "asc assembly/index.ts -b build/small.wasm -t build/small.wat -O3z --runtime stub --use abort=wasi_abort", + "asbuild:optimized": "asc assembly/index.ts -b build/optimized.wasm -t build/optimized.wat -O3 --runtime stub --use abort=wasi_abort", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "github:AssemblyScript/assemblyscript" + } +} diff --git a/assemblyscript/modules/wasa/README.md b/assemblyscript/modules/wasa/README.md index 308cc5ddf..589c96df9 100644 --- a/assemblyscript/modules/wasa/README.md +++ b/assemblyscript/modules/wasa/README.md @@ -1,6 +1,6 @@ # An AssemblyScript layer for the WebAssembly System Interface (WASI) -[WASI](https://github.com/CraneStation/wasmtime-wasi/blob/wasi/docs/WASI-overview.md) is an API providing access to the external world to WebAssembly modules. +[WASI](https://wasi.dev) is an API providing access to the external world to WebAssembly modules. WASA is an effort to expose the WASI standard set of system calls to AssemblyScript. @@ -9,10 +9,9 @@ WASA is an effort to expose the WASI standard set of system calls to AssemblyScr Example usage of the `Console` and `Environ` classes: ```typescript -import "allocator/arena"; import { Console, Environ } from "../node_modules/wasa/assembly"; let env = new Environ(); -let home = env.get("HOME") as String; +let home = env.get("HOME")!; Console.log(home); -``` \ No newline at end of file +``` diff --git a/assemblyscript/modules/wasa/assembly/index.ts b/assemblyscript/modules/wasa/assembly/index.ts index 110b2f97f..a04fec277 100644 --- a/assemblyscript/modules/wasa/assembly/index.ts +++ b/assemblyscript/modules/wasa/assembly/index.ts @@ -1,5 +1,9 @@ -import { IO, Console, Random, Date, Process, EnvironEntry, Environ, CommandLine, Filesystem } from "./wasa"; +import { + WASAError, Descriptor, + Console, Random, Date, Process, EnvironEntry, Environ, CommandLine, FileSystem, FileStat +} from "./wasa"; export { - IO, Console, Random, Date, Process, EnvironEntry, Environ, CommandLine, Filesystem -} + WASAError, Descriptor, + Console, Random, Date, Process, EnvironEntry, Environ, CommandLine, FileSystem, FileStat +}; diff --git a/assemblyscript/modules/wasa/assembly/wasa.ts b/assemblyscript/modules/wasa/assembly/wasa.ts index 51599a1f5..c38cbe89a 100644 --- a/assemblyscript/modules/wasa/assembly/wasa.ts +++ b/assemblyscript/modules/wasa/assembly/wasa.ts @@ -1,178 +1,348 @@ -// The entry file of your WebAssembly module. - import { - errno, - clockid, - fd_write, - fd_read, - random_get, - clock_time_get, + advice, + args_get, + args_sizes_get, clock_res_get, - proc_exit, - environ_sizes_get, + clock_time_get, + clockid, + dircookie, environ_get, - args_sizes_get, - args_get, - path_open, - oflags, - rights, - lookupflags, + environ_sizes_get, + errno, + fd_advise, + fd_allocate, + fd_close, + fd_datasync, + fd_fdstat_get, + fd_fdstat_set_flags, + fd_filestat_get, + fd_filestat_set_size, + fd_filestat_set_times, + fd_prestat_dir_name, + fd_read, + fd_readdir, + fd_seek, + fd_sync, + fd_tell, + fd_write, fd, fdflags, - fd_close, -} from './wasi_unstable'; + fdstat, + whence, + filesize, + filestat, + filetype, + fstflags, + lookupflags, + oflags, + path_create_directory, + path_filestat_get, + path_link, + path_open, + path_rename, + path_remove_directory, + path_symlink, + path_unlink_file, + proc_exit, + random_get, + rights, +} from "bindings/wasi"; + +/** + * A WASA error + */ +export class WASAError extends Error { + constructor(message: string = "") { + super(message); + this.name = "WASAError"; + } +} -export type Descriptor = fd; +/** + * Portable information about a file + */ +export class FileStat { + file_type: filetype; + file_size: filesize; + access_time: f64; + modification_time: f64; + creation_time: f64; + + constructor(st_buf: usize) { + this.file_type = load(st_buf + 16); + this.file_size = load(st_buf + 24); + this.access_time = (load(st_buf + 32) as f64) / 1e9; + this.modification_time = (load(st_buf + 40) as f64) / 1e9; + this.creation_time = (load(st_buf + 48) as f64) / 1e9; + } +} -export class Filesystem { +/** + * A descriptor, that doesn't necessarily have to represent a file + */ +export class Descriptor { /** - * A simplified interface to open a file for read operations - * @param path Path - * @param dirfd Base directory descriptor (will be automatically set soon) + * An invalid file descriptor, that can represent an error */ - static openForRead(path: string, dirfd: Descriptor = 3): Descriptor | null { - let fd_lookup_flags = lookupflags.SYMLINK_FOLLOW; - let fd_oflags: oflags = 0; - let fd_rights = rights.FD_READ | rights.FD_SEEK | - rights.FD_TELL | rights.FD_FILESTAT_GET; - let fd_rights_inherited = fd_rights; - let fd_flags: fdflags = 0; - let path_utf8_len: usize = path.lengthUTF8 - 1; - let path_utf8 = path.toUTF8(); - let fd_buf = memory.allocate(sizeof()); - let res = path_open(dirfd as fd, fd_lookup_flags, path_utf8, path_utf8_len, - fd_oflags, fd_rights, fd_rights_inherited, fd_flags, fd_buf); - if (res != errno.SUCCESS) { - return null; + static Invalid(): Descriptor { return new Descriptor(-1); }; + + /** + * The standard input + */ + static Stdin(): Descriptor { return new Descriptor(0); }; + + /** + * The standard output + */ + static Stdout(): Descriptor { return new Descriptor(1); }; + + /** + * The standard error + */ + static Stderr(): Descriptor { return new Descriptor(2); }; + + /** + * Build a new descriptor from a raw WASI file descriptor + * @param rawfd a raw file descriptor + */ + constructor(readonly rawfd: fd) { } + + /** + * Hint at how the data accessible via the descriptor will be used + * @offset offset + * @len length + * @advice `advice.{NORMAL, SEQUENTIAL, RANDOM, WILLNEED, DONTNEED, NOREUSE}` + * @returns `true` on success, `false` on error + */ + advise(offset: u64, len: u64, advice: advice): bool { + return fd_advise(this.rawfd, offset, len, advice) === errno.SUCCESS; + } + + /** + * Preallocate data + * @param offset where to start preallocating data in the file + * @param len bytes to preallocate + * @returns `true` on success, `false` on error + */ + allocate(offset: u64, len: u64): bool { + return fd_allocate(this.rawfd, offset, len) === errno.SUCCESS; + } + + /** + * Wait for the data to be written + * @returns `true` on success, `false` on error + */ + fdatasync(): bool { + return fd_datasync(this.rawfd) === errno.SUCCESS; + } + + /** + * Wait for the data and metadata to be written + * @returns `true` on success, `false` on error + */ + fsync(): bool { + return fd_sync(this.rawfd) === errno.SUCCESS; + } + + /** + * Return the file type + */ + fileType(): filetype { + let st_buf = changetype(new ArrayBuffer(24)); + if (fd_fdstat_get(this.rawfd, changetype(st_buf)) !== errno.SUCCESS) { + throw new WASAError("Unable to get the file type"); } - let fd = load(fd_buf); - memory.free(fd_buf); + let file_type: u8 = load(st_buf); - return fd as Descriptor; + return file_type; } /** - * A simplified interface to open a file for write operations - * @param path Path - * @param dirfd Base directory descriptor (will be automatically set soon) - */ - static openForWrite(path: string, dirfd: Descriptor = 3): Descriptor | null { - let fd_lookup_flags = lookupflags.SYMLINK_FOLLOW; - let fd_oflags: oflags = oflags.CREAT; - let fd_rights = rights.FD_WRITE | rights.FD_SEEK | - rights.FD_TELL | rights.FD_FILESTAT_GET | rights.PATH_CREATE_FILE; - let fd_rights_inherited = fd_rights; - let fd_flags: fdflags = 0; - let path_utf8_len: usize = path.lengthUTF8 - 1; - let path_utf8 = path.toUTF8(); - let fd_buf = memory.allocate(sizeof()); - let res = path_open(dirfd as fd, fd_lookup_flags, path_utf8, path_utf8_len, - fd_oflags, fd_rights, fd_rights_inherited, fd_flags, fd_buf); - if (res != errno.SUCCESS) { - return null; + * Set WASI flags for that descriptor + * @params flags: one or more of `fdflags.{APPEND, DSYNC, NONBLOCK, RSYNC, SYNC}` + * @returns `true` on success, `false` on error + */ + setFlags(flags: fdflags): bool { + return fd_fdstat_set_flags(this.rawfd, flags) === errno.SUCCESS; + } + + /** + * Retrieve information about a descriptor + * @returns a `FileStat` object` + */ + stat(): FileStat { + let st_buf = changetype(new ArrayBuffer(56)); + if (fd_filestat_get(this.rawfd, changetype(st_buf)) !== errno.SUCCESS) { + throw new WASAError("Unable to get the file information"); } - let fd = load(fd_buf); - memory.free(fd_buf); + return new FileStat(st_buf); + } - return fd as Descriptor; + /** + * Change the size of a file + * @param size new size + * @returns `true` on success, `false` on error + */ + ftruncate(size: u64 = 0): bool { + return fd_filestat_set_size(this.rawfd, size) === errno.SUCCESS; + } + + /** + * Update the access time + * @ts timestamp in seconds + * @returns `true` on success, `false` on error + */ + fatime(ts: f64): bool { + return ( + fd_filestat_set_times(this.rawfd, (ts * 1e9) as u64, 0, fstflags.SET_ATIM) === + errno.SUCCESS + ); + } + + /** + * Update the modification time + * @ts timestamp in seconds + * @returns `true` on success, `false` on error + */ + fmtime(ts: f64): bool { + return ( + fd_filestat_set_times(this.rawfd, 0, (ts * 1e9) as u64, fstflags.SET_MTIM) === + errno.SUCCESS + ); + } + + /** + * Update both the access and the modification times + * @atime timestamp in seconds + * @mtime timestamp in seconds + * @returns `true` on success, `false` on error + */ + futimes(atime: f64, mtime: f64): bool { + return ( + fd_filestat_set_times(this.rawfd, (atime * 1e9) as u64, (mtime * 1e9) as u64, + fstflags.SET_ATIM | fstflags.SET_ATIM) === errno.SUCCESS + ); + } + + /** + * Update the timestamp of the object represented by the descriptor + * @returns `true` on success, `false` on error + */ + touch(): bool { + return ( + fd_filestat_set_times( + this.rawfd, + 0, + 0, + fstflags.SET_ATIM_NOW | fstflags.SET_MTIM_NOW + ) === errno.SUCCESS + ); + } + + /** + * Return the directory associated to that descriptor + */ + dirName(): String { + let path_max: usize = 4096; + for (; ;) { + let path_buf = changetype(new ArrayBuffer(path_max)); + let ret = fd_prestat_dir_name(this.rawfd, path_buf, path_max); + if (ret === errno.NAMETOOLONG) { + path_max = path_max * 2; + continue; + } + let path_len = 0; + while (load(path_buf + path_len) !== 0) { + path_len++; + } + return String.UTF8.decodeUnsafe(path_buf, path_len); + } } -} -export class IO { /** * Close a file descriptor - * @param fd file descriptor */ - static close(fd: Descriptor): void { - fd_close(fd); + close(): void { + fd_close(this.rawfd); } /** * Write data to a file descriptor - * @param fd file descriptor * @param data data */ - static write(fd: Descriptor, data: Array): void { + write(data: Array): void { let data_buf_len = data.length; - let data_buf = memory.allocate(data_buf_len); + let data_buf = changetype(new ArrayBuffer(data_buf_len)); for (let i = 0; i < data_buf_len; i++) { store(data_buf + i, unchecked(data[i])); } - let iov = memory.allocate(2 * sizeof()); + let iov = changetype(new ArrayBuffer(2 * sizeof())); store(iov, data_buf); store(iov + sizeof(), data_buf_len); - let written_ptr = memory.allocate(sizeof()); - fd_write(fd, iov, 1, written_ptr); - memory.free(written_ptr); - memory.free(data_buf); + + let written_ptr = changetype(new ArrayBuffer(sizeof())); + fd_write(this.rawfd, iov, 1, written_ptr); } /** - * Write a string to a file descriptor, after encoding it to UTF8 - * @param fd file descriptor - * @param s string - * @param newline `true` to add a newline after the string - */ - static writeString(fd: Descriptor, s: string, newline: bool = false): void { + * Write a string to a file descriptor, after encoding it to UTF8 + * @param s string + * @param newline `true` to add a newline after the string + */ + writeString(s: string, newline: bool = false): void { if (newline) { - this.writeStringLn(fd, s); + this.writeStringLn(s); return; } - let s_utf8_len: usize = s.lengthUTF8 - 1; - let s_utf8 = s.toUTF8(); - let iov = memory.allocate(2 * sizeof()); + let s_utf8_len: usize = String.UTF8.byteLength(s); + let s_utf8 = changetype(String.UTF8.encode(s)); + let iov = changetype(new ArrayBuffer(2 * sizeof())); store(iov, s_utf8); store(iov + sizeof(), s_utf8_len); - let written_ptr = memory.allocate(sizeof()); - fd_write(fd, iov, 1, written_ptr); - memory.free(written_ptr); - memory.free(s_utf8); + let written_ptr = changetype(new ArrayBuffer(sizeof())); + fd_write(this.rawfd, iov, 1, written_ptr); } /** * Write a string to a file descriptor, after encoding it to UTF8, with a newline - * @param fd file descriptor * @param s string */ - static writeStringLn(fd: Descriptor, s: string): void { - let s_utf8_len: usize = s.lengthUTF8 - 1; - let s_utf8 = s.toUTF8(); - let iov = memory.allocate(4 * sizeof()); + writeStringLn(s: string): void { + let s_utf8_len: usize = String.UTF8.byteLength(s); + let s_utf8 = changetype(String.UTF8.encode(s)); + let iov = changetype(new ArrayBuffer(4 * sizeof())); store(iov, s_utf8); store(iov + sizeof(), s_utf8_len); - let lf = memory.allocate(1); + let lf = changetype(new ArrayBuffer(1)); store(lf, 10); store(iov + sizeof() * 2, lf); store(iov + sizeof() * 3, 1); - let written_ptr = memory.allocate(sizeof()); - fd_write(fd, iov, 2, written_ptr); - memory.free(written_ptr); - memory.free(s_utf8); + let written_ptr = changetype(new ArrayBuffer(sizeof())); + fd_write(this.rawfd, iov, 2, written_ptr); } /** * Read data from a file descriptor - * @param fd file descriptor * @param data existing array to push data to * @param chunk_size chunk size (default: 4096) */ - static read(fd: Descriptor, data: Array = [], chunk_size: usize = 4096): Array | null { + read( + data: Array = [], + chunk_size: usize = 4096 + ): Array | null { let data_partial_len = chunk_size; - let data_partial = memory.allocate(data_partial_len); - let iov = memory.allocate(2 * sizeof()); + let data_partial = changetype(new ArrayBuffer(data_partial_len)); + let iov = changetype(new ArrayBuffer(2 * sizeof())); store(iov, data_partial); store(iov + sizeof(), data_partial_len); - let read_ptr = memory.allocate(sizeof()); - fd_read(fd, iov, 1, read_ptr); + let read_ptr = changetype(new ArrayBuffer(sizeof())); + fd_read(this.rawfd, iov, 1, read_ptr); let read = load(read_ptr); if (read > 0) { for (let i: usize = 0; i < read; i++) { data.push(load(data_partial + i)); } } - memory.free(read_ptr); - memory.free(data_partial); - if (read <= 0) { return null; } @@ -181,20 +351,22 @@ export class IO { /** * Read from a file descriptor until the end of the stream - * @param fd file descriptor * @param data existing array to push data to * @param chunk_size chunk size (default: 4096) */ - static readAll(fd: Descriptor, data: Array = [], chunk_size: usize = 4096): Array | null { + readAll( + data: Array = [], + chunk_size: usize = 4096 + ): Array | null { let data_partial_len = chunk_size; - let data_partial = memory.allocate(data_partial_len); - let iov = memory.allocate(2 * sizeof()); + let data_partial = changetype(new ArrayBuffer(data_partial_len)); + let iov = changetype(new ArrayBuffer(2 * sizeof())); store(iov, data_partial); store(iov + sizeof(), data_partial_len); - let read_ptr = memory.allocate(sizeof()); + let read_ptr = changetype(new ArrayBuffer(sizeof())); let read: usize = 0; for (; ;) { - if (fd_read(fd, iov, 1, read_ptr) != errno.SUCCESS) { + if (fd_read(this.rawfd, iov, 1, read_ptr) !== errno.SUCCESS) { break; } read = load(read_ptr); @@ -205,9 +377,6 @@ export class IO { data.push(load(data_partial + i)); } } - memory.free(read_ptr); - memory.free(data_partial); - if (read < 0) { return null; } @@ -216,25 +385,311 @@ export class IO { /** * Read an UTF8 string from a file descriptor, convert it to a native string - * @param fd file descriptor * @param chunk_size chunk size (default: 4096) */ - static readString(fd: Descriptor, chunk_size: usize = 4096): string | null { - let s_utf8_ = IO.readAll(fd); - if (s_utf8_ === null) { + readString(chunk_size: usize = 4096): string | null { + let s_utf8 = this.readAll(); + if (s_utf8 === null) { return null; } - let s_utf8 = s_utf8_!; let s_utf8_len = s_utf8.length; - let s_utf8_buf = memory.allocate(s_utf8_len); + let s_utf8_buf = changetype(new ArrayBuffer(s_utf8_len)); for (let i = 0; i < s_utf8_len; i++) { store(s_utf8_buf + i, s_utf8[i]); } - let s = String.fromUTF8(s_utf8_buf, s_utf8.length); - memory.free(s_utf8_buf); + let s = String.UTF8.decodeUnsafe(s_utf8_buf, s_utf8.length); return s; } + + /** + * Seek into a stream + * @off offset + * @w the position relative to which to set the offset of the file descriptor. + */ + seek(off: u64, w: whence): bool { + let fodder = changetype(new ArrayBuffer(8)); + let res = fd_seek(this.rawfd, off, w, fodder); + + return res === errno.SUCCESS; + } + + /** + * Return the current offset in the stream + * @returns offset + */ + tell(): u64 { + let buf_off = changetype(new ArrayBuffer(8)); + let res = fd_tell(this.rawfd, buf_off); + if (res !== errno.SUCCESS) { + abort(); + } + return load(buf_off); + } +} + +/** + * A class to access a filesystem + */ +export class FileSystem { + /** + * Open a path + * @path path + * @flags r, r+, w, wx, w+ or xw+ + * @returns a descriptor + */ + static open(path: string, flags: string = "r"): Descriptor | null { + let dirfd = this.dirfdForPath(path); + let fd_lookup_flags = lookupflags.SYMLINK_FOLLOW; + let fd_oflags: u16 = 0; + let fd_rights: u64 = 0; + if (flags === "r") { + fd_rights = rights.FD_READ | rights.FD_SEEK | rights.FD_TELL | rights.FD_FILESTAT_GET | rights.FD_READDIR; + } else if (flags === "r+") { + fd_rights = + rights.FD_READ | rights.FD_SEEK | rights.FD_TELL | rights.FD_FILESTAT_GET | rights.FD_WRITE | + rights.FD_SEEK | rights.FD_TELL | rights.FD_FILESTAT_GET | rights.PATH_CREATE_FILE; + } else if (flags === "w") { + fd_oflags = oflags.CREAT | oflags.TRUNC; + fd_rights = rights.FD_WRITE | rights.FD_SEEK | rights.FD_TELL | rights.FD_FILESTAT_GET | rights.PATH_CREATE_FILE; + } else if (flags === "wx") { + fd_oflags = oflags.CREAT | oflags.TRUNC | oflags.EXCL; + fd_rights = rights.FD_WRITE | rights.FD_SEEK | rights.FD_TELL | rights.FD_FILESTAT_GET | rights.PATH_CREATE_FILE; + } else if (flags === "w+") { + fd_oflags = oflags.CREAT | oflags.TRUNC; + fd_rights = + rights.FD_READ | rights.FD_SEEK | rights.FD_TELL | rights.FD_FILESTAT_GET | rights.FD_WRITE | + rights.FD_SEEK | rights.FD_TELL | rights.FD_FILESTAT_GET | rights.PATH_CREATE_FILE; + } else if (flags === "xw+") { + fd_oflags = oflags.CREAT | oflags.TRUNC | oflags.EXCL; + fd_rights = + rights.FD_READ | rights.FD_SEEK | rights.FD_TELL | rights.FD_FILESTAT_GET | rights.FD_WRITE | + rights.FD_SEEK | rights.FD_TELL | rights.FD_FILESTAT_GET | rights.PATH_CREATE_FILE; + } else { + return null; + } + let fd_rights_inherited = fd_rights; + let fd_flags: fdflags = 0; + let path_utf8_len: usize = String.UTF8.byteLength(path); + let path_utf8 = changetype(String.UTF8.encode(path)); + let fd_buf = changetype(new ArrayBuffer(sizeof())); + let res = path_open( + dirfd as fd, + fd_lookup_flags, + path_utf8, + path_utf8_len, + fd_oflags, + fd_rights, + fd_rights_inherited, + fd_flags, + fd_buf + ); + if (res !== errno.SUCCESS) { + return null; + } + let fd = load(fd_buf); + + return new Descriptor(fd); + } + + /** + * Create a new directory + * @path path + * @returns `true` on success, `false` on failure + */ + static mkdir(path: string): bool { + let dirfd = this.dirfdForPath(path); + let path_utf8_len: usize = String.UTF8.byteLength(path); + let path_utf8 = changetype(String.UTF8.encode(path)); + let res = path_create_directory(dirfd, path_utf8, path_utf8_len); + + return res === errno.SUCCESS; + } + + /** + * Check if a file exists at a given path + * @path path + * @returns `true` on success, `false` on failure + */ + static exists(path: string): bool { + let dirfd = this.dirfdForPath(path); + let path_utf8_len: usize = String.UTF8.byteLength(path); + let path_utf8 = changetype(String.UTF8.encode(path)); + let fd_lookup_flags = lookupflags.SYMLINK_FOLLOW; + let st_buf = changetype(new ArrayBuffer(56)); + let res = path_filestat_get(dirfd, fd_lookup_flags, path_utf8, path_utf8_len, + changetype(st_buf)); + + return res === errno.SUCCESS; + } + + /** + * Create a hard link + * @old_path old path + * @new_path new path + * @returns `true` on success, `false` on failure + */ + static link(old_path: string, new_path: string): bool { + let old_dirfd = this.dirfdForPath(old_path); + let old_path_utf8_len: usize = String.UTF8.byteLength(old_path); + let old_path_utf8 = changetype(String.UTF8.encode(old_path)); + let new_dirfd = this.dirfdForPath(new_path); + let new_path_utf8_len: usize = String.UTF8.byteLength(new_path); + let new_path_utf8 = changetype(String.UTF8.encode(new_path)); + let fd_lookup_flags = lookupflags.SYMLINK_FOLLOW; + let res = path_link(old_dirfd, fd_lookup_flags, old_path_utf8, old_path_utf8_len, + new_dirfd, new_path_utf8, new_path_utf8_len); + + return res === errno.SUCCESS; + } + + /** + * Create a symbolic link + * @old_path old path + * @new_path new path + * @returns `true` on success, `false` on failure + */ + static symlink(old_path: string, new_path: string): bool { + let old_path_utf8_len: usize = String.UTF8.byteLength(old_path); + let old_path_utf8 = changetype(String.UTF8.encode(old_path)); + let new_dirfd = this.dirfdForPath(new_path); + let new_path_utf8_len: usize = String.UTF8.byteLength(new_path); + let new_path_utf8 = changetype(String.UTF8.encode(new_path)); + let res = path_symlink(old_path_utf8, old_path_utf8_len, + new_dirfd, new_path_utf8, new_path_utf8_len); + + return res === errno.SUCCESS; + } + + /** + * Unlink a file + * @path path + * @returns `true` on success, `false` on failure + */ + static unlink(path: string): bool { + let dirfd = this.dirfdForPath(path); + let path_utf8_len: usize = String.UTF8.byteLength(path); + let path_utf8 = changetype(String.UTF8.encode(path)); + let res = path_unlink_file(dirfd, path_utf8, path_utf8_len); + + return res === errno.SUCCESS; + } + + /** + * Remove a directory + * @path path + * @returns `true` on success, `false` on failure + */ + static rmdir(path: string): bool { + let dirfd = this.dirfdForPath(path); + let path_utf8_len: usize = String.UTF8.byteLength(path); + let path_utf8 = changetype(String.UTF8.encode(path)); + let res = path_remove_directory(dirfd, path_utf8, path_utf8_len); + + return res === errno.SUCCESS; + } + + /** + * Retrieve information about a file + * @path path + * @returns a `FileStat` object + */ + static stat(path: string): FileStat { + let dirfd = this.dirfdForPath(path); + let path_utf8_len: usize = String.UTF8.byteLength(path); + let path_utf8 = changetype(String.UTF8.encode(path)); + let fd_lookup_flags = lookupflags.SYMLINK_FOLLOW; + let st_buf = changetype(new ArrayBuffer(56)); + if (path_filestat_get(dirfd, fd_lookup_flags, path_utf8, path_utf8_len, changetype(st_buf)) !== errno.SUCCESS) { + throw new WASAError("Unable to get the file information"); + } + return new FileStat(st_buf); + } + + /** + * Retrieve information about a file or a symbolic link + * @path path + * @returns a `FileStat` object + */ + static lstat(path: string): FileStat { + let dirfd = this.dirfdForPath(path); + let path_utf8_len: usize = String.UTF8.byteLength(path); + let path_utf8 = changetype(String.UTF8.encode(path)); + let fd_lookup_flags = 0; + let st_buf = changetype(new ArrayBuffer(56)); + if (path_filestat_get(dirfd, fd_lookup_flags, path_utf8, path_utf8_len, changetype(st_buf)) !== errno.SUCCESS) { + throw new WASAError("Unable to get the file information"); + } + return new FileStat(st_buf); + } + + /** + * Rename a file + * @old_path old path + * @new_path new path + * @returns `true` on success, `false` on failure + */ + static rename(old_path: string, new_path: string): bool { + let old_dirfd = this.dirfdForPath(old_path); + let old_path_utf8_len: usize = String.UTF8.byteLength(old_path); + let old_path_utf8 = changetype(String.UTF8.encode(old_path)); + let new_dirfd = this.dirfdForPath(new_path); + let new_path_utf8_len: usize = String.UTF8.byteLength(new_path); + let new_path_utf8 = changetype(String.UTF8.encode(new_path)); + let res = path_rename(old_dirfd, old_path_utf8, old_path_utf8_len, + new_dirfd, new_path_utf8, new_path_utf8_len); + + return res === errno.SUCCESS; + } + + /** + * Get the content of a directory + * @param path the directory path + * @returns An array of file names + */ + static readdir(path: string): Array | null { + let fd = this.open(path, "r"); + if (fd === null) { + return null; + } + let out = new Array(); + let buf = null; + let buf_size = 4096; + let buf_used_p = changetype(new ArrayBuffer(4)); + let buf_used = 0; + for (; ;) { + buf = __alloc(buf_size, 0); + if (fd_readdir(fd.rawfd, buf, buf_size, 0 as dircookie, buf_used_p) !== errno.SUCCESS) { + fd.close(); + } + buf_used = load(buf_used_p); + if (buf_used < buf_size) { + break; + } + buf_size <<= 1; + __free(buf); + } + let offset = 0; + while (offset < buf_used) { + offset += 16; + let name_len = load(buf + offset); + offset += 8; + if (offset + name_len > buf_used) { + return null; + } + let name = String.UTF8.decodeUnsafe(buf + offset, name_len); + out.push(name); + offset += name_len; + } + __free(buf); + fd.close(); + + return out; + } + + protected static dirfdForPath(path: string): fd { + return 3; + } } @global @@ -245,14 +700,14 @@ export class Console { * @param newline `false` to avoid inserting a newline after the string */ static write(s: string, newline: bool = true): void { - IO.writeString(1, s, newline); + Descriptor.Stdout().writeString(s, newline); } /** * Read an UTF8 string from the console, convert it to a native string */ static readAll(): string | null { - return IO.readString(0); + return Descriptor.Stdin().readString(); } /** @@ -268,7 +723,7 @@ export class Console { * @param newline `false` to avoid inserting a newline after the string */ static error(s: string, newline: bool = true): void { - IO.writeString(2, s, newline); + Descriptor.Stderr().writeString(s, newline); } } @@ -279,10 +734,10 @@ export class Random { */ static randomFill(buffer: ArrayBuffer): void { let len = buffer.byteLength; - let ptr = buffer.data; + let ptr = changetype(buffer); while (len > 0) { let chunk = min(len, 256); - if (random_get(ptr, chunk) != errno.SUCCESS) { + if (random_get(ptr, chunk) !== errno.SUCCESS) { abort(); } len -= chunk; @@ -306,20 +761,20 @@ export class Date { * Return the current timestamp, as a number of milliseconds since the epoch */ static now(): f64 { - let time_ptr = memory.allocate(8); + let time_ptr = changetype(new ArrayBuffer(8)); clock_time_get(clockid.REALTIME, 1000, time_ptr); let unix_ts = load(time_ptr); - memory.free(time_ptr); - return unix_ts as f64 / 1000.0; + + return (unix_ts as f64) / 1000.0; } } export class Performance { static now(): f64 { - let time_ptr = memory.allocate(8); + let time_ptr = changetype(new ArrayBuffer(8)); clock_res_get(clockid.MONOTONIC, time_ptr); let res_ts = load(time_ptr); - memory.free(time_ptr); + return res_ts as f64; } } @@ -330,12 +785,12 @@ export class Process { * @param status exit code */ static exit(status: u32): void { - proc_exit(status) + proc_exit(status); } } export class EnvironEntry { - constructor(readonly key: string, readonly value: string) { }; + constructor(readonly key: string, readonly value: string) { } } export class Environ { @@ -343,16 +798,20 @@ export class Environ { constructor() { this.env = []; - let count_and_size = memory.allocate(2 * sizeof()); + let count_and_size = changetype( + new ArrayBuffer(2 * sizeof()) + ); let ret = environ_sizes_get(count_and_size, count_and_size + 4); - if (ret != errno.SUCCESS) { + if (ret !== errno.SUCCESS) { abort(); } let count = load(count_and_size); let size = load(count_and_size + sizeof()); - let env_ptrs = memory.allocate((count + 1) * sizeof()); - let buf = memory.allocate(size); - if (environ_get(env_ptrs, buf) != errno.SUCCESS) { + let env_ptrs = changetype( + new ArrayBuffer((count + 1) * sizeof()) + ); + let buf = changetype(new ArrayBuffer(size)); + if (environ_get(env_ptrs, buf) !== errno.SUCCESS) { abort(); } for (let i: usize = 0; i < count; i++) { @@ -362,8 +821,6 @@ export class Environ { let value = env_ptr_split[1]; this.env.push(new EnvironEntry(key, value)); } - memory.free(buf); - memory.free(env_ptrs); } /** @@ -379,7 +836,7 @@ export class Environ { */ get(key: string): string | null { for (let i = 0, j = this.env.length; i < j; i++) { - if (this.env[i].key == key) { + if (this.env[i].key === key) { return this.env[i].value; } } @@ -388,20 +845,24 @@ export class Environ { } export class CommandLine { - args: Array; + args: Array; constructor() { this.args = []; - let count_and_size = memory.allocate(2 * sizeof()); + let count_and_size = changetype( + new ArrayBuffer(2 * sizeof()) + ); let ret = args_sizes_get(count_and_size, count_and_size + 4); - if (ret != errno.SUCCESS) { + if (ret !== errno.SUCCESS) { abort(); } let count = load(count_and_size); let size = load(count_and_size + sizeof()); - let env_ptrs = memory.allocate((count + 1) * sizeof()); - let buf = memory.allocate(size); - if (args_get(env_ptrs, buf) != errno.SUCCESS) { + let env_ptrs = changetype( + new ArrayBuffer((count + 1) * sizeof()) + ); + let buf = changetype(new ArrayBuffer(size)); + if (args_get(env_ptrs, buf) !== errno.SUCCESS) { abort(); } for (let i: usize = 0; i < count; i++) { @@ -409,14 +870,12 @@ export class CommandLine { let arg = StringUtils.fromCString(env_ptr); this.args.push(arg); } - memory.free(buf); - memory.free(env_ptrs); } /** * Return all the command-line arguments */ - all(): Array { + all(): Array { return this.args; } @@ -434,11 +893,28 @@ export class CommandLine { } class StringUtils { + /** + * Returns a native string from a zero-terminated C string + * @param cstring + * @returns native string + */ static fromCString(cstring: usize): string { let size = 0; - while (load(cstring + size) != 0) { + while (load(cstring + size) !== 0) { size++; } - return String.fromUTF8(cstring, size); + return String.UTF8.decodeUnsafe(cstring, size); } } + +@global +export function wasi_abort( + message: string | null = "", + fileName: string | null = "", + lineNumber: u32 = 0, + columnNumber: u32 = 0 +): void { + Console.error(fileName! + ":" + lineNumber.toString() + ":" + columnNumber.toString() + + ": error: " + message!); + proc_exit(255); +} diff --git a/assemblyscript/modules/wasa/assembly/wasi_unstable.ts b/assemblyscript/modules/wasa/assembly/wasi_unstable.ts deleted file mode 100644 index 5645f547a..000000000 --- a/assemblyscript/modules/wasa/assembly/wasi_unstable.ts +++ /dev/null @@ -1,690 +0,0 @@ -// see: https://wasi.dev - -/* tslint:disable:max-line-length */ - -type uintptr = usize; -type size = usize; - -/** Read command-line argument data. */ -export declare function args_get(argv: uintptr, argv_buf: uintptr): errno; -/** Return command-line argument data sizes. */ -export declare function args_sizes_get(argc: uintptr, argv_buf_size_ptr: uintptr): errno; - -/** Return the resolution of a clock. */ -export declare function clock_res_get(clock: clockid, resolution: uintptr): errno; -/** Return the time value of a clock. */ -export declare function clock_time_get(clock: clockid, precision: timestamp, time: uintptr): errno; - -/** Read environment variable data. */ -export declare function environ_get(environ: uintptr, environ_buf: uintptr): errno; -/** Return command-line argument data sizes. */ -export declare function environ_sizes_get(environ_count: uintptr, environ_buf_size: uintptr): errno; - -/** Provide file advisory information on a file descriptor. */ -export declare function fd_advise(fd: fd, offset: filesize, len: filesize, advice: advice): errno; -/** Provide file advisory information on a file descriptor. */ -export declare function fd_allocate(fd: fd, offset: filesize, len: filesize): errno; -/** Close a file descriptor. */ -export declare function fd_close(fd: fd): errno; -/** Synchronize the data of a file to disk. */ -export declare function fd_datasync(fd: fd): errno; -/** Get the attributes of a file descriptor. */ -export declare function fd_fdstat_get(fd: fd, buf: uintptr): errno; -/** Adjust the flags associated with a file descriptor. */ -export declare function fd_fdstat_set_flags(fd: fd, flags: fdflags): errno; -/** Adjust the rights associated with a file descriptor. */ -export declare function fd_fdstat_set_rights(fd: fd, fs_rights_base: rights, fs_rights_inheriting: rights): errno; -/** Return the attributes of an open file. */ -export declare function fd_filestat_get(fd: fd, buf: uintptr): errno; -/** Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros. */ -export declare function fd_filestat_set_size(fd: fd, size: filesize): errno; -/** Adjust the timestamps of an open file or directory. */ -export declare function fd_filestat_set_times(fd: fd, st_atim: timestamp, st_mtim: timestamp, fstflags: fstflags): errno; -/** Read from a file descriptor, without using and updating the file descriptor's offset. */ -export declare function fd_pread(fd: fd, iovs: uintptr, iovs_len: size, offset: filesize, nread: uintptr): errno; -/** Return a description of the given preopened file descriptor. */ -export declare function fd_prestat_get(fd: fd, buf: uintptr): errno; -/** Return a description of the given preopened file descriptor. */ -export declare function fd_prestat_dir_name(fd: fd, path: uintptr, path_len: size): errno; -/** Write to a file descriptor, without using and updating the file descriptor's offset. */ -export declare function fd_pwrite(fd: fd, iovs: uintptr, iovs_len: size, offset: filesize, nwritten: uintptr): errno; -/** Read from a file descriptor. */ -export declare function fd_read(fd: fd, iovs: uintptr, iovs_len: size, nread: uintptr): errno; -/** Read directory entries from a directory. */ -export declare function fd_readdir(fd: fd, buf: uintptr, buf_len: size, cookie: dircookie, buf_used: uintptr): errno; -/** Atomically replace a file descriptor by renumbering another file descriptor. */ -export declare function fd_renumber(from: fd, to: fd): errno; -/** Move the offset of a file descriptor. */ -export declare function fd_seek(fd: fd, offset: filedelta, whence: whence, newoffset: uintptr): errno; -/** Synchronize the data and metadata of a file to disk. */ -export declare function fd_sync(fd: fd): errno; -/** Return the current offset of a file descriptor. */ -export declare function fd_tell(fd: fd, newoffset: uintptr): errno; -/** Write to a file descriptor. */ -export declare function fd_write(fd: fd, iovs: uintptr, iovs_len: size, nwritten: uintptr): errno; - -/* Create a directory. */ -export declare function path_create_directory(fd: fd, path: uintptr, path_len: size): errno; -/** Return the attributes of a file or directory. */ -export declare function path_filestat_get(fd: fd, flags: lookupflags, path: uintptr, path_len: size, buf: uintptr): errno; -/** Adjust the timestamps of a file or directory. */ -export declare function path_filestat_set_times(fd: fd, flags: lookupflags, path: uintptr, path_len: size, st_atim: timestamp, st_mtim: timestamp, fstflags: fstflags): errno; -/** Create a hard link. */ -export declare function path_link(fd0: fd, flags0: lookupflags, path0: uintptr, path_len0: size, fd1: fd, path1: uintptr, path_len: size): errno; -/** Open a file or directory. */ -export declare function path_open(dirfd: fd, dirflags: lookupflags, path: uintptr, path_len: size, oflags: oflags, fs_rights_base: rights, fs_rights_inheriting: rights, fs_flags: fdflags, fd: fd): errno; -/** Read the contents of a symbolic link. */ -export declare function path_readlink(fd: fd, path: uintptr, path_len: size, buf: uintptr, buf_len: size, buf_used: uintptr): errno; -/** Remove a directory. */ -export declare function path_remove_directory(fd: fd, path: uintptr, path_len: size): errno; -/** Rename a file or directory. */ -export declare function path_rename(fd0: fd, path0: uintptr, path_len0: size, fd1: fd, path1: uintptr, path_len1: size): errno; -/** Create a symbolic link. */ -export declare function path_symlink(path0: uintptr, path_len0: size, fd: fd, path1: uintptr, path_len1: size): errno; -/** Unlink a file. */ -export declare function path_unlink_file(fd: fd, path: uintptr, path_len: size): errno; - -/** Concurrently poll for the occurrence of a set of events. */ -export declare function poll_oneoff(in_: uintptr, out: uintptr, nsubscriptions: size, nevents: uintptr): errno; - -/** Terminate the process normally. An exit code of 0 indicates successful termination of the program. The meanings of other values is dependent on the environment. */ -export declare function proc_exit(rval: u32): void; -/** Send a signal to the process of the calling thread. */ -export declare function proc_raise(sig: signal): errno; - -/** Write high-quality random data into a buffer. */ -export declare function random_get(buf: uintptr, buf_len: size): errno; - -/** Temporarily yield execution of the calling thread. */ -export declare function sched_yield(): errno; - -/** Receive a message from a socket. */ -export declare function sock_recv(sock: fd, ri_data: uintptr, ri_data_len: size, ri_flags: riflags, ro_datalen: uintptr, ro_flags: roflags): errno; -/** Send a message on a socket. */ -export declare function sock_send(sock: fd, si_data: uintptr, si_data_len: size, si_flags: siflags, so_datalen: uintptr): errno; -/** Shut down socket send and receive channels. */ -export declare function sock_shutdown(sock: fd, how: sdflags): errno; - -// === Types ====================================================================================== - -/** File or memory access pattern advisory information. */ -export namespace advice { - /** The application has no advice to give on its behavior with respect to the specified data. */ - @inline export const NORMAL: advice = 0; - /** The application expects to access the specified data sequentially from lower offsets to higher offsets. */ - @inline export const SEQUENTIAL : advice = 1; - /** The application expects to access the specified data in a random order. */ - @inline export const RANDOM: advice = 2; - /** The application expects to access the specified data in the near future. */ - @inline export const WILLNEED: advice = 3; - /** The application expects that it will not access the specified data in the near future. */ - @inline export const DONTNEED: advice = 4; - /** The application expects to access the specified data once and then not reuse it thereafter. */ - @inline export const NOREUSE: advice = 5; -} -export type advice = u8; - -/** Identifiers for clocks. */ -export namespace clockid { - /** The clock measuring real time. Time value zero corresponds with 1970-01-01T00:00:00Z. */ - @inline export const REALTIME: clockid = 0; - /** The store-wide monotonic clock. Absolute value has no meaning. */ - @inline export const MONOTONIC: clockid = 1; - /** The CPU-time clock associated with the current process. */ - @inline export const PROCESS_CPUTIME_ID: clockid = 2; - /** The CPU-time clock associated with the current thread. */ - @inline export const THREAD_CPUTIME_ID: clockid = 3; -} -export type clockid = u32; - -/** Identifier for a device containing a file system. Can be used in combination with `inode` to uniquely identify a file or directory in the filesystem. */ -export type device = u64; - -/** A reference to the offset of a directory entry. */ -export type dircookie = u64; - -/** A directory entry. */ -@unmanaged export class dirent { - /** The offset of the next directory entry stored in this directory. */ - next: dircookie; - /** The serial number of the file referred to by this directory entry. */ - ino: inode; - /** The length of the name of the directory entry. */ - namlen: u32; - /** The type of the file referred to by this directory entry. */ - type: filetype; - private __padding0: u16; -} - -/** Error codes returned by functions. */ -export namespace errno { - /** No error occurred. System call completed successfully. */ - @inline export const SUCCESS: errno = 0; - /** Argument list too long. */ - @inline export const TOOBIG: errno = 1; - /** Permission denied. */ - @inline export const ACCES: errno = 2; - /** Address in use. */ - @inline export const ADDRINUSE: errno = 3; - /** Address not available. */ - @inline export const ADDRNOTAVAIL: errno = 4; - /** Address family not supported. */ - @inline export const AFNOSUPPORT: errno = 5; - /** Resource unavailable, or operation would block. */ - @inline export const AGAIN: errno = 6; - /** Connection already in progress. */ - @inline export const ALREADY: errno = 7; - /** Bad file descriptor. */ - @inline export const BADF: errno = 8; - /** Bad message. */ - @inline export const BADMSG: errno = 9; - /** Device or resource busy. */ - @inline export const BUSY: errno = 10; - /** Operation canceled. */ - @inline export const CANCELED: errno = 11; - /** No child processes. */ - @inline export const CHILD: errno = 12; - /** Connection aborted. */ - @inline export const CONNABORTED: errno = 13; - /** Connection refused. */ - @inline export const CONNREFUSED: errno = 14; - /** Connection reset. */ - @inline export const CONNRESET: errno = 15; - /** Resource deadlock would occur. */ - @inline export const DEADLK: errno = 16; - /** Destination address required. */ - @inline export const DESTADDRREQ: errno = 17; - /** Mathematics argument out of domain of function. */ - @inline export const DOM: errno = 18; - /** Reserved. */ - @inline export const DQUOT: errno = 19; - /** File exists. */ - @inline export const EXIST: errno = 20; - /** Bad address. */ - @inline export const FAULT: errno = 21; - /** File too large. */ - @inline export const FBIG: errno = 22; - /** Host is unreachable. */ - @inline export const HOSTUNREACH: errno = 23; - /** Identifier removed. */ - @inline export const IDRM: errno = 24; - /** Illegal byte sequence. */ - @inline export const ILSEQ: errno = 25; - /** Operation in progress. */ - @inline export const INPROGRESS: errno = 26; - /** Interrupted function. */ - @inline export const INTR: errno = 27; - /** Invalid argument. */ - @inline export const INVAL: errno = 28; - /** I/O error. */ - @inline export const IO: errno = 29; - /** Socket is connected. */ - @inline export const ISCONN: errno = 30; - /** Is a directory. */ - @inline export const ISDIR: errno = 31; - /** Too many levels of symbolic links. */ - @inline export const LOOP: errno = 32; - /** File descriptor value too large. */ - @inline export const MFILE: errno = 33; - /** Too many links. */ - @inline export const MLINK: errno = 34; - /** Message too large. */ - @inline export const MSGSIZE: errno = 35; - /** Reserved. */ - @inline export const MULTIHOP: errno = 36; - /** Filename too long. */ - @inline export const NAMETOOLONG: errno = 37; - /** Network is down. */ - @inline export const NETDOWN: errno = 38; - /** Connection aborted by network. */ - @inline export const NETRESET: errno = 39; - /** Network unreachable. */ - @inline export const NETUNREACH: errno = 40; - /** Too many files open in system. */ - @inline export const NFILE: errno = 41; - /** No buffer space available. */ - @inline export const NOBUFS: errno = 42; - /** No such device. */ - @inline export const NODEV: errno = 43; - /** No such file or directory. */ - @inline export const NOENT: errno = 44; - /** Executable file format error. */ - @inline export const NOEXEC: errno = 45; - /** No locks available. */ - @inline export const NOLCK: errno = 46; - /** Reserved. */ - @inline export const NOLINK: errno = 47; - /** Not enough space. */ - @inline export const NOMEM: errno = 48; - /** No message of the desired type. */ - @inline export const NOMSG: errno = 49; - /** Protocol not available. */ - @inline export const NOPROTOOPT: errno = 50; - /** No space left on device. */ - @inline export const NOSPC: errno = 51; - /** Function not supported. */ - @inline export const NOSYS: errno = 52; - /** The socket is not connected. */ - @inline export const NOTCONN: errno = 53; - /** Not a directory or a symbolic link to a directory. */ - @inline export const NOTDIR: errno = 54; - /** Directory not empty. */ - @inline export const NOTEMPTY: errno = 55; - /** State not recoverable. */ - @inline export const NOTRECOVERABLE: errno = 56; - /** Not a socket. */ - @inline export const NOTSOCK: errno = 57; - /** Not supported, or operation not supported on socket. */ - @inline export const NOTSUP: errno = 58; - /** Inappropriate I/O control operation. */ - @inline export const NOTTY: errno = 59; - /** No such device or address. */ - @inline export const NXIO: errno = 60; - /** Value too large to be stored in data type. */ - @inline export const OVERFLOW: errno = 61; - /** Previous owner died. */ - @inline export const OWNERDEAD: errno = 62; - /** Operation not permitted. */ - @inline export const PERM: errno = 63; - /** Broken pipe. */ - @inline export const PIPE: errno = 64; - /** Protocol error. */ - @inline export const PROTO: errno = 65; - /** Protocol not supported. */ - @inline export const PROTONOSUPPORT: errno = 66; - /** Protocol wrong type for socket. */ - @inline export const PROTOTYPE: errno = 67; - /** Result too large. */ - @inline export const RANGE: errno = 68; - /** Read-only file system. */ - @inline export const ROFS: errno = 69; - /** Invalid seek. */ - @inline export const SPIPE: errno = 70; - /** No such process. */ - @inline export const SRCH: errno = 71; - /** Reserved. */ - @inline export const STALE: errno = 72; - /** Connection timed out. */ - @inline export const TIMEDOUT: errno = 73; - /** Text file busy. */ - @inline export const TXTBSY: errno = 74; - /** Cross-device link. */ - @inline export const XDEV: errno = 75; - /** Extension: Capabilities insufficient. */ - @inline export const NOTCAPABLE: errno = 76; -} -export type errno = u16; - -/** An event that occurred. */ -@unmanaged export abstract class event { - /** User-provided value that got attached to `subscription#userdata`. */ - userdata: userdata; - /** If non-zero, an error that occurred while processing the subscription request. */ - error: errno; - /* The type of the event that occurred. */ - type: eventtype; - private __padding0: u16; -} - -/** An event that occurred when type is `eventtype.FD_READ` or `eventtype.FD_WRITE`. */ -@unmanaged export class rwevent extends event { - /* The number of bytes available for reading or writing. */ - nbytes: filesize; - /* The state of the file descriptor. */ - flags: eventrwflags; - private __padding1: u32; -} - -/** The state of the file descriptor subscribed to with `eventtype.FD_READ` or `eventtype.FD_WRITE`. */ -export namespace eventrwflags { - /** The peer of this socket has closed or disconnected. */ - @inline export const HANGUP: eventrwflags = 1; -} -export type eventrwflags = u16; - -/** Type of a subscription to an event or its occurrence. */ -export namespace eventtype { - /** The time value of clock has reached the timestamp. */ - @inline export const CLOCK: eventtype = 0; - /** File descriptor has data available for reading. */ - @inline export const FD_READ: eventtype = 1; - /** File descriptor has capacity available for writing */ - @inline export const FD_WRITE: eventtype = 2; -} -export type eventtype = u8; - -/** Exit code generated by a process when exiting. */ -export type exitcode = u32; - -/** A file descriptor number. */ -export type fd = u32; - -/** File descriptor flags. */ -export namespace fdflags { - /** Append mode: Data written to the file is always appended to the file's end. */ - @inline export const APPEND: fdflags = 1; - /** Write according to synchronized I/O data integrity completion. Only the data stored in the file is synchronized. */ - @inline export const DSYNC: fdflags = 2; - /** Non-blocking mode. */ - @inline export const NONBLOCK: fdflags = 4; - /** Synchronized read I/O operations. */ - @inline export const RSYNC: fdflags = 8; - /** Write according to synchronized I/O file integrity completion. */ - @inline export const SYNC: fdflags = 16; -} -export type fdflags = u16; - -/** File descriptor attributes. */ -@unmanaged export class fdstat { - /** File type. */ - filetype: filetype; - /** File descriptor flags. */ - flags: fdflags; - /** Rights that apply to this file descriptor. */ - rights_base: rights; - /** Maximum set of rights that may be installed on new file descriptors that are created through this file descriptor, e.g., through `path_open`. */ - rights_inheriting: rights; -} - -/** Relative offset within a file. */ -export type filedelta = i64; - -/** Non-negative file size or length of a region within a file. */ -export type filesize = u64; - -/** File attributes. */ -@unmanaged export class filestat { - /** Device ID of device containing the file. */ - dev: device; - /** File serial number. */ - ino: inode; - /** File type. */ - filetype: filetype; - /** Number of hard links to the file. */ - nlink: linkcount; - /** For regular files, the file size in bytes. For symbolic links, the length in bytes of the pathname contained in the symbolic link. */ - size: filesize; - /** Last data access timestamp. */ - atim: timestamp; - /** Last data modification timestamp. */ - mtim: timestamp; - /** Last file status change timestamp. */ - ctim: timestamp; -} - -/** The type of a file descriptor or file. */ -export namespace filetype { - /** The type of the file descriptor or file is unknown or is different from any of the other types specified. */ - @inline export const UNKNOWN: filetype = 0; - /** The file descriptor or file refers to a block device inode. */ - @inline export const BLOCK_DEVICE: filetype = 1; - /** The file descriptor or file refers to a character device inode. */ - @inline export const CHARACTER_DEVICE: filetype = 2; - /** The file descriptor or file refers to a directory inode. */ - @inline export const DIRECTORY: filetype = 3; - /** The file descriptor or file refers to a regular file inode. */ - @inline export const REGULAR_FILE: filetype = 4; - /** The file descriptor or file refers to a datagram socket. */ - @inline export const SOCKET_DGRAM: filetype = 5; - /** The file descriptor or file refers to a byte-stream socket. */ - @inline export const SOCKET_STREAM: filetype = 6; - /** The file refers to a symbolic link inode. */ - @inline export const SYMBOLIC_LINK: filetype = 7; -} -export type filetype = u8; - -/** Which file time attributes to adjust. */ -export namespace fstflags { - /** Adjust the last data access timestamp to the value stored in `filestat#st_atim`. */ - @inline export const SET_ATIM: fstflags = 1; - /** Adjust the last data access timestamp to the time of clock `clockid.REALTIME`. */ - @inline export const SET_ATIM_NOW: fstflags = 2; - /** Adjust the last data modification timestamp to the value stored in `filestat#st_mtim`. */ - @inline export const SET_MTIM: fstflags = 4; - /** Adjust the last data modification timestamp to the time of clock `clockid.REALTIME`. */ - @inline export const SET_MTIM_NOW: fstflags = 8; -} -export type fstflags = u16; - -/** File serial number that is unique within its file system. */ -export type inode = u64; - -/** A region of memory for scatter/gather reads. */ -@unmanaged export class iovec { - /** The address of the buffer to be filled. */ - buf: uintptr; - /** The length of the buffer to be filled. */ - buf_len: size; -} - -/** Number of hard links to an inode. */ -export type linkcount = u32; - -/** Flags determining the method of how paths are resolved. */ -export namespace lookupflags { - /** As long as the resolved path corresponds to a symbolic link, it is expanded. */ - @inline export const SYMLINK_FOLLOW: lookupflags = 1; -} -export type lookupflags = u32; - -/** Open flags. */ -export namespace oflags { - /** Create file if it does not exist. */ - @inline export const CREAT: oflags = 1; - /** Fail if not a directory. */ - @inline export const DIRECTORY: oflags = 2; - /** Fail if file already exists. */ - @inline export const EXCL: oflags = 4; - /** Truncate file to size 0. */ - @inline export const TRUNC: oflags = 8; -} -export type oflags = u16; - -/** Flags provided to `sock_recv`. */ -export namespace riflags { - /** Returns the message without removing it from the socket's receive queue. */ - @inline export const PEEK: riflags = 1; - /** On byte-stream sockets, block until the full amount of data can be returned. */ - @inline export const WAITALL: riflags = 2; -} -export type riflags = u16; - -/** File descriptor rights, determining which actions may be performed. */ -export namespace rights { - /** The right to invoke `fd_datasync`. */ - @inline export const FD_DATASYNC: rights = 1; - /** The right to invoke `fd_read` and `sock_recv`. */ - @inline export const FD_READ: rights = 2; - /** The right to invoke `fd_seek`. This flag implies `rights.FD_TELL`. */ - @inline export const FD_SEEK: rights = 4; - /** The right to invoke `fd_fdstat_set_flags`. */ - @inline export const FD_FDSTAT_SET_FLAGS: rights = 8; - /** The right to invoke `fd_sync`. */ - @inline export const FD_SYNC: rights = 16; - /** The right to invoke `fd_seek` in such a way that the file offset remains unaltered (i.e., `whence.CUR` with offset zero), or to invoke `fd_tell`). */ - @inline export const FD_TELL: rights = 32; - /** The right to invoke `fd_write` and `sock_send`. If `rights.FD_SEEK` is set, includes the right to invoke `fd_pwrite`. */ - @inline export const FD_WRITE: rights = 64; - /** The right to invoke `fd_advise`. */ - @inline export const FD_ADVISE: rights = 128; - /** The right to invoke `fd_allocate`. */ - @inline export const FD_ALLOCATE: rights = 256; - /** The right to invoke `path_create_directory`. */ - @inline export const PATH_CREATE_DIRECTORY: rights = 512; - /** If `rights.PATH_OPEN` is set, the right to invoke `path_open` with `oflags.CREAT`. */ - @inline export const PATH_CREATE_FILE: rights = 1024; - /** The right to invoke `path_link` with the file descriptor as the source directory. */ - @inline export const PATH_LINK_SOURCE: rights = 2048; - /** The right to invoke `path_link` with the file descriptor as the target directory. */ - @inline export const PATH_LINK_TARGET: rights = 4096; - /** The right to invoke `path_open`. */ - @inline export const PATH_OPEN: rights = 8192; - /** The right to invoke `fd_readdir`. */ - @inline export const FD_READDIR: rights = 16384; - /** The right to invoke `path_readlink`. */ - @inline export const PATH_READLINK: rights = 32768; - /** The right to invoke `path_rename` with the file descriptor as the source directory. */ - @inline export const PATH_RENAME_SOURCE: rights = 65536; - /** The right to invoke `path_rename` with the file descriptor as the target directory. */ - @inline export const PATH_RENAME_TARGET: rights = 131072; - /** The right to invoke `path_filestat_get`. */ - @inline export const PATH_FILESTAT_GET: rights = 262144; - /** The right to change a file's size (there is no `path_filestat_set_size`). If `rights.PATH_OPEN` is set, includes the right to invoke `path_open` with `oflags.TRUNC`. */ - @inline export const PATH_FILESTAT_SET_SIZE: rights = 524288; - /** The right to invoke `path_filestat_set_times`. */ - @inline export const PATH_FILESTAT_SET_TIMES: rights = 1048576; - /** The right to invoke `fd_filestat_get`. */ - @inline export const FD_FILESTAT_GET: rights = 2097152; - /** The right to invoke `fd_filestat_set_size`. */ - @inline export const FD_FILESTAT_SET_SIZE: rights = 4194304; - /** The right to invoke `fd_filestat_set_times`. */ - @inline export const FD_FILESTAT_SET_TIMES: rights = 8388608; - /** The right to invoke `path_symlink`. */ - @inline export const RIGHT_PATH_SYMLINK: rights = 16777216; - /** The right to invoke `path_remove_directory`. */ - @inline export const PATH_REMOVE_DIRECTORY: rights = 33554432; - /** The right to invoke `path_unlink_file`. */ - @inline export const PATH_UNLINK_FILE: rights = 67108864; - /** If `rights.FD_READ` is set, includes the right to invoke `poll_oneoff` to subscribe to `eventtype.FD_READ`. If `rights.FD_WRITE` is set, includes the right to invoke `poll_oneoff` to subscribe to `eventtype.FD_WRITE`. */ - @inline export const POLL_FD_READWRITE: rights = 134217728; - /** The right to invoke `sock_shutdown`. */ - @inline export const SOCK_SHUTDOWN: rights = 268435456; -} -export type rights = u64; - -/** Flags returned by `sock_recv`. */ -export namespace roflags { - /** Message data has been truncated. */ - @inline export const DATA_TRUNCATED: roflags = 1; -} -export type roflags = u16; - -/** Which channels on a socket to shut down. */ -export namespace sdflags { - /** Disables further receive operations. */ - @inline export const RD: sdflags = 1; - /** Disables further send operations. */ - @inline export const WR: sdflags = 2; -} -export type sdflags = u8; - -/** Flags provided to `sock_send`. */ -export namespace siflags { - // As there are currently no flags defined, it must be set to zero. -} -export type siflags = u16; - -/** Signal condition. */ -export namespace signal { - /** Hangup. */ - @inline export const HUP: signal = 1; - /** Terminate interrupt signal. */ - @inline export const INT: signal = 2; - /** Terminal quit signal. */ - @inline export const QUIT: signal = 3; - /** Illegal instruction. */ - @inline export const ILL: signal = 4; - /** Trace/breakpoint trap. */ - @inline export const TRAP: signal = 5; - /** Process abort signal. */ - @inline export const ABRT: signal = 6; - /** Access to an undefined portion of a memory object. */ - @inline export const BUS: signal = 7; - /** Erroneous arithmetic operation. */ - @inline export const FPE: signal = 8; - /** Kill. */ - @inline export const KILL: signal = 9; - /** User-defined signal 1. */ - @inline export const USR1: signal = 10; - /** Invalid memory reference. */ - @inline export const SEGV: signal = 11; - /** User-defined signal 2. */ - @inline export const USR2: signal = 12; - /** Write on a pipe with no one to read it. */ - @inline export const PIPE: signal = 13; - /** Alarm clock. */ - @inline export const ALRM: signal = 14; - /** Termination signal. */ - @inline export const TERM: signal = 15; - /** Child process terminated, stopped, or continued. */ - @inline export const CHLD: signal = 16; - /** Continue executing, if stopped. */ - @inline export const CONT: signal = 17; - /** Stop executing. */ - @inline export const STOP: signal = 18; - /** Terminal stop signal. */ - @inline export const TSTP: signal = 19; - /** Background process attempting read. */ - @inline export const TTIN: signal = 20; - /** Background process attempting write. */ - @inline export const TTOU: signal = 21; - /** High bandwidth data is available at a socket. */ - @inline export const URG: signal = 22; - /** CPU time limit exceeded. */ - @inline export const XCPU: signal = 23; - /** File size limit exceeded. */ - @inline export const XFSZ: signal = 24; - /** Virtual timer expired. */ - @inline export const VTALRM: signal = 25; - @inline export const PROF: signal = 26; - @inline export const WINCH: signal = 27; - @inline export const POLL: signal = 28; - @inline export const PWR: signal = 29; - /** Bad system call. */ - @inline export const SYS: signal = 30; -} -export type signal = u8; - -/** Flags determining how to interpret the timestamp provided in `subscription_t::u.clock.timeout. */ -export namespace subclockflags { - /** If set, treat the timestamp provided in `clocksubscription` as an absolute timestamp. */ - @inline export const ABSTIME: subclockflags = 1; -} -export type subclockflags = u16; - -/** Subscription to an event. */ -@unmanaged export abstract class subscription { - /** User-provided value that is attached to the subscription. */ - userdata: userdata; - /** The type of the event to which to subscribe. */ - type: eventtype; - private __padding0: u32; -} - -/* Subscription to an event of type `eventtype.CLOCK`.**/ -@unmanaged export class clocksubscription extends subscription { - /** The user-defined unique identifier of the clock. */ - identifier: userdata; - /** The clock against which to compare the timestamp. */ - clock_id: clockid; - /** The absolute or relative timestamp. */ - timeout: timestamp; - /** The amount of time that the implementation may wait additionally to coalesce with other events. */ - precision: timestamp; - /** Flags specifying whether the timeout is absolute or relative. */ - flags: subclockflags; - private __padding1: u32; -} - -/* Subscription to an event of type `eventtype.FD_READ` or `eventtype.FD_WRITE`.**/ -@unmanaged export class fdsubscription extends subscription { - /** The file descriptor on which to wait for it to become ready for reading or writing. */ - fd: fd; -} - -/** Timestamp in nanoseconds. */ -export type timestamp = u64; - -/** User-provided value that may be attached to objects that is retained when extracted from the implementation. */ -export type userdata = u64; - -/** The position relative to which to set the offset of the file descriptor. */ -export namespace whence { - /** Seek relative to current position. */ - @inline export const CUR: whence = 0; - /** Seek relative to end-of-file. */ - @inline export const END: whence = 1; - /** Seek relative to start-of-file. */ - @inline export const SET: whence = 2; -} -export type whence = u8; diff --git a/assemblyscript/modules/wasa/bindings.json b/assemblyscript/modules/wasa/bindings.json index 9d037f3a2..1eec11f6b 100644 --- a/assemblyscript/modules/wasa/bindings.json +++ b/assemblyscript/modules/wasa/bindings.json @@ -1,19 +1,48 @@ { - "env": { - "abort": "abort" - }, - "wasi_unstable": { - "fd_write": "__wasi_fd_write", - "fd_read": "__wasi_fd_read", - "fd_close": "__wasi_fd_close", - "random_get": "__wasi_random_get", - "clock_time_get": "__wasi_clock_time_get", - "clock_res_get": "__wasi_clock_res_get", - "proc_exit": "__wasi_proc_exit", - "environ_sizes_get": "__wasi_environ_sizes_get", - "environ_get": "__wasi_environ_get", - "args_sizes_get": "__wasi_args_sizes_get", - "args_get": "__wasi_args_get", - "path_open": "__wasi_path_open" - } + "env": { + "abort": "abort" + }, + "wasi_unstable": { + "args_get": "__wasi_args_get", + "args_sizes_get": "__wasi_args_sizes_get", + "clock_res_get": "__wasi_clock_res_get", + "clock_time_get": "__wasi_clock_time_get", + "environ_get": "__wasi_environ_get", + "environ_sizes_get": "__wasi_environ_sizes_get", + "fd_advise": "__wasi_fd_advise", + "fd_allocate": "__wasi_fd_allocate", + "fd_close": "__wasi_fd_close", + "fd_datasync": "__wasi_fd_datasync", + "fd_fdstat_get": "__wasi_fd_fdstat_get", + "fd_fdstat_set_flags": "__wasi_fd_fdstat_set_flags", + "fd_fdstat_set_rights": "__wasi_fd_fdstat_set_rights", + "fd_filestat_get": "__wasi_fd_filestat_get", + "fd_filestat_set_size": "__wasi_fd_filestat_set_size", + "fd_filestat_set_times": "__wasi_fd_filestat_set_times", + "fd_pread": "__wasi_fd_pread", + "fd_prestat_dir_name": "__wasi_fd_prestat_dir_name", + "fd_prestat_get": "__wasi_fd_prestat_get", + "fd_pwrite": "__wasi_fd_pwrite", + "fd_read": "__wasi_fd_read", + "fd_readdir": "__wasi_fd_readdir", + "fd_renumber": "__wasi_fd_renumber", + "fd_seek": "__wasi_fd_seek", + "fd_sync": "__wasi_fd_sync", + "fd_tell": "__wasi_fd_tell", + "fd_write": "__wasi_fd_write", + "path_create_directory": "__wasi_path_create_directory", + "path_filestat_get": "__wasi_path_filestat_get", + "path_filestat_set_times": "__wasi_path_filestat_set_times", + "path_link": "__wasi_path_link", + "path_open": "__wasi_path_open", + "path_readlink": "__wasi_path_readlink", + "path_remove_directory": "__wasi_path_remove_directory", + "path_rename": "__wasi_path_rename", + "path_symlink": "__wasi_path_symlink", + "path_unlink_file": "__wasi_path_unlink_file", + "poll_oneoff": "__wasi_poll_oneoff", + "proc_exit": "__wasi_proc_exit", + "random_get": "__wasi_random_get", + "sched_yield": "__wasi_sched_yield" + } } diff --git a/assemblyscript/modules/wasa/package.json b/assemblyscript/modules/wasa/package.json index db5095b86..ad6184530 100644 --- a/assemblyscript/modules/wasa/package.json +++ b/assemblyscript/modules/wasa/package.json @@ -1,10 +1,9 @@ { "scripts": { - "asbuild:untouched": "asc assembly/index.ts assembly/wasa.ts -b build/untouched.wasm", - "asbuild:small": "asc assembly/index.ts assembly/wasa.ts -b build/small.wasm -t build/small.wat -O3z", - "asbuild:optimized": "asc assembly/index.ts assembly/wasa.ts -b build/optimized.wasm -t build/optimized.wat -O3", - "asbuild": "npm run asbuild:optimized", - "test": "asp" + "asbuild:untouched": "asc assembly/index.ts -b build/untouched.wasm -t build/untouched.wat --use abort=wasi_abort --debug", + "asbuild:small": "asc assembly/index.ts -b build/optimized.wasm -t build/optimized.wat --use abort=wasi_abort --validate -O3z ", + "asbuild:optimized": "asc assembly/index.ts -b build/optimized.wasm -t build/optimized.wat --use abort=wasi_abort --validate -O3", + "asbuild": "npm run asbuild:optimized" }, "devDependencies": { "as-pect": "github:jtenner/as-pect", diff --git a/assets/lucet_dfds.xml b/assets/lucet_dfds.xml old mode 100755 new mode 100644 diff --git a/assets/security_dfd_cl.png b/assets/security_dfd_cl.png old mode 100755 new mode 100644 index f6793ee19..ffd874ea0 Binary files a/assets/security_dfd_cl.png and b/assets/security_dfd_cl.png differ diff --git a/assets/security_dfd_pe.png b/assets/security_dfd_pe.png old mode 100755 new mode 100644 index 18b36e5d0..983308270 Binary files a/assets/security_dfd_pe.png and b/assets/security_dfd_pe.png differ diff --git a/benchmarks/lucet-benchmarks/Cargo.toml b/benchmarks/lucet-benchmarks/Cargo.toml new file mode 100644 index 000000000..63045b953 --- /dev/null +++ b/benchmarks/lucet-benchmarks/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "lucet-benchmarks" +version = "0.4.1" +description = "Benchmarks for the Lucet runtime" +homepage = "https://github.com/fastly/lucet" +repository = "https://github.com/fastly/lucet" +license = "Apache-2.0 WITH LLVM-exception" +categories = ["wasm"] +authors = ["Lucet team "] +edition = "2018" + +[dependencies] +criterion = "0.3.0" +lucetc = { path = "../../lucetc" } +lucet-module = { path = "../../lucet-module" } +lucet-runtime = { path = "../../lucet-runtime" } +lucet-runtime-internals = { path = "../../lucet-runtime/lucet-runtime-internals" } +lucet-wasi = { path = "../../lucet-wasi" } +lucet-wasi-sdk = { path = "../../lucet-wasi-sdk" } +nix = "0.15" +num_cpus = "1.0" +rayon = "1.0" +tempfile = "3.0" + +[lib] +bench = false + +[[bench]] +name = "benchmarks" +harness = false diff --git a/benchmarks/lucet-benchmarks/README.md b/benchmarks/lucet-benchmarks/README.md new file mode 100644 index 000000000..58ddfe605 --- /dev/null +++ b/benchmarks/lucet-benchmarks/README.md @@ -0,0 +1,34 @@ +# lucet-benchmarks + +This crate defines a suite of microbenchmarks for the Lucet compiler and runtime. It is divided into +several suites to measure different categories of performance. + +To run the suite, run `cargo bench -p lucet-benchmarks`. Since some of the benchmarks measure +operations with extremely small runtimes, make sure to close as many competing background +applications as possible. For the most consistent results, disable simultaneous multithreading and +dynamic frequency scaling features such as Hyper-Threading or Turbo Boost before benchmarking. + +## Benchmark Suites + +### `src/compiler.rs` + +The compiler suite measures the performance of `lucetc` when compiling WebAssembly modules. This +suite is still fairly rudimentary; we need to add more example guests, split out the `clang` step, +and add a programmatic interface to `lucetc` that doesn't require an output file. + +### `src/context.rs` + +The context suite measures the performance of the low-level context switching code used by the +runtime to swap between host and guest execution. + +### `src/par.rs` + +The parallel execution suite measures the performance of running various runtime operations in +parallel. This suite is still fairly rudimentary. + +### `src/seq.rs` + +The sequential execution suite measures the performance of single-threaded Lucet runtime use. It +attempts to isolate the main phases of instantiation, running, and dropping a Lucet instance, as +well as some basic guest code benchmarking. The shootout benchmarks in `/benchmarks/shootout` are a +better means of analyzing the performance of guest code. diff --git a/benchmarks/lucet-benchmarks/benches/benchmarks.rs b/benchmarks/lucet-benchmarks/benches/benchmarks.rs new file mode 100644 index 000000000..df0bc6877 --- /dev/null +++ b/benchmarks/lucet-benchmarks/benches/benchmarks.rs @@ -0,0 +1,14 @@ +use criterion::Criterion; +use lucet_benchmarks::*; +use lucet_runtime::MmapRegion; + +fn main() { + let mut c = Criterion::default().configure_from_args(); + + compile_benches(&mut c); + context_benches(&mut c); + seq_benches::(&mut c); + par_benches::(&mut c); + + c.final_summary(); +} diff --git a/benchmarks/lucet-benchmarks/guests/hello.c b/benchmarks/lucet-benchmarks/guests/hello.c new file mode 100644 index 000000000..9fbdadbd5 --- /dev/null +++ b/benchmarks/lucet-benchmarks/guests/hello.c @@ -0,0 +1,6 @@ +#include + +int main() +{ + printf("Hello, benchmarks!\n"); +} diff --git a/benchmarks/lucet-benchmarks/src/compile.rs b/benchmarks/lucet-benchmarks/src/compile.rs new file mode 100644 index 000000000..92a1efcbf --- /dev/null +++ b/benchmarks/lucet-benchmarks/src/compile.rs @@ -0,0 +1,31 @@ +use crate::modules::*; +use criterion::Criterion; +use lucetc::OptLevel; +use tempfile::TempDir; + +/// Compile Hello World with default optimizations. +fn compile_hello_all(c: &mut Criterion) { + fn body(workdir: &TempDir, opt_level: OptLevel) { + let out = workdir.path().join("out.so"); + compile_hello(out, opt_level); + } + + let bench = criterion::ParameterizedBenchmark::new( + "compile_hello", + move |b, &&opt_level| { + b.iter_batched_ref( + || TempDir::new().expect("create per-run working directory"), + |workdir| body(workdir, opt_level), + criterion::BatchSize::SmallInput, + ) + }, + &[OptLevel::None, OptLevel::Speed, OptLevel::SpeedAndSize], + ) + .sample_size(10); + + c.bench("compile", bench); +} + +pub fn compile_benches(c: &mut Criterion) { + compile_hello_all(c); +} diff --git a/benchmarks/lucet-benchmarks/src/context.rs b/benchmarks/lucet-benchmarks/src/context.rs new file mode 100644 index 000000000..1803e6735 --- /dev/null +++ b/benchmarks/lucet-benchmarks/src/context.rs @@ -0,0 +1,366 @@ +use criterion::Criterion; +use lucet_runtime_internals::context::{Context, ContextHandle}; + +/// Time the initialization of a context. +fn context_init(c: &mut Criterion) { + extern "C" fn f() {} + + let mut stack = vec![0u64; 1024].into_boxed_slice(); + + c.bench_function("context_init", move |b| { + b.iter(|| { + ContextHandle::create_and_init(&mut *stack, f as usize, &[]).unwrap(); + }) + }); +} + +/// Time the swap from an already-initialized context to a guest function and back. +fn context_swap_return(c: &mut Criterion) { + extern "C" fn f() {} + + c.bench_function("context_swap_return", move |b| { + b.iter_batched( + || { + let mut stack = vec![0u64; 1024].into_boxed_slice(); + let child = ContextHandle::create_and_init(&mut *stack, f as usize, &[]).unwrap(); + (stack, child) + }, + |(stack, mut child)| unsafe { + let mut parent = ContextHandle::new(); + Context::swap(&mut parent, &mut child); + stack + }, + criterion::BatchSize::PerIteration, + ) + }); +} + +/// Time initialization plus swap and return. +fn context_init_swap_return(c: &mut Criterion) { + extern "C" fn f() {} + + c.bench_function("context_init_swap_return", move |b| { + b.iter_batched( + || vec![0u64; 1024].into_boxed_slice(), + |mut stack| { + let mut parent = ContextHandle::new(); + let mut child = + ContextHandle::create_and_init(&mut *stack, f as usize, &[]).unwrap(); + unsafe { Context::swap(&mut parent, &mut child) }; + stack + }, + criterion::BatchSize::PerIteration, + ) + }); +} + +/// Swap to a trivial guest function that takes a bunch of arguments. +fn context_init_swap_return_many_args(c: &mut Criterion) { + extern "C" fn f( + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + ) { + } + + let args = vec![ + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + ]; + + c.bench_function("context_init_swap_return_many_args", move |b| { + b.iter_batched( + || vec![0u64; 1024].into_boxed_slice(), + |mut stack| { + let mut parent = ContextHandle::new(); + let mut child = + ContextHandle::create_and_init(&mut *stack, f as usize, &args).unwrap(); + unsafe { Context::swap(&mut parent, &mut child) }; + stack + }, + criterion::BatchSize::PerIteration, + ) + }); +} + +/// Time the call to pthread_sigmask as used in `Context::init()`. +fn context_pthread_sigmask(c: &mut Criterion) { + use nix::sys::signal; + c.bench_function("context_pthread_sigmask", |b| { + b.iter_batched( + || signal::SigSet::empty(), + |mut sigset| { + signal::pthread_sigmask(signal::SigmaskHow::SIG_SETMASK, None, Some(&mut sigset)) + .unwrap() + }, + criterion::BatchSize::PerIteration, + ) + }); +} + +pub fn context_benches(c: &mut Criterion) { + context_init(c); + context_swap_return(c); + context_init_swap_return(c); + context_init_swap_return_many_args(c); + context_pthread_sigmask(c); +} diff --git a/benchmarks/lucet-benchmarks/src/lib.rs b/benchmarks/lucet-benchmarks/src/lib.rs new file mode 100644 index 000000000..989b582af --- /dev/null +++ b/benchmarks/lucet-benchmarks/src/lib.rs @@ -0,0 +1,16 @@ +mod compile; +mod context; +mod modules; +mod par; +mod seq; + +pub use compile::compile_benches; +pub use context::context_benches; +pub use par::par_benches; +pub use seq::seq_benches; + +#[no_mangle] +extern "C" fn lucet_benchmarks_ensure_linked() { + lucet_runtime::lucet_internal_ensure_linked(); + lucet_wasi::export_wasi_funcs(); +} diff --git a/benchmarks/lucet-benchmarks/src/modules.rs b/benchmarks/lucet-benchmarks/src/modules.rs new file mode 100644 index 000000000..1c789e96c --- /dev/null +++ b/benchmarks/lucet-benchmarks/src/modules.rs @@ -0,0 +1,296 @@ +use lucet_module::lucet_signature; +use lucet_runtime::lucet_hostcall; +use lucet_runtime::vmctx::{lucet_vmctx, Vmctx}; +use lucet_runtime_internals::module::{ + FunctionPointer, HeapSpec, MockExportBuilder, MockModuleBuilder, Module, +}; +use lucet_wasi_sdk::{CompileOpts, Lucetc}; +use lucetc::{Bindings, LucetcOpts, OptLevel}; +use std::path::Path; +use std::sync::Arc; + +fn wasi_bindings() -> Bindings { + let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../lucet-wasi/bindings.json"); + Bindings::from_file(&path).unwrap() +} + +pub fn compile_hello>(so_file: P, opt_level: OptLevel) { + let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("guests/hello.c"); + let wasm_build = Lucetc::new(&[&path]) + .with_cflag("-Wall") + .with_cflag("-Werror") + .with_bindings(wasi_bindings()) + .with_opt_level(opt_level); + + wasm_build.build(&so_file).unwrap(); +} + +pub fn null_mock() -> Arc { + extern "C" fn f(_vmctx: *mut lucet_vmctx) {} + + MockModuleBuilder::new() + .with_export_func(MockExportBuilder::new( + "f", + FunctionPointer::from_usize(f as usize), + )) + .build() +} + +pub fn large_dense_heap_mock(heap_kb: usize) -> Arc { + extern "C" fn f(_vmctx: *mut lucet_vmctx) {} + + let heap_len = heap_kb * 1024; + + let heap_spec = HeapSpec { + reserved_size: heap_len as u64, + guard_size: 4 * 1024 * 1024, + initial_size: heap_len as u64, + max_size: None, + }; + + let mut heap = vec![0x00; heap_len]; + (0..heap_len).into_iter().for_each(|i| { + heap[i] = (i % 256) as u8; + }); + + MockModuleBuilder::new() + .with_export_func(MockExportBuilder::new( + "f", + FunctionPointer::from_usize(f as usize), + )) + .with_initial_heap(heap.as_slice()) + .with_heap_spec(heap_spec) + .build() +} + +pub fn large_sparse_heap_mock(heap_kb: usize, stride: usize) -> Arc { + extern "C" fn f(_vmctx: *mut lucet_vmctx) {} + + let heap_len = heap_kb * 1024; + + let heap_spec = HeapSpec { + reserved_size: heap_len as u64, + guard_size: 4 * 1024 * 1024, + initial_size: heap_len as u64, + max_size: None, + }; + + let mut heap = vec![0x00; heap_len]; + + // fill every `stride`th page with data + (0..heap_len) + .into_iter() + .step_by(4096 * stride) + .for_each(|base| { + for i in base..base + 4096 { + heap[i] = (i % 256) as u8; + } + }); + + MockModuleBuilder::new() + .with_export_func(MockExportBuilder::new( + "f", + FunctionPointer::from_usize(f as usize), + )) + .with_initial_heap(heap.as_slice()) + .with_heap_spec(heap_spec) + .build() +} + +pub fn fib_mock() -> Arc { + extern "C" fn f(_vmctx: *mut lucet_vmctx) { + fn fib(n: u32) -> u32 { + if n == 0 { + 0 + } else if n == 1 { + 1 + } else { + fib(n - 1) + fib(n - 2) + } + } + assert_eq!(fib(25), 75025); + } + + MockModuleBuilder::new() + .with_export_func(MockExportBuilder::new( + "f", + FunctionPointer::from_usize(f as usize), + )) + .build() +} + +pub fn many_args_mock() -> Arc { + extern "C" fn f( + _vmctx: *mut lucet_vmctx, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + _: u8, + _: u16, + _: u32, + _: u64, + _: f32, + _: f64, + ) { + } + + MockModuleBuilder::new() + .with_export_func( + MockExportBuilder::new("f", FunctionPointer::from_usize(f as usize)).with_sig( + lucet_signature!( + ( + I32, I32, I32, I64, F32, F64, + I32, I32, I32, I64, F32, F64, + I32, I32, I32, I64, F32, F64, + I32, I32, I32, I64, F32, F64, + I32, I32, I32, I64, F32, F64, + I32, I32, I32, I64, F32, F64, + I32, I32, I32, I64, F32, F64, + I32, I32, I32, I64, F32, F64, + I32, I32, I32, I64, F32, F64, + I32, I32, I32, I64, F32, F64, + I32, I32, I32, I64, F32, F64 + ) -> () + ), + ), + ) + .build() +} + +pub fn hostcalls_mock() -> Arc { + #[lucet_hostcall] + #[inline(never)] + #[no_mangle] + pub unsafe extern "C" fn hostcall_wrapped( + vmctx: &mut Vmctx, + x1: u64, + x2: u64, + x3: u64, + x4: u64, + x5: u64, + x6: u64, + x7: u64, + x8: u64, + x9: u64, + x10: u64, + x11: u64, + x12: u64, + x13: u64, + x14: u64, + x15: u64, + x16: u64, + ) -> () { + vmctx.heap_mut()[0] = + (x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 + x12 + x13 + x14 + x15 + x16) + as u8; + assert_eq!(vmctx.heap()[0], 136); + } + + #[inline(never)] + #[no_mangle] + pub unsafe extern "C" fn hostcall_raw( + vmctx: *mut lucet_vmctx, + x1: u64, + x2: u64, + x3: u64, + x4: u64, + x5: u64, + x6: u64, + x7: u64, + x8: u64, + x9: u64, + x10: u64, + x11: u64, + x12: u64, + x13: u64, + x14: u64, + x15: u64, + x16: u64, + ) { + let vmctx = Vmctx::from_raw(vmctx); + vmctx.heap_mut()[0] = + (x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 + x12 + x13 + x14 + x15 + x16) + as u8; + assert_eq!(vmctx.heap()[0], 136); + } + + unsafe extern "C" fn wrapped(vmctx: *mut lucet_vmctx) { + for _ in 0..1000 { + hostcall_wrapped(vmctx, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); + } + } + + unsafe extern "C" fn raw(vmctx: *mut lucet_vmctx) { + for _ in 0..1000 { + hostcall_raw(vmctx, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); + } + } + + MockModuleBuilder::new() + .with_export_func(MockExportBuilder::new( + "wrapped", + FunctionPointer::from_usize(wrapped as usize), + )) + .with_export_func(MockExportBuilder::new( + "raw", + FunctionPointer::from_usize(raw as usize), + )) + .build() +} diff --git a/benchmarks/lucet-benchmarks/src/par.rs b/benchmarks/lucet-benchmarks/src/par.rs new file mode 100644 index 000000000..be017d39c --- /dev/null +++ b/benchmarks/lucet-benchmarks/src/par.rs @@ -0,0 +1,148 @@ +use crate::modules::{compile_hello, fib_mock, null_mock}; +use criterion::Criterion; +use lucet_runtime::{DlModule, InstanceHandle, Limits, Module, Region, RegionCreate}; +use lucetc::OptLevel; +use rayon::prelude::*; +use std::sync::Arc; +use tempfile::TempDir; + +/// Common definiton of OptLevel +const BENCHMARK_OPT_LEVEL: OptLevel = OptLevel::SpeedAndSize; + +/// Parallel instantiation. +/// +/// This measures how well the region handles concurrent instantiations from multiple +/// threads. Scaling is not necessarily the point here, due to the locks on the region freelist and +/// memory management syscalls, but we do want to make sure the concurrent case isn't slower than +/// single-threaded. +fn par_instantiate(c: &mut Criterion) { + const INSTANCES_PER_RUN: usize = 2000; + + fn setup() -> (Arc, Vec>) { + let region = R::create(INSTANCES_PER_RUN, &Limits::default()).unwrap(); + let mut handles = vec![]; + handles.resize_with(INSTANCES_PER_RUN, || None); + (region, handles) + } + + fn body( + num_threads: usize, + module: Arc, + region: Arc, + handles: &mut [Option], + ) { + rayon::ThreadPoolBuilder::new() + .num_threads(num_threads) + .build() + .unwrap() + .install(|| { + handles + .par_iter_mut() + .for_each(|handle| *handle = Some(region.new_instance(module.clone()).unwrap())) + }) + } + + let workdir = TempDir::new().expect("create working directory"); + + let so_file = workdir.path().join("out.so"); + compile_hello(&so_file, BENCHMARK_OPT_LEVEL); + + let module = DlModule::load(&so_file).unwrap(); + + let bench = criterion::ParameterizedBenchmark::new( + format!("par_instantiate ({})", R::TYPE_NAME), + move |b, &num_threads| { + b.iter_batched( + setup, + |(region, mut handles): (Arc, _)| { + body(num_threads, module.clone(), region, handles.as_mut_slice()) + }, + criterion::BatchSize::SmallInput, + ) + }, + (1..=num_cpus::get_physical()).collect::>(), + ) + .sample_size(10); + + c.bench("par", bench); + + workdir.close().unwrap(); +} + +/// Run a function in parallel. +fn par_run( + name: &str, + instances_per_run: usize, + module: Arc, + c: &mut Criterion, +) { + let setup = move || { + let region = R::create(instances_per_run, &Limits::default()).unwrap(); + + (0..instances_per_run) + .into_iter() + .map(|_| region.new_instance(module.clone()).unwrap()) + .collect::>() + }; + + fn body(num_threads: usize, handles: &mut [InstanceHandle]) { + rayon::ThreadPoolBuilder::new() + .num_threads(num_threads) + .build() + .unwrap() + .install(|| { + handles.par_iter_mut().for_each(|handle| { + handle.run("f", &[]).unwrap(); + }) + }) + } + + let bench = criterion::ParameterizedBenchmark::new( + name, + move |b, &num_threads| { + b.iter_batched_ref( + setup.clone(), + |handles| body(num_threads, handles.as_mut_slice()), + criterion::BatchSize::SmallInput, + ) + }, + (1..=num_cpus::get_physical()).collect::>(), + ) + .sample_size(10); + + c.bench("par", bench); +} + +/// Run a trivial function in parallel. +/// +/// This measures how well the region handles concurrent executions from multiple threads. Since the +/// body of the function is empty, scaling is not necessarily the point here, rather we want to make +/// sure that the locks for signal handling don't unduly slow the program down with multiple +/// threads. +fn par_run_null(c: &mut Criterion) { + par_run::( + &format!("par_run_null ({})", R::TYPE_NAME), + 1000, + null_mock(), + c, + ); +} + +/// Run a computation-heavy function in parallel. +/// +/// Since running multiple independent fibonaccis is embarassingly parallel, this should scale close +/// to linearly. +fn par_run_fib(c: &mut Criterion) { + par_run::( + &format!("par_run_fib ({})", R::TYPE_NAME), + 1000, + fib_mock(), + c, + ); +} + +pub fn par_benches(c: &mut Criterion) { + par_instantiate::(c); + par_run_null::(c); + par_run_fib::(c); +} diff --git a/benchmarks/lucet-benchmarks/src/seq.rs b/benchmarks/lucet-benchmarks/src/seq.rs new file mode 100644 index 000000000..6529f6c4c --- /dev/null +++ b/benchmarks/lucet-benchmarks/src/seq.rs @@ -0,0 +1,423 @@ +use crate::modules::*; +use criterion::Criterion; +use lucet_runtime::{DlModule, InstanceHandle, Limits, Module, Region, RegionCreate}; +use lucet_wasi::WasiCtxBuilder; +use lucetc::OptLevel; +use std::path::Path; +use std::sync::Arc; +use tempfile::TempDir; + +/// Common definiton of OptLevel +const BENCHMARK_OPT_LEVEL: OptLevel = OptLevel::SpeedAndSize; + +const DENSE_HEAP_SIZES_KB: &'static [usize] = + &[0, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2 * 1024, 4 * 1024]; + +const SPARSE_HEAP_SIZES_KB: &'static [usize] = &[0, 256, 512, 1024, 2 * 1024, 4 * 1024]; + +/// End-to-end instance instantiation. +/// +/// This is meant to simulate our startup time when we start from scratch, with no module loaded and +/// no region created at all. This would be unusual for a server application, but reflects what +/// one-shot command-line tools like `lucet-wasi` do. +/// +/// To minimize the effects of filesystem cache on the `DlModule::load()`, this runs `sync` between +/// each iteration. +fn hello_load_mkregion_and_instantiate(c: &mut Criterion) { + lucet_wasi::export_wasi_funcs(); + fn body(so_file: &Path) -> InstanceHandle { + let module = DlModule::load(so_file).unwrap(); + let region = R::create(1, &Limits::default()).unwrap(); + region.new_instance(module).unwrap() + } + + let workdir = TempDir::new().expect("create working directory"); + + let so_file = workdir.path().join("out.so"); + compile_hello(&so_file, BENCHMARK_OPT_LEVEL); + + c.bench_function( + &format!("hello_load_mkregion_and_instantiate ({})", R::TYPE_NAME), + move |b| { + b.iter_batched( + || unsafe { nix::libc::sync() }, + |_| body::(&so_file), + criterion::BatchSize::PerIteration, + ) + }, + ); + + workdir.close().unwrap(); +} + +/// Instance instantiation. +/// +/// This simulates a typical case for a server process like Terrarium: the region and module stay +/// initialized, but a new instance is created for each request. +fn hello_instantiate(c: &mut Criterion) { + fn body(module: Arc, region: Arc) -> InstanceHandle { + region.new_instance(module).unwrap() + } + + let workdir = TempDir::new().expect("create working directory"); + + let so_file = workdir.path().join("out.so"); + compile_hello(&so_file, BENCHMARK_OPT_LEVEL); + + let module = DlModule::load(&so_file).unwrap(); + let region = R::create(1, &Limits::default()).unwrap(); + + c.bench_function(&format!("hello_instantiate ({})", R::TYPE_NAME), move |b| { + b.iter(|| body(module.clone(), region.clone())) + }); + + workdir.close().unwrap(); +} + +/// Instance instantiation with a large, dense heap. +fn instantiate_with_dense_heap(c: &mut Criterion) { + fn body(module: Arc, region: Arc) -> InstanceHandle { + region.new_instance(module).unwrap() + } + + let limits = Limits { + heap_memory_size: 1024 * 1024 * 1024, + ..Limits::default() + }; + + let region = R::create(1, &limits).unwrap(); + + c.bench_function_over_inputs( + &format!("instantiate_with_dense_heap ({})", R::TYPE_NAME), + move |b, &&heap_kb| { + let module = large_dense_heap_mock(heap_kb); + b.iter(|| body(module.clone(), region.clone())) + }, + DENSE_HEAP_SIZES_KB, + ); +} + +/// Instance instantiation with a large, sparse heap. +fn instantiate_with_sparse_heap(c: &mut Criterion) { + fn body(module: Arc, region: Arc) -> InstanceHandle { + region.new_instance(module).unwrap() + } + + let limits = Limits { + heap_memory_size: 1024 * 1024 * 1024, + ..Limits::default() + }; + + let region = R::create(1, &limits).unwrap(); + + c.bench_function_over_inputs( + &format!("instantiate_with_sparse_heap ({})", R::TYPE_NAME), + move |b, &&heap_kb| { + // 8 means that only every eighth page has non-zero data + let module = large_sparse_heap_mock(heap_kb, 8); + b.iter(|| body(module.clone(), region.clone())) + }, + SPARSE_HEAP_SIZES_KB, + ); +} + +/// Instance destruction. +/// +/// Instances have some cleanup to do with memory management and freeing their slot on their region. +fn hello_drop_instance(c: &mut Criterion) { + fn body(_inst: InstanceHandle) {} + + let workdir = TempDir::new().expect("create working directory"); + + let so_file = workdir.path().join("out.so"); + compile_hello(&so_file, BENCHMARK_OPT_LEVEL); + + let module = DlModule::load(&so_file).unwrap(); + let region = R::create(1, &Limits::default()).unwrap(); + + c.bench_function( + &format!("hello_drop_instance ({})", R::TYPE_NAME), + move |b| { + b.iter_batched( + || region.new_instance(module.clone()).unwrap(), + |inst| body(inst), + criterion::BatchSize::PerIteration, + ) + }, + ); + + workdir.close().unwrap(); +} + +/// Instance destruction with a large, dense heap. +fn drop_instance_with_dense_heap(c: &mut Criterion) { + fn body(_inst: InstanceHandle) {} + + let limits = Limits { + heap_memory_size: 1024 * 1024 * 1024, + ..Limits::default() + }; + + let region = R::create(1, &limits).unwrap(); + + c.bench_function_over_inputs( + &format!("drop_instance_with_dense_heap ({})", R::TYPE_NAME), + move |b, &&heap_kb| { + let module = large_dense_heap_mock(heap_kb); + b.iter_batched( + || region.clone().new_instance(module.clone()).unwrap(), + |inst| body(inst), + criterion::BatchSize::PerIteration, + ) + }, + DENSE_HEAP_SIZES_KB, + ); +} + +/// Instance destruction with a large, sparse heap. +fn drop_instance_with_sparse_heap(c: &mut Criterion) { + fn body(_inst: InstanceHandle) {} + + let limits = Limits { + heap_memory_size: 1024 * 1024 * 1024, + ..Limits::default() + }; + + let region = R::create(1, &limits).unwrap(); + + c.bench_function_over_inputs( + &format!("drop_instance_with_sparse_heap ({})", R::TYPE_NAME), + move |b, &&heap_kb| { + // 8 means that only every eighth page has non-zero data + let module = large_sparse_heap_mock(heap_kb, 8); + b.iter_batched( + || region.clone().new_instance(module.clone()).unwrap(), + |inst| body(inst), + criterion::BatchSize::PerIteration, + ) + }, + SPARSE_HEAP_SIZES_KB, + ); +} + +/// Run a trivial guest function. +/// +/// This is primarily a measurement of the signal handler installation and removal, and the context +/// switching overhead. +fn run_null(c: &mut Criterion) { + fn body(inst: &mut InstanceHandle) { + inst.run("f", &[]).unwrap(); + } + + let module = null_mock(); + let region = R::create(1, &Limits::default()).unwrap(); + + c.bench_function(&format!("run_null ({})", R::TYPE_NAME), move |b| { + b.iter_batched_ref( + || region.new_instance(module.clone()).unwrap(), + |inst| body(inst), + criterion::BatchSize::PerIteration, + ) + }); +} + +/// Run a computation-heavy guest function from a mock module. +/// +/// Since this is running code in a mock module, the cost of the computation should overwhelm the +/// cost of the Lucet runtime. +fn run_fib(c: &mut Criterion) { + fn body(inst: &mut InstanceHandle) { + inst.run("f", &[]).unwrap(); + } + + let module = fib_mock(); + let region = R::create(1, &Limits::default()).unwrap(); + + c.bench_function(&format!("run_fib ({})", R::TYPE_NAME), move |b| { + b.iter_batched_ref( + || region.new_instance(module.clone()).unwrap(), + |inst| body(inst), + criterion::BatchSize::PerIteration, + ) + }); +} + +/// Run a trivial WASI program. +fn run_hello(c: &mut Criterion) { + fn body(inst: &mut InstanceHandle) { + inst.run("_start", &[]).unwrap(); + } + + let workdir = TempDir::new().expect("create working directory"); + + let so_file = workdir.path().join("out.so"); + compile_hello(&so_file, BENCHMARK_OPT_LEVEL); + + let module = DlModule::load(&so_file).unwrap(); + let region = R::create(1, &Limits::default()).unwrap(); + + c.bench_function(&format!("run_hello ({})", R::TYPE_NAME), move |b| { + b.iter_batched_ref( + || { + let ctx = WasiCtxBuilder::new() + .args(["hello"].iter()) + .build() + .expect("build WasiCtx"); + region + .new_instance_builder(module.clone()) + .with_embed_ctx(ctx) + .build() + .unwrap() + }, + |inst| body(inst), + criterion::BatchSize::PerIteration, + ) + }); +} + +/// Run a trivial guest function that takes a bunch of arguments. +/// +/// This is primarily interesting as a comparison to `run_null`; the difference is the overhead of +/// installing the arguments into the guest registers and stack. +/// +/// `rustfmt` hates this function. +fn run_many_args(c: &mut Criterion) { + fn body(inst: &mut InstanceHandle) { + inst.run( + "f", + &[ + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + 0xAFu8.into(), + 0xAFu16.into(), + 0xAFu32.into(), + 0xAFu64.into(), + 175.0f32.into(), + 175.0f64.into(), + ], + ) + .unwrap(); + } + + let module = many_args_mock(); + let region = R::create(1, &Limits::default()).unwrap(); + + c.bench_function(&format!("run_many_args ({})", R::TYPE_NAME), move |b| { + b.iter_batched_ref( + || region.new_instance(module.clone()).unwrap(), + |inst| body(inst), + criterion::BatchSize::PerIteration, + ) + }); +} + +fn run_hostcall_wrapped(c: &mut Criterion) { + fn body(inst: &mut InstanceHandle) { + inst.run("wrapped", &[]).unwrap(); + } + + let module = hostcalls_mock(); + let region = R::create(1, &Limits::default()).unwrap(); + + c.bench_function( + &format!("run_hostcall_wrapped ({})", R::TYPE_NAME), + move |b| { + b.iter_batched_ref( + || region.new_instance(module.clone()).unwrap(), + |inst| body(inst), + criterion::BatchSize::PerIteration, + ) + }, + ); +} + +fn run_hostcall_raw(c: &mut Criterion) { + fn body(inst: &mut InstanceHandle) { + inst.run("raw", &[]).unwrap(); + } + + let module = hostcalls_mock(); + let region = R::create(1, &Limits::default()).unwrap(); + + c.bench_function(&format!("run_hostcall_raw ({})", R::TYPE_NAME), move |b| { + b.iter_batched_ref( + || region.new_instance(module.clone()).unwrap(), + |inst| body(inst), + criterion::BatchSize::PerIteration, + ) + }); +} + +pub fn seq_benches(c: &mut Criterion) { + hello_load_mkregion_and_instantiate::(c); + hello_instantiate::(c); + instantiate_with_dense_heap::(c); + instantiate_with_sparse_heap::(c); + hello_drop_instance::(c); + drop_instance_with_dense_heap::(c); + drop_instance_with_sparse_heap::(c); + run_null::(c); + run_fib::(c); + run_hello::(c); + run_many_args::(c); + run_hostcall_wrapped::(c); + run_hostcall_raw::(c); +} diff --git a/benchmarks/shootout/Makefile b/benchmarks/shootout/Makefile index 3c335e087..6ab2406d1 100644 --- a/benchmarks/shootout/Makefile +++ b/benchmarks/shootout/Makefile @@ -1,7 +1,9 @@ LUCET_ROOT:=$(abspath ../..) -WASI_SDK?=/opt/wasi -WASI_CC=$(WASI_SDK)/bin/clang +WASI_SDK?=/opt/wasi-sdk +WASI_CC?=$(WASI_SDK)/bin/clang +BINARYEN_DIR?=/opt/binaryen +WASM_OPT?=$(shell command -v wasm-opt || echo $(BINARYEN_DIR)/bin/wasm-opt) LUCETC:=$(LUCET_ROOT)/target/release/lucetc @@ -16,13 +18,15 @@ SHOOTOUT_SRCS:=$(shell ls $(SHOOTOUT)/*.c) SHOOTOUT_NATIVE_OBJS:= SHOOTOUT_LUCET_OBJS:= -LUCETC_FLAGS:=--opt-level best --reserved-size 4294967296 +LUCETC_FLAGS:=--opt-level fast --min-reserved-size 4294967296 COMMON_CFLAGS:=--std=c99 -Ofast -Wall -W -I$(SIGHTGLASS)/include SHOOTOUT_NATIVE_CFLAGS:=-march=native -fPIC \ -DIMPL_REFERENCE -DUSE_LEND \ -Dmalloc=lend_malloc -Dcalloc=lend_calloc -Dfree=lend_free +BINARYEN_VERSION=86 + ifdef CI SIGHTGLASS_ARGS:=--quick endif @@ -60,10 +64,13 @@ build/lucet/shootout/%.o: $(SHOOTOUT)/%.c @mkdir -p $(@D) $(WASI_CC) $(COMMON_CFLAGS) -c $^ -o $@ -build/lucet/module.wasm: $(patsubst %.c, %.o, $(addprefix build/lucet/shootout/, $(notdir $(SHOOTOUT_SRCS)))) +build/lucet/module.wasm.unoptimized: $(patsubst %.c, %.o, $(addprefix build/lucet/shootout/, $(notdir $(SHOOTOUT_SRCS)))) @mkdir -p $(@D) $(WASI_CC) $^ -o $@ -nostartfiles -Wl,--no-entry -Wl,--export-all +build/lucet/module.wasm: build/lucet/module.wasm.unoptimized + $(WASM_OPT) -mvp --disable-mutable-globals -O4 -o $@ $^ + # Don't emit the shared object, we need a saparate link step below. build/lucet/module.o: build/lucet/module.wasm $(LIBBUILTINS) @mkdir -p $(@D) diff --git a/benchmarks/shootout/wrapper.c b/benchmarks/shootout/wrapper.c index 68d78060b..57c7e2bc7 100644 --- a/benchmarks/shootout/wrapper.c +++ b/benchmarks/shootout/wrapper.c @@ -62,7 +62,8 @@ static void setup_wrapper(const char *name, void *global_ctx_, void **ctx_p) (void) global_ctx_; ASSERT_OK(lucet_instance_run( lucet_ctx.inst, name, 2, - (struct lucet_val[]){ LUCET_VAL_GUEST_PTR(0), LUCET_VAL_GUEST_PTR(lucet_ctx.ctx_p) })); + (struct lucet_val[]){ LUCET_VAL_GUEST_PTR(0), LUCET_VAL_GUEST_PTR(lucet_ctx.ctx_p) }, + NULL)); *ctx_p = (void *) (uintptr_t) * (guest_ptr_t *) &lucet_ctx.heap[lucet_ctx.ctx_p]; } @@ -84,7 +85,8 @@ static void setup_wrapper(const char *name, void *global_ctx_, void **ctx_p) static void body_wrapper(const char *name, void *ctx) { lucet_instance_run(lucet_ctx.inst, name, 1, - (struct lucet_val[]){ LUCET_VAL_GUEST_PTR((guest_ptr_t)(uintptr_t) ctx) }); + (struct lucet_val[]){ LUCET_VAL_GUEST_PTR((guest_ptr_t)(uintptr_t) ctx) }, + NULL); } #define BODY(NAME) \ @@ -93,7 +95,8 @@ static void body_wrapper(const char *name, void *ctx) static void teardown_wrapper(const char *name, void *ctx) { lucet_instance_run(lucet_ctx.inst, name, 1, - (struct lucet_val[]){ LUCET_VAL_GUEST_PTR((guest_ptr_t)(uintptr_t) ctx) }); + (struct lucet_val[]){ LUCET_VAL_GUEST_PTR((guest_ptr_t)(uintptr_t) ctx) }, + NULL); } #define TEARDOWN(NAME) \ @@ -164,11 +167,11 @@ TEARDOWN_NOWRAP(nestedloop) SETUP(random2) BODY(random2) -TEARDOWN(random2) +TEARDOWN_NOWRAP(random2) SETUP(random) BODY(random) -TEARDOWN(random) +TEARDOWN_NOWRAP(random) SETUP(ratelimit) BODY(ratelimit) diff --git a/cranelift b/cranelift index 894cecc78..da996fe5e 160000 --- a/cranelift +++ b/cranelift @@ -1 +1 @@ -Subproject commit 894cecc78fa3169c7efd74bafb5165315f0f7f36 +Subproject commit da996fe5ee4ea32a57156ba93f6813b052964183 diff --git a/devenv_build_container.sh b/devenv_build_container.sh index 467642a91..9bd8fec05 100755 --- a/devenv_build_container.sh +++ b/devenv_build_container.sh @@ -1,16 +1,57 @@ #!/bin/sh +set -e . "$(dirname ${BASH_SOURCE:-$0})/config.inc" git submodule update --init 2>/dev/null ||: if docker image inspect lucet-dev:latest > /dev/null; then - if [ -z "$DEVENV_FORCE_REBUILD" ]; then - echo "A lucet-dev image is already present" - echo "Hit Ctrl-C right now if you don't want to rebuild it" - echo "or skip this wait by setting the DEVENV_FORCE_REBUILD variable" - sleep 30 - fi + if [ -z "$DEVENV_FORCE_REBUILD" ]; then + echo "A lucet-dev image is already present" + echo "Hit Ctrl-C right now if you don't want to rebuild it" + echo "or skip this wait by setting the DEVENV_FORCE_REBUILD variable" + sleep 30 + fi fi +echo "Building lucet-dev:latest" docker build -t lucet-dev:latest . + +docker tag lucet-dev:latest lucet:latest + +if [ ! -z "$DEVENV_SKIP_LUCET_BUILD" ]; then + echo "Done" + exit 0 +fi + +if docker image inspect lucet:latest > /dev/null; then + if [ -z "$DEVENV_FORCE_REBUILD" ]; then + echo "A lucet image is already present" + echo "Hit Ctrl-C right now if you don't want to rebuild it" + echo "or skip this wait by setting the DEVENV_FORCE_REBUILD variable" + sleep 30 + fi +fi + +echo "Now creating lucet:latest on top of lucet-dev:latest" +docker run --name=lucet-dev --detach --mount type=bind,src="$(cd $(dirname ${0}); pwd),target=/lucet" \ + lucet-dev:latest /bin/sleep 99999999 > /dev/null + +echo "Building and installing optimized files in [$HOST_LUCET_MOUNT_POINT]" +if [ -z "$UNOPTIMIZED_BUILD" ]; then + docker exec -t -w "$HOST_LUCET_MOUNT_POINT" lucet-dev make install +else + docker exec -t -w "$HOST_LUCET_MOUNT_POINT" lucet-dev make install-dev +fi + +echo "Cleaning" +docker exec -t -w "$HOST_LUCET_MOUNT_POINT" lucet-dev make clean + +echo "Tagging the new image" +docker container commit lucet-dev lucet:latest + +echo "Cleaning" +docker kill lucet-dev +docker rm lucet-dev + +echo "Done" diff --git a/devenv_build_toolchain_only.sh b/devenv_build_toolchain_only.sh new file mode 100755 index 000000000..42439f231 --- /dev/null +++ b/devenv_build_toolchain_only.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +. "$(dirname ${BASH_SOURCE:-$0})/config.inc" + +git submodule update --init 2>/dev/null || : + +if ! docker image inspect lucet:latest >/dev/null; then + echo "A lucet image is not present" + exit 1 +fi + +echo "Building lucet-toolchain:latest" +docker build -t lucet-toolchain:latest -f Dockerfile.toolchain . + +echo "Starting the lucet container" +docker run --name=lucet --detach --mount type=bind,src="$(cd $(dirname ${0}); pwd),target=/lucet" \ + lucet:latest /bin/sleep 99999999 > /dev/null + +echo "Creating a container from the lucet-toolchain:latest image" +docker run --name=lucet-toolchain --detach --mount type=bind,src="$( + cd $(dirname ${0}) || exit 1 + pwd -P +),target=/lucet" \ + lucet-toolchain:latest /bin/sleep 99999999 >/dev/null + +docker exec lucet tar c -pf - -C /opt lucet | + docker exec -i lucet-toolchain tar x -pf - -C /opt + +docker exec lucet-toolchain mkdir /opt/wasi-sysroot + +docker exec lucet tar c -pf - -C /opt/wasi-sdk/share/wasi-sysroot . | + docker exec -i lucet-toolchain tar x -pf - -C /opt/wasi-sysroot + +docker exec lucet-toolchain sh -c 'rm -f /opt/lucet/bin/wasm32-*' + +docker exec -i lucet-toolchain sh -c 'cat > /opt/lucet/bin/wasm32-wasi-clang; chmod 755 /opt/lucet/bin/wasm32-wasi-clang' < /opt/lucet/bin/wasm32-wasi-clang++; chmod 755 /opt/lucet/bin/wasm32-wasi-clang++' < /dev/null; then +if ! docker image inspect lucet:latest > /dev/null; then ${HOST_BASE_PREFIX}/devenv_build_container.sh fi -if docker ps -f name=lucet | grep -Fq lucet ; then +if docker ps -f name='lucet' --format '{{.Names}}' | grep -q '^lucet$' ; then echo "container is already running" >&2 exit 1 fi docker run --name=lucet --detach --mount type=bind,src="$(cd $(dirname ${0}); pwd -P),target=/lucet" \ - lucet-dev:latest /bin/sleep 99999999 > /dev/null - -if [ -z "$DEVENV_NO_INSTALL" ]; then - if ! docker exec -t -w "$HOST_LUCET_MOUNT_POINT" lucet stat "$LUCET_PREFIX" > /dev/null ; then - echo "Lucet hasn't been installed yet... installing..." - docker exec -t -w "$HOST_LUCET_MOUNT_POINT" lucet make install - fi -fi + lucet:latest /bin/sleep 99999999 > /dev/null diff --git a/faerie b/faerie deleted file mode 160000 index ebe9edff9..000000000 --- a/faerie +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ebe9edff906749bd52718b8552d560b232a608ff diff --git a/helpers/bump-global-version.sh b/helpers/bump-global-version.sh new file mode 100755 index 000000000..a55d07149 --- /dev/null +++ b/helpers/bump-global-version.sh @@ -0,0 +1,27 @@ +#! /bin/sh + +VERSION=$(grep '#*Lucet version ' Cargo.toml | sed 's/^ *# Lucet version *//') +[ -z "$VERSION" ] && echo "Version header not found in the top Cargo.toml file" >&2 && exit 1 + +dry_run() { + echo "Checking if the package can be published (dry run)..." + echo + find lucetc lucet-* benchmarks/lucet-* lucet-runtime/lucet-* -type f -maxdepth 1 -name 'Cargo.toml' -print | while read -r file; do + dir="$(dirname $file)" + echo "* Checking [$dir]" + (cd "$dir" && cargo publish --allow-dirty --dry-run >/dev/null) || exit 1 + done || exit 1 + echo + echo "Done." +} + +version_bump() { + echo + echo "Setting the global Lucet version number to: [$VERSION]" + find lucetc lucet-* benchmarks/lucet-* -type f -maxdepth 1 -name 'Cargo.toml' -print | while read -r file; do + sed -i'.previous' "s/^ *version *=.*/version = \"${VERSION}\"/" "$file" && rm -f "${file}.previous" + done + echo "Done." +} + +dry_run && version_bump && dry_run diff --git a/helpers/indent.sh b/helpers/indent.sh index 642378999..be4faea7d 100755 --- a/helpers/indent.sh +++ b/helpers/indent.sh @@ -1,36 +1,37 @@ #!/bin/bash set -e ARG=$1 -cleanup () { +cleanup() { if [[ $ARG == "check" ]]; then echo "" echo "Formatting diffs detected! run \"./indent\" to correct." fi rm -f .formatted } -trap cleanup 1 2 3 6 9 15 +trap cleanup 1 2 3 6 15 -if ! $(rustfmt --version | grep -q "rustfmt 1.0.1-stable"); then - echo "indent requires rustfmt 1.0.1-stable" - exit 1; +RUSTFMT_VERSION=1.4.9-stable + +if ! rustfmt --version | grep -q "rustfmt $RUSTFMT_VERSION"; then + echo "indent requires rustfmt $RUSTFMT_VERSION" + exit 1 fi -RUST_DIRS=$(find lucet-analyze lucet-idl lucet-spectest lucetc lucet-runtime lucet-wasi-sdk -type f -name 'Cargo.toml' -print) +RUST_DIRS=$(find lucetc lucet-* benchmarks/lucet-* -type f -name 'Cargo.toml' -print) if [[ $ARG == "check" ]]; then for RUST_DIR in ${RUST_DIRS}; do - pushd $(dirname ${RUST_DIR}) > /dev/null + pushd "$(dirname ${RUST_DIR})" >/dev/null cargo fmt -- --check - popd > /dev/null + popd >/dev/null done elif [[ $ARG == "" ]]; then for RUST_DIR in ${RUST_DIRS}; do - pushd $(dirname ${RUST_DIR}) > /dev/null + pushd "$(dirname ${RUST_DIR})" >/dev/null cargo fmt - popd > /dev/null + popd >/dev/null done else echo "unsupported argument: $1" exit 1 fi - diff --git a/helpers/install.sh b/helpers/install.sh index c2e2e9e6a..9ef65925b 100755 --- a/helpers/install.sh +++ b/helpers/install.sh @@ -1,14 +1,23 @@ #! /bin/sh -LUCET_SRC_PREFIX=${LUCET_SRC_PREFIX:-"$(readlink -e $(dirname $(dirname ${0})))"} +LUCET_SRC_PREFIX=${LUCET_SRC_PREFIX:-"$( + cd $(dirname $(dirname ${0})) + pwd -P +)"} if [ ! -x "${LUCET_SRC_PREFIX}/helpers/install.sh" ]; then echo "Unable to find the current script base directory" >&2 exit 1 fi +if [ "$1" = "--unoptimized" ]; then + LUCET_BUILD_TYPE="debug" +else + LUCET_BUILD_TYPE="release" +fi + LUCET_PREFIX=${LUCET_PREFIX:-"/opt/lucet"} LUCET_SRC_PREFIX=${LUCET_SRC_PREFIX:-"/lucet"} -LUCET_SRC_RELEASE_DIR=${LUCET_SRC_RELEASE_DIR:-"${LUCET_SRC_PREFIX}/target/release"} +LUCET_SRC_RELEASE_DIR=${LUCET_SRC_RELEASE_DIR:-"${LUCET_SRC_PREFIX}/target/${LUCET_BUILD_TYPE}"} LUCET_BIN_DIR=${LUCET_BIN_DIR:-"${LUCET_PREFIX}/bin"} LUCET_LIB_DIR=${LUCET_LIB_DIR:-"${LUCET_PREFIX}/lib"} LUCET_LIBEXEC_DIR=${LUCET_LIBEXEC_DIR:-"${LUCET_PREFIX}/libexec"} @@ -17,37 +26,138 @@ LUCET_SHARE_DIR=${LUCET_SHARE_DIR:-"${LUCET_PREFIX}/share"} LUCET_EXAMPLES_DIR=${LUCET_EXAMPLES_DIR:-"${LUCET_SHARE_DIR}/examples"} LUCET_DOC_DIR=${LUCET_DOC_DIR:-"${LUCET_SHARE_DIR}/doc"} LUCET_BUNDLE_DOC_DIR=${LUCET_BUNDLE_DOC_DIR:-"${LUCET_DOC_DIR}/lucet"} -WASI_PREFIX=${WASI_PREFIX:-${WASI_SDK:-"/opt/wasi-sdk"}} -WASI_BIN=${WASI_BIN:-"${WASI_PREFIX}/bin"} -WASI_SYSROOT=${WASI_SYSROOT:-"${WASI_PREFIX}/share/sysroot"} -WASI_TARGET=${WASI_TARGET:-"wasm32-unknown-wasi"} +WASI_SDK_PREFIX=${WASI_SDK_PREFIX:-${WASI_SDK:-"/opt/wasi-sdk"}} +WASI_TARGET=${WASI_TARGET:-"wasm32-wasi"} WASI_BIN_PREFIX=${WASI_BIN_PREFIX:-"$WASI_TARGET"} +BINARYEN_DIR=${BINARYEN_DIR:-"/opt/binaryen"} +BINARYEN_BIN_DIR=${BINARYEN_BIN_DIR:-"${BINARYEN_DIR}/bin"} + +if [ "$(uname -s)" = "Darwin" ]; then + DYLIB_SUFFIX="dylib" +else + DYLIB_SUFFIX="so" +fi -BINS="lucet-analyze lucet-wasi lucetc sightglass spec-test wasmonkey" -LIBS="liblucet_runtime.so" -DOCS="lucet-wasi/README.md sightglass/README.md" +BINS="lucet-objdump lucet-validate lucet-wasi lucetc sightglass spec-test wasmonkey" +LIBS="liblucet_runtime.${DYLIB_SUFFIX}" +DOCS="sightglass/README.md" BUNDLE_DOCS="README.md" -install -d -v "$LUCET_BIN_DIR" +if test -t 0; then + echo + echo "The Lucet toolchain is going to be installed in [${LUCET_PREFIX}]." + echo "The installation prefix can be changed by defining a LUCET_PREFIX environment variable." + echo "Hit Ctrl-C right now to abort before the installation begins." + echo + sleep 10 +fi + +if ! install -d "$LUCET_PREFIX" 2>/dev/null; then + SUDO="" + if command -v doas >/dev/null; then + SUDO="doas" + elif command -v sudo >/dev/null; then + SUDO="sudo" + else + echo "[${LUCET_PREFIX}] doesn't exist and cannot be created" >&2 + exit 1 + fi + echo "[${LUCET_PREFIX}] doesn't exist and the $SUDO command is required to create it" + if ! "$SUDO" install -o "$(id -u)" -d "$LUCET_PREFIX"; then + echo "[${LUCET_PREFIX}] doesn't exist and cannot be created even with additional privileges" >&2 + exit 1 + fi +fi + +# Find a WASI sysroot +for wasi_sysroot in $WASI_SYSROOT ${WASI_SDK_PREFIX}/share/wasi-sysroot /opt/wasi-sysroot; do + if [ -e "${wasi_sysroot}/include/wasi/core.h" ]; then + WASI_SYSROOT="$wasi_sysroot" + fi +done +if [ -z "$WASI_SYSROOT" ]; then + echo "The WASI sysroot was not found." >&2 + echo "You may have to define a WASI_SYSROOT environment variable set to its base directory." + exit 1 +fi +echo "* WASI sysroot: [$WASI_SYSROOT]" + +# Find: +# - A clang/llvm installation able to compile to WebAssmbly/WASI +# - The base path to this installation +# - The optional suffix added to clang (e.g. clang-8) +# - The optional suffix added to LLVM tools (e.g. ar-8) that differs from the clang one on some Linux distributions + +TMP_OBJ=$(mktemp) +for llvm_bin_path_candidate in "$LLVM_BIN" "${WASI_SDK_PREFIX}/bin" /usr/local/opt/llvm/bin $(echo "$PATH" | sed s/:/\ /g); do + [ -d "$llvm_bin_path_candidate" ] || continue + clang_candidate=$(find "$llvm_bin_path_candidate" -maxdepth 1 \( -type f -o -type l \) \( -name "clang" -o -name "clang-[0-9]*" \) -print | + sort | while read -r clang_candidate; do + echo "int main(void){return 0;}" | "$clang_candidate" --target=wasm32-wasi -o "$TMP_OBJ" -c -x c - 2>/dev/null || continue + echo "$clang_candidate" + break + done) + [ -z "$clang_candidate" ] && continue + llvm_bin=$(dirname "$clang_candidate") + clang_candidate_bn=$(basename "$clang_candidate") + case "$clang_candidate_bn" in + clang) clang_bin_suffix="none" ;; + clang-[0-9]*) clang_bin_suffix=$(echo "$clang_candidate_bn" | sed "s/^clang//") ;; + *) continue ;; + esac + CLANG_BIN_SUFFIX="$clang_bin_suffix" + LLVM_BIN="$llvm_bin" + if [ -z "$CLANG_BIN_SUFFIX" ] || [ -z "$LLVM_BIN" ]; then + continue + fi + if [ "$CLANG_BIN_SUFFIX" = "none" ]; then + CLANG_BIN_SUFFIX="" + fi + break +done +rm -f "$TMP_OBJ" + +if [ -z "$LLVM_BIN" ]; then + echo "No clang/LLVM installation able to compile to WebAssembly/WASI was found." >&2 + echo "The builtins might be missing -- See the Lucet documentation." >&2 + exit 1 +fi +echo "* LLVM installation directory: [$LLVM_BIN]" +echo "* Suitable clang executable: [clang${CLANG_BIN_SUFFIX}]" + +LLVM_BIN_SUFFIX="$CLANG_BIN_SUFFIX" +if ! command -v "${LLVM_BIN}/llvm-ar${LLVM_BIN_SUFFIX}" >/dev/null; then + LLVM_BIN_SUFFIX="" + if ! command -v "${LLVM_BIN}/llvm-ar${LLVM_BIN_SUFFIX}" >/dev/null; then + echo "LLVM not found" >&2 + exit 1 + fi + echo test +fi +echo "* LLVM tools suffix: [${LLVM_BIN_SUFFIX}] (ex: [llvm-ar${LLVM_BIN_SUFFIX}])" +echo + +install -d -v "$LUCET_BIN_DIR" || exit 1 for bin in $BINS; do install -p -v "${LUCET_SRC_RELEASE_DIR}/${bin}" "${LUCET_BIN_DIR}/${bin}" done -install -d -v "$LUCET_LIB_DIR" +install -d -v "$LUCET_LIB_DIR" || exit 1 for lib in $LIBS; do install -p -v "${LUCET_SRC_RELEASE_DIR}/${lib}" "${LUCET_LIB_DIR}/${lib}" done -install -d -v "$LUCET_LIBEXEC_DIR" +install -d -v "$LUCET_LIBEXEC_DIR" || exit 1 install -p -v "${LUCET_SRC_PREFIX}/lucet-builtins/build/libbuiltins.so" \ - "${LUCET_LIBEXEC_DIR}/libbuiltins.so" + "${LUCET_LIBEXEC_DIR}/libbuiltins.${DYLIB_SUFFIX}" devenv_setenv_file="$(mktemp)" -cat > "$devenv_setenv_file" << EOT +cat >"$devenv_setenv_file" < "$wrapper_file" << EOT + cat >"$wrapper_file" < "$wrapper_file" << EOT +cat >"$wrapper_file" <&2 + exit 1 +fi + +if [ -x "${LUCET_DIR}/target/release/lucet-wasi" ]; then + LUCET_WASI="${LUCET_DIR}/target/release/lucet-wasi" +elif [ -x "${LUCET_DIR}/target/debug/lucet-wasi" ]; then + LUCET_WASI="${LUCET_DIR}/target/debug/lucet-wasi" +else + echo "lucet-wasi not found" >&2 + exit 1 +fi + +if ! command -v rsign >/dev/null; then + cargo install rsign2 + export PATH="${HOME}/.cargo/bin:${PATH}" +fi + +echo "Creating a key pair to sign the WebAssembly code" +( + echo x + echo x +) | rsign generate -p "${TMPDIR}/src_public.key" -s "${TMPDIR}/src_secret.key" -f >/dev/null + +echo "Signing the WebAssembly code" +cp "${LUCET_DIR}/lucetc/tests/wasm/call.wat" "${TMPDIR}/test.wat" +echo x | rsign sign -p "${TMPDIR}/src_public.key" -s "${TMPDIR}/src_secret.key" "${TMPDIR}/test.wat" >/dev/null + +echo "Creating a key pair using lucetc for the compiled code" +if ! "${LUCETC}" \ + --signature-keygen \ + --signature-pk="${TMPDIR}/public.key" \ + --signature-sk="raw:${TMPDIR}/secret.key"; then + echo "Keypair generation failed" >&2 + exit 1 +fi + +echo "Trying to compile source code whose signature is invalid" +if "${LUCETC}" \ + "${TMPDIR}/test.wat" \ + -o "${TMPDIR}/test.so" \ + --signature-verify \ + --signature-pk="${TMPDIR}/public.key" 2>/dev/null; then + echo "Source signature verification with the wrong public key shouldn't have passed" >&2 + exit 1 +fi + +echo "Compiling the verified source code" +if ! "${LUCETC}" \ + "${TMPDIR}/test.wat" \ + -o "${TMPDIR}/test.so" \ + --signature-verify \ + --signature-pk="${TMPDIR}/src_public.key" 2>/dev/null; then + echo "Source signature verification with the correct public key didn't pass" >&2 + exit 1 +fi + +echo "Compiling the verified source code and embedding a signature into the resulting object" +if ! "${LUCETC}" \ + "${TMPDIR}/test.wat" \ + -o "${TMPDIR}/test.so" \ + --signature-create \ + --signature-verify \ + --signature-pk="${TMPDIR}/src_public.key" \ + --signature-sk=raw:"${TMPDIR}/secret.key" 2>/dev/null; then + echo "Compilation failed" >&2 + exit 1 +fi + +echo "Running the resulting object" +if ! "${LUCET_WASI}" \ + "${TMPDIR}/test.so" \ + --signature-verify \ + --signature-pk="${TMPDIR}/public.key" \ + --entrypoint main; then + echo "Runtime failed" >&2 + exit 1 +fi + +echo >>"${TMPDIR}/test.so" + +echo "Trying to run a tampered version of the object" +if "${LUCET_WASI}" \ + "${TMPDIR}/test.so" \ + --signature-verify \ + --signature-pk="${TMPDIR}/public.key" \ + --entrypoint main 2>/dev/null; then + echo "Signature verification of tampered module shouldn't have passed" >&2 + exit 1 +fi + +rm -fr "$TMPDIR" + +echo "Done." diff --git a/lucet-analyze/Cargo.toml b/lucet-analyze/Cargo.toml deleted file mode 100644 index 086ed97d6..000000000 --- a/lucet-analyze/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "lucet-analyze" -version = "0.1.0" -description = "Analyze binaries emitted by lucetc" -repository = "https://github.com/fastly/lucet" -authors = ["tyler "] -license = "Apache-2.0 WITH LLVM-exception" -edition = "2018" - -[dependencies] -goblin="~0.0.17" -byteorder="1.2.1" -colored="1.6.1" diff --git a/lucet-analyze/src/main.rs b/lucet-analyze/src/main.rs deleted file mode 100644 index b52e87a76..000000000 --- a/lucet-analyze/src/main.rs +++ /dev/null @@ -1,527 +0,0 @@ -use byteorder::{LittleEndian, ReadBytesExt}; -use colored::Colorize; -use goblin::{elf, Object}; -use std::env; -use std::fs::File; -use std::io::Cursor; -use std::io::Read; -use std::mem::size_of; - -#[derive(Debug)] -struct ArtifactSummary<'a> { - buffer: &'a Vec, - elf: &'a elf::Elf<'a>, - symbols: StandardSymbols, - heap_spec: Option, - globals_spec: Option, - data_segments: Option, - sparse_page_data: Option, - trap_manifest: Option, - exported_functions: Vec<&'a str>, - imported_symbols: Vec<&'a str>, -} - -#[derive(Debug)] -struct StandardSymbols { - lucet_trap_manifest: Option, - lucet_trap_manifest_len: Option, - wasm_data_segments: Option, - wasm_data_segments_len: Option, - lucet_heap_spec: Option, - lucet_globals_spec: Option, - guest_sparse_page_data: Option, -} - -#[derive(Debug)] -struct TrapManifest { - records: Vec, -} - -#[derive(Debug)] -struct TrapManifestRow { - func_name: String, - func_addr: u64, - func_len: u64, - trap_count: u64, - sites: Vec, -} - -#[derive(Debug)] -struct TrapSite { - offset: u32, - reason: u32, -} - -#[derive(Debug)] -struct DataSegments { - segments: Vec, -} - -#[derive(Debug)] -struct DataSegment { - offset: u32, - len: u32, - data: Vec, -} - -#[derive(Debug)] -struct SparsePageData { - pages: Vec<*const u8>, -} - -#[derive(Debug)] -struct HeapSpec { - reserved_size: u64, - guard_size: u64, - initial_size: u64, - max_size: Option, -} - -#[derive(Debug)] -struct GlobalsSpec { - count: u64, -} - -impl<'a> ArtifactSummary<'a> { - fn new(buffer: &'a Vec, elf: &'a elf::Elf) -> Self { - Self { - buffer: buffer, - elf: elf, - symbols: StandardSymbols { - lucet_trap_manifest: None, - lucet_trap_manifest_len: None, - wasm_data_segments: None, - wasm_data_segments_len: None, - lucet_heap_spec: None, - lucet_globals_spec: None, - guest_sparse_page_data: None, - }, - heap_spec: None, - globals_spec: None, - data_segments: None, - sparse_page_data: None, - trap_manifest: None, - exported_functions: Vec::new(), - imported_symbols: Vec::new(), - } - } - - fn read_memory(&self, addr: u64, size: u64) -> Option> { - for header in &self.elf.program_headers { - if header.p_type == elf::program_header::PT_LOAD { - // Bounds check the entry - if addr >= header.p_vaddr && (addr + size) < (header.p_vaddr + header.p_memsz) { - let start = (addr - header.p_vaddr + header.p_offset) as usize; - let end = start + size as usize; - - return Some(self.buffer[start..end].to_vec()); - } - } - } - - None - } - - fn gather(&mut self) { - // println!("Syms"); - // for sym in eo.syms.iter() { - // let name = eo.strtab - // .get(sym.st_name) - // .unwrap_or(Ok("(no name)")) - // .expect("strtab entry"); - - // println!("Sym: name={} {:?}", name, sym); - // } - - // println!("Dyn syms"); - - for ref sym in self.elf.syms.iter() { - let name = self - .elf - .strtab - .get(sym.st_name) - .unwrap_or(Ok("(no name)")) - .expect("strtab entry"); - - //println!("sym: name={} {:?}", name, sym); - match name { - "lucet_trap_manifest" => self.symbols.lucet_trap_manifest = Some(sym.clone()), - "lucet_trap_manifest_len" => { - self.symbols.lucet_trap_manifest_len = Some(sym.clone()) - } - "wasm_data_segments" => self.symbols.wasm_data_segments = Some(sym.clone()), - "wasm_data_segments_len" => self.symbols.wasm_data_segments_len = Some(sym.clone()), - "lucet_heap_spec" => self.symbols.lucet_heap_spec = Some(sym.clone()), - "lucet_globals_spec" => self.symbols.lucet_globals_spec = Some(sym.clone()), - "guest_sparse_page_data" => self.symbols.guest_sparse_page_data = Some(sym.clone()), - _ => { - if sym.st_bind() == elf::sym::STB_GLOBAL { - if sym.is_function() { - self.exported_functions.push(name.clone()); - } else if sym.st_shndx == elf::section_header::SHN_UNDEF as usize { - self.imported_symbols.push(name.clone()); - } - } - } - } - } - - self.heap_spec = self.parse_heap_spec(); - self.globals_spec = self.parse_globals_spec(); - self.data_segments = self.parse_data_segments(); - self.trap_manifest = self.parse_trap_manifest(); - self.sparse_page_data = self.parse_sparse_page_data(); - } - - fn parse_heap_spec(&self) -> Option { - if let Some(ref sym) = self.symbols.lucet_heap_spec { - let mut spec = HeapSpec { - reserved_size: 0, - guard_size: 0, - initial_size: 0, - max_size: None, - }; - - let serialized = self.read_memory(sym.st_value, sym.st_size).unwrap(); - let mut rdr = Cursor::new(serialized); - spec.reserved_size = rdr.read_u64::().unwrap(); - spec.guard_size = rdr.read_u64::().unwrap(); - spec.initial_size = rdr.read_u64::().unwrap(); - - let max_size = rdr.read_u64::().unwrap(); - let max_size_valid = rdr.read_u64::().unwrap(); - - if max_size_valid == 1 { - spec.max_size = Some(max_size); - } else { - spec.max_size = None; - } - - Some(spec) - } else { - None - } - } - - fn parse_globals_spec(&self) -> Option { - if let Some(ref sym) = self.symbols.lucet_globals_spec { - let mut spec = GlobalsSpec { count: 0 }; - - let serialized = self.read_memory(sym.st_value, sym.st_size).unwrap(); - let mut rdr = Cursor::new(serialized); - spec.count = rdr.read_u64::().unwrap(); - - Some(spec) - } else { - None - } - } - - fn parse_data_segments(&self) -> Option { - if let Some(ref data_sym) = self.symbols.wasm_data_segments { - if let Some(ref data_len_sym) = self.symbols.wasm_data_segments_len { - let mut data_segments = DataSegments { - segments: Vec::new(), - }; - - // TODO: validate that sym.st_size == 4 - let buffer = self - .read_memory(data_len_sym.st_value, data_len_sym.st_size) - .unwrap(); - let mut rdr = Cursor::new(buffer); - let data_len = rdr.read_u32::().unwrap(); - // TODO: validate that data_len == data_sym.st_size - - let buffer = self - .read_memory(data_sym.st_value, data_sym.st_size) - .unwrap(); - let mut rdr = Cursor::new(&buffer); - - while rdr.position() < data_len as u64 { - let _memory_index = rdr.read_u32::(); - // TODO: validate that memory_index == 0 - let offset = rdr.read_u32::().unwrap(); - let len = rdr.read_u32::().unwrap(); - - let pos = rdr.position() as usize; - let data_slice = &buffer[pos..pos + len as usize]; - - let mut data = Vec::new(); - data.extend_from_slice(data_slice); - - let pad = (8 - (pos + len as usize) % 8) % 8; - let new_pos = pos as u64 + len as u64 + pad as u64; - rdr.set_position(new_pos); - - data_segments.segments.push(DataSegment { - offset: offset, - len: len, - data: data, - }); - } - - Some(data_segments) - } else { - None - } - } else { - None - } - } - - fn parse_sparse_page_data(&self) -> Option { - if let Some(ref sparse_sym) = self.symbols.guest_sparse_page_data { - let mut sparse_page_data = SparsePageData { pages: Vec::new() }; - let buffer = self - .read_memory(sparse_sym.st_value, sparse_sym.st_size) - .unwrap(); - let buffer_len = buffer.len(); - let mut rdr = Cursor::new(buffer); - let num_pages = rdr.read_u64::().unwrap(); - if buffer_len != size_of::() + num_pages as usize * size_of::() { - eprintln!("size of sparse page data doesn't match the number of pages specified"); - None - } else { - for _ in 0..num_pages { - let ptr = rdr.read_u64::().unwrap() as *const u8; - sparse_page_data.pages.push(ptr); - } - Some(sparse_page_data) - } - } else { - None - } - } - - fn parse_trap_manifest(&self) -> Option { - let trap_manifest: elf::sym::Sym; - let trap_manifest_len: elf::sym::Sym; - - // Make sure we have the necessary symbols first - if let Some(ref tm) = self.symbols.lucet_trap_manifest { - trap_manifest = tm.clone(); - } else { - return None; - } - - if let Some(ref tml) = self.symbols.lucet_trap_manifest_len { - trap_manifest_len = tml.clone(); - } else { - return None; - } - - let mut manifest = TrapManifest { - records: Vec::new(), - }; - - // Get the length of the manifest - // TODO: return error if st_size != 4 - let serialized = self - .read_memory(trap_manifest_len.st_value, trap_manifest_len.st_size) - .unwrap(); - let mut rdr = Cursor::new(serialized); - let trap_manifest_len = rdr.read_u32::().unwrap(); - - // Find the manifest itself - let serialized = self - .read_memory(trap_manifest.st_value, trap_manifest.st_size) - .unwrap(); - let mut rdr = Cursor::new(serialized); - - // Iterate through each row - for _ in 0..trap_manifest_len { - let func_start = rdr.read_u64::().unwrap(); - let func_len = rdr.read_u64::().unwrap(); - let _traps = rdr.read_u64::().unwrap(); - let traps_len = rdr.read_u64::().unwrap(); - let func_name = self - .get_func_name_for_addr(func_start) - .unwrap_or("(not found)"); - - let sites = Vec::new(); - - // TODO: This doesn't work yet for unknown reasons - // // Find the table - // let serialized_table = self.read_memory(traps, 8 * traps_len).unwrap(); - // let mut table_rdr = Cursor::new(serialized_table); - - // Iterate through each site - // for _ in 0..traps_len { - // let offset = table_rdr.read_u32::().unwrap(); - // let reason = table_rdr.read_u32::().unwrap(); - - // sites.push(TrapSite { - // offset: offset, - // reason: reason, - // }); - // } - - manifest.records.push(TrapManifestRow { - func_name: func_name.to_string(), - func_addr: func_start, - func_len: func_len, - trap_count: traps_len, - sites: sites, - }); - } - - Some(manifest) - } - - fn get_func_name_for_addr(&self, addr: u64) -> Option<&str> { - for ref sym in self.elf.syms.iter() { - if sym.is_function() && sym.st_value == addr { - let name = self - .elf - .strtab - .get(sym.st_name) - .unwrap_or(Ok("(no name)")) - .expect("strtab entry"); - - return Some(name); - } - } - None - } -} - -fn main() { - let path = env::args().nth(1).unwrap(); - let mut fd = File::open(path).expect("open"); - let mut buffer = Vec::new(); - fd.read_to_end(&mut buffer).expect("read"); - let object = Object::parse(&buffer).expect("parse"); - - if let Object::Elf(eo) = object { - let mut summary = ArtifactSummary::new(&buffer, &eo); - summary.gather(); - print_summary(summary); - } else { - println!("Expected Elf!"); - } -} - -fn print_summary(summary: ArtifactSummary) { - println!("Required Symbols:"); - println!( - " {:25}: {}", - "lucet_trap_manifest", - exists_to_str(&summary.symbols.lucet_trap_manifest) - ); - println!( - " {:25}: {}", - "lucet_trap_manifest_len", - exists_to_str(&summary.symbols.lucet_trap_manifest_len) - ); - println!( - " {:25}: {}", - "wasm_data_segments", - exists_to_str(&summary.symbols.wasm_data_segments) - ); - println!( - " {:25}: {}", - "wasm_data_segments_len", - exists_to_str(&summary.symbols.wasm_data_segments_len) - ); - println!( - " {:25}: {}", - "guest_sparse_page_data", - exists_to_str(&summary.symbols.guest_sparse_page_data) - ); - println!( - " {:25}: {}", - "lucet_heap_spec", - exists_to_str(&summary.symbols.lucet_heap_spec) - ); - println!( - " {:25}: {}", - "lucet_globals_spec", - exists_to_str(&summary.symbols.lucet_globals_spec) - ); - - println!(""); - println!("Exported Functions/Symbols:"); - for function_name in summary.exported_functions { - println!(" {}", function_name); - } - - println!(""); - println!("Imported Functions/Symbols:"); - for function_name in summary.imported_symbols { - println!(" {}", function_name); - } - - println!(""); - println!("Heap Specification:"); - if let Some(heap_spec) = summary.heap_spec { - println!(" {:9}: {} bytes", "Reserved", heap_spec.reserved_size); - println!(" {:9}: {} bytes", "Guard", heap_spec.guard_size); - println!(" {:9}: {} bytes", "Initial", heap_spec.initial_size); - if let Some(max_size) = heap_spec.max_size { - println!(" {:9}: {} bytes", "Maximum", max_size); - } else { - println!(" {:9}: None", "Maximum"); - } - } else { - println!(" {}", "MISSING!".red().bold()); - } - - println!(""); - println!("Globals Specification:"); - if let Some(globals_spec) = summary.globals_spec { - println!(" {:6}: {}", "Count", globals_spec.count); - } else { - println!(" {}", "MISSING!".red().bold()); - } - - println!(""); - println!("Data Segments:"); - if let Some(data_segments) = summary.data_segments { - println!(" {:6}: {}", "Count", data_segments.segments.len()); - for segment in &data_segments.segments { - println!( - " {:7}: {:6} {:6}: {:6}", - "Offset", segment.offset, "Length", segment.len, - ); - } - } else { - println!(" {}", "MISSING!".red().bold()); - } - - println!(""); - println!("Sparse page data:"); - if let Some(sparse_page_data) = summary.sparse_page_data { - println!(" {:6}: {}", "Count", sparse_page_data.pages.len()); - let mut allempty = true; - for (i, page) in sparse_page_data.pages.iter().enumerate() { - if !page.is_null() { - allempty = false; - println!(" Page[{}]: {:p}", i, *page); - } - } - if allempty { - println!(" (all pages empty)"); - } else { - println!(" (empty pages omitted)"); - } - } else { - println!(" {}", "MISSING!".red().bold()); - } - - println!(""); - println!("Trap Manifest:"); - if let Some(trap_manifest) = summary.trap_manifest { - for row in trap_manifest.records { - println!(" {:25} {} traps", row.func_name, row.trap_count); - } - } else { - println!(" {}", "MISSING!".red().bold()); - } -} - -fn exists_to_str(p: &Option) -> colored::ColoredString { - return match p { - Some(_) => "exists".green(), - None => "MISSING!".red().bold(), - }; -} diff --git a/lucet-builtins/Makefile b/lucet-builtins/Makefile index 3913d3011..6ddbcfde0 100644 --- a/lucet-builtins/Makefile +++ b/lucet-builtins/Makefile @@ -17,6 +17,8 @@ build/%.o: src/%.c $(CC) $(COMMON_CFLAGS) -c $^ -o $@ build/libbuiltins.so: $(LIBBUILTINS_OBJS) + # -U is not supported by the GNU linker. Retry without this option if the first command fails. + $(CC) -Wl,-U,_lucet_vmctx_current_memory -Wl,-U,_lucet_vmctx_get_heap -shared $^ -o $@ 2> /dev/null || \ $(CC) -shared $^ -o $@ .PHONY: clean diff --git a/lucet-builtins/wasmonkey/.gitignore b/lucet-builtins/wasmonkey/.gitignore deleted file mode 100644 index 53eaa2196..000000000 --- a/lucet-builtins/wasmonkey/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target -**/*.rs.bk diff --git a/lucet-builtins/wasmonkey/.travis.yml b/lucet-builtins/wasmonkey/.travis.yml deleted file mode 100644 index 32905a519..000000000 --- a/lucet-builtins/wasmonkey/.travis.yml +++ /dev/null @@ -1,4 +0,0 @@ -language: rust -rust: - - nightly - - stable diff --git a/lucet-builtins/wasmonkey/Cargo.toml b/lucet-builtins/wasmonkey/Cargo.toml deleted file mode 100644 index 62195fba1..000000000 --- a/lucet-builtins/wasmonkey/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "wasmonkey" -version = "0.1.4" -authors = ["Frank Denis "] -description = "Patch a WASM object file to replace a set of exported functions with imported functions from another library" -license = "ISC" -homepage = "https://github.com/jedisct1/wasmonkey" -repository = "https://github.com/jedisct1/wasmonkey" -categories = ["wasm"] - -[dependencies] -clap = "2.32" -failure = "0.1" -goblin = "~0.0.19" -lazy_static = "1.2" -parity-wasm = "0.35" -serde = "1.0" -serde_derive = "1.0" -serde_json = "1.0" -siphasher = "0.2" -xfailure = "0.1" diff --git a/lucet-builtins/wasmonkey/README.md b/lucet-builtins/wasmonkey/README.md deleted file mode 100644 index 00887e644..000000000 --- a/lucet-builtins/wasmonkey/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# WASMonkey - -The WASMonkey patches a WASM object file to replace a set of exported -functions with imported functions from another library. - -## Usage - -```text -USAGE: - wasmonkey [FLAGS] [OPTIONS] --input --output - -FLAGS: - -n, --original-names Use the original name as a key in the builtins map - -h, --help Prints help information - -V, --version Prints version information - -OPTIONS: - -B, --builtins-additional ... Additional builtins function names to replace - -b, --builtins Path to the builtins library - -m, --builtins-map Path to the builtins map file - -i, --input Path to the input file - -o, --output Path to the output file -``` - -`builtins_file` is an object containing alternative implementations to -funcitons called in the wasm code. - -Symbols starting with a `builtin_` prefix will be used for substitution. - -For example, a `memmove()` function defined as an external in the WASM -object will be replaced by calls to an imported `builtin_memmove()` -function, if `builtin_memmove()` is present in the builtins file. - -A JSON-encoded map of the performed substitutions can be optionally written -into `builtins_map_file`. diff --git a/lucet-builtins/wasmonkey/src/bin/config/mod.rs b/lucet-builtins/wasmonkey/src/bin/config/mod.rs deleted file mode 100644 index f3cf30bc3..000000000 --- a/lucet-builtins/wasmonkey/src/bin/config/mod.rs +++ /dev/null @@ -1,93 +0,0 @@ -use clap::{App, Arg}; -use std::path::PathBuf; -use {PatcherConfig, WError}; - -#[derive(Default, Clone, Debug)] -pub struct Config { - pub input_path: PathBuf, - pub output_path: PathBuf, - pub patcher_config: PatcherConfig, -} - -impl Config { - pub fn parse_cmdline() -> Result { - let matches = App::new("wasmonkey") - .version("1.0") - .about("Transforms WASM exports to imports") - .arg( - Arg::with_name("input_file") - .short("i") - .long("input") - .takes_value(true) - .required(true) - .help("Path to the input file"), - ) - .arg( - Arg::with_name("output_file") - .short("o") - .long("output") - .takes_value(true) - .required(true) - .help("Path to the output file"), - ) - .arg( - Arg::with_name("builtins_file") - .short("b") - .long("builtins") - .takes_value(true) - .required(false) - .help("Path to the builtins library"), - ) - .arg( - Arg::with_name("builtins_additional") - .short("B") - .long("builtins-additional") - .takes_value(true) - .required(false) - .multiple(true) - .help("Additional builtins function names to replace"), - ) - .arg( - Arg::with_name("builtins_map_file") - .short("m") - .long("builtins-map") - .takes_value(true) - .required(false) - .help("Path to the builtins map file"), - ) - .arg( - Arg::with_name("builtins_map_original_names") - .short("n") - .long("original-names") - .takes_value(false) - .required(false) - .help("Use the original name as a key in the builtins map"), - ) - .get_matches(); - let input_path = PathBuf::from(matches - .value_of("input_file") - .ok_or(WError::UsageError("Input file required"))?); - let output_path = PathBuf::from(matches - .value_of("output_file") - .ok_or(WError::UsageError("Output file required"))?); - let builtins_path = matches.value_of("builtins_file").map(PathBuf::from); - let builtins_map_path = matches.value_of("builtins_map_file").map(PathBuf::from); - let builtins_map_original_names = matches.is_present("builtins_map_original_names"); - let builtins_additional = matches - .values_of("builtins_additional") - .unwrap_or_default() - .map(|name| name.to_string()) - .collect(); - let config = Config { - input_path, - output_path, - patcher_config: PatcherConfig { - builtins_path, - builtins_map_path, - builtins_map_original_names, - builtins_additional, - }, - }; - Ok(config) - } -} diff --git a/lucet-builtins/wasmonkey/src/bin/wasmonkey.rs b/lucet-builtins/wasmonkey/src/bin/wasmonkey.rs deleted file mode 100644 index 1ee180b32..000000000 --- a/lucet-builtins/wasmonkey/src/bin/wasmonkey.rs +++ /dev/null @@ -1,15 +0,0 @@ -extern crate clap; -extern crate failure; -extern crate wasmonkey; - -mod config; - -use config::*; -use wasmonkey::*; - -fn main() -> Result<(), WError> { - let config = Config::parse_cmdline()?; - let patcher = Patcher::from_file(config.patcher_config, config.input_path)?; - patcher.store_to_file(config.output_path)?; - Ok(()) -} diff --git a/lucet-builtins/wasmonkey/src/errors.rs b/lucet-builtins/wasmonkey/src/errors.rs deleted file mode 100644 index 70e0998f8..000000000 --- a/lucet-builtins/wasmonkey/src/errors.rs +++ /dev/null @@ -1,31 +0,0 @@ -use parity_wasm::elements; -use std::io; - -#[allow(dead_code)] -#[derive(Debug, Fail)] -pub enum WError { - #[fail(display = "Internal error: {}", _0)] - InternalError(&'static str), - #[fail(display = "Incorrect usage: {}", _0)] - UsageError(&'static str), - #[fail(display = "{}", _0)] - Io(#[cause] io::Error), - #[fail(display = "{}", _0)] - WAsmError(#[cause] elements::Error), - #[fail(display = "Parse error")] - ParseError, - #[fail(display = "Unsupported")] - Unsupported, -} - -impl From for WError { - fn from(e: io::Error) -> WError { - WError::Io(e) - } -} - -impl From for WError { - fn from(e: elements::Error) -> WError { - WError::WAsmError(e) - } -} diff --git a/lucet-builtins/wasmonkey/src/functions_ids.rs b/lucet-builtins/wasmonkey/src/functions_ids.rs deleted file mode 100644 index c8db939fc..000000000 --- a/lucet-builtins/wasmonkey/src/functions_ids.rs +++ /dev/null @@ -1,104 +0,0 @@ -use errors::*; -use parity_wasm::elements::{ - CodeSection, ElementSection, ExportSection, FuncBody, Instruction, Instructions, Internal, - Module, -}; - -fn shift_function_ids_in_code_section( - code_section: &mut CodeSection, - shift: u32, -) -> Result<(), WError> { - let code_bodies = code_section.bodies_mut(); - for code_body in code_bodies.iter_mut() { - let opcodes = code_body.code_mut().elements_mut(); - for opcode in opcodes.iter_mut() { - if let Instruction::Call(ref mut function_id) = opcode { - *function_id = *function_id + shift - } - } - } - Ok(()) -} - -fn shift_function_ids_in_exports_section(export_section: &mut ExportSection, shift: u32) { - for entry in export_section.entries_mut() { - let internal = entry.internal_mut(); - if let Internal::Function(ref mut function_id) = internal { - *function_id = *function_id + shift - } - } -} - -fn shift_function_ids_in_elements_section(elements_section: &mut ElementSection, shift: u32) { - for elements_segment in elements_section.entries_mut() { - for function_id in elements_segment.members_mut() { - *function_id += shift; - } - } -} - -pub fn shift_function_ids(module: &mut Module, shift: u32) -> Result<(), WError> { - shift_function_ids_in_code_section(module.code_section_mut().expect("No code section"), shift)?; - if let Some(export_section) = module.export_section_mut() { - shift_function_ids_in_exports_section(export_section, shift) - } - if let Some(elements_section) = module.elements_section_mut() { - shift_function_ids_in_elements_section(elements_section, shift) - } - Ok(()) -} - -fn replace_function_id_in_code_section(code_section: &mut CodeSection, before: u32, after: u32) { - let code_bodies = code_section.bodies_mut(); - for code_body in code_bodies.iter_mut() { - let opcodes = code_body.code_mut().elements_mut(); - for opcode in opcodes.iter_mut() { - match opcode { - Instruction::Call(ref mut function_id) if *function_id == before => { - *function_id = after - } - _ => {} - } - } - } -} - -fn replace_function_id_in_elements_section( - elements_section: &mut ElementSection, - before: u32, - after: u32, -) { - for elements_segment in elements_section.entries_mut() { - for function_id in elements_segment.members_mut() { - if *function_id == before { - *function_id = after; - } - } - } -} - -pub fn replace_function_id(module: &mut Module, before: u32, after: u32) -> Result<(), WError> { - if let Some(code_section) = module.code_section_mut() { - replace_function_id_in_code_section(code_section, before, after) - } - - if let Some(elements_section) = module.elements_section_mut() { - replace_function_id_in_elements_section(elements_section, before, after) - }; - - Ok(()) -} - -#[allow(dead_code)] -pub fn disable_function_id(module: &mut Module, function_id: u32) -> Result<(), WError> { - let base_id = match module.import_section() { - None => 0, - Some(import_section) => import_section.entries().len() as u32, - }; - let code_section = module.code_section_mut().expect("No code section"); - let code_bodies = code_section.bodies_mut(); - let opcodes = Instructions::new(vec![Instruction::Unreachable, Instruction::End]); - let func_body = FuncBody::new(vec![], opcodes); - code_bodies[(function_id - base_id) as usize] = func_body; - Ok(()) -} diff --git a/lucet-builtins/wasmonkey/src/functions_names.rs b/lucet-builtins/wasmonkey/src/functions_names.rs deleted file mode 100644 index 9c69d5e41..000000000 --- a/lucet-builtins/wasmonkey/src/functions_names.rs +++ /dev/null @@ -1,15 +0,0 @@ -use errors::*; -use parity_wasm::elements::{FunctionNameSection, IndexMap}; - -pub fn prepend_function_name( - function_names_section: &mut FunctionNameSection, - name: String, -) -> Result<(), WError> { - let mut map_new = IndexMap::with_capacity(function_names_section.names().len() + 1 as usize); - for (idx, name) in function_names_section.names() { - map_new.insert(idx + 1, name.clone()); - } - map_new.insert(0, name); - *function_names_section.names_mut() = map_new; - Ok(()) -} diff --git a/lucet-builtins/wasmonkey/src/lib.rs b/lucet-builtins/wasmonkey/src/lib.rs deleted file mode 100644 index a00d00961..000000000 --- a/lucet-builtins/wasmonkey/src/lib.rs +++ /dev/null @@ -1,28 +0,0 @@ -extern crate clap; -#[macro_use] -extern crate failure; -extern crate goblin; -#[cfg_attr(test, macro_use)] -extern crate lazy_static; -extern crate parity_wasm; -#[macro_use] -extern crate serde_derive; -extern crate serde_json; -#[cfg(test)] -extern crate siphasher; -#[macro_use] -extern crate xfailure; - -mod errors; -mod functions_ids; -mod functions_names; -mod map; -mod patcher; -mod sections; -mod symbols; - -#[cfg(test)] -mod tests; - -pub use errors::*; -pub use patcher::*; diff --git a/lucet-builtins/wasmonkey/src/map.rs b/lucet-builtins/wasmonkey/src/map.rs deleted file mode 100644 index 81d60c07c..000000000 --- a/lucet-builtins/wasmonkey/src/map.rs +++ /dev/null @@ -1,63 +0,0 @@ -use errors::*; -use serde_json; -use std::collections::HashMap; -use std::fs::File; -use std::io::prelude::*; -use std::path::Path; - -#[derive(Clone, Debug, Default, Serialize)] -pub struct PatchedBuiltinsMap { - pub env: HashMap, -} - -impl PatchedBuiltinsMap { - pub fn with_capacity(capacity: usize) -> Self { - PatchedBuiltinsMap { - env: HashMap::with_capacity(capacity), - } - } - - pub fn insert(&mut self, name: String, imported_name: String) -> Option { - self.env.insert(name, imported_name) - } - - pub fn write_to_file>( - &self, - builtins_map_path: P, - original_names: bool, - ) -> Result<(), WError> { - let mut map_with_original_names; - let map = if original_names { - self - } else { - map_with_original_names = PatchedBuiltinsMap::default(); - for imported_name in self.env.values() { - map_with_original_names - .env - .insert(imported_name.clone(), imported_name.clone()); - } - &map_with_original_names - }; - let json = serde_json::to_string_pretty(map).map_err(|_| WError::ParseError)?; - File::create(builtins_map_path)?.write_all(json.as_bytes())?; - Ok(()) - } - - pub fn builtins_map( - &self, - module: &str, - original_names: bool, - ) -> Result, WError> { - if module != "env" { - xbail!(WError::UsageError("Empty module")) - } - if original_names { - return Ok(self.env.clone()); - } - let mut env_map_with_original_names = HashMap::new(); - for imported_name in self.env.values() { - env_map_with_original_names.insert(imported_name.clone(), imported_name.clone()); - } - Ok(env_map_with_original_names) - } -} diff --git a/lucet-builtins/wasmonkey/src/patcher.rs b/lucet-builtins/wasmonkey/src/patcher.rs deleted file mode 100644 index bb39a0a79..000000000 --- a/lucet-builtins/wasmonkey/src/patcher.rs +++ /dev/null @@ -1,217 +0,0 @@ -use errors::*; -use functions_ids::*; -use functions_names::*; -use map::*; -use parity_wasm; -use parity_wasm::elements::{ - self, External, ImportEntry, ImportSection, Internal, Module, NameSection, Section, -}; -use sections::*; -use std::collections::HashMap; -use std::path::{Path, PathBuf}; -use symbols::{self, ExtractedSymbols}; - -pub const BUILTIN_PREFIX: &str = "builtin_"; - -#[derive(Default, Clone, Debug)] -pub struct PatcherConfig { - pub builtins_path: Option, - pub builtins_map_path: Option, - pub builtins_map_original_names: bool, - pub builtins_additional: Vec, -} - -pub struct Patcher { - pub config: PatcherConfig, - patched_module: Module, - patched_builtins_map: PatchedBuiltinsMap, -} - -impl Patcher { - pub fn new(config: PatcherConfig, module: Module) -> Result { - let symbols = match &config.builtins_path { - None => ExtractedSymbols::from(vec![]), - Some(builtins_path) => symbols::extract_symbols(&builtins_path)?, - }.merge_additional(&config.builtins_additional); - let builtins_names = symbols.builtins_names(); - let (patched_module, patched_builtins_map) = patch_module(module, &builtins_names)?; - let patcher = Patcher { - config, - patched_module, - patched_builtins_map, - }; - Ok(patcher) - } - - pub fn from_bytes(config: PatcherConfig, bytes: &[u8]) -> Result { - let module = parity_wasm::deserialize_buffer(bytes)?; - Self::new(config, module) - } - - pub fn from_file>(config: PatcherConfig, path_in: P) -> Result { - let module = parity_wasm::deserialize_file(path_in)?; - Self::new(config, module) - } - - pub fn into_bytes(self) -> Result, WError> { - let bytes = elements::serialize(self.patched_module)?; - Ok(bytes) - } - - pub fn store_to_file>(self, path_out: P) -> Result<(), WError> { - elements::serialize_to_file(path_out, self.patched_module)?; - if let Some(builtins_map_path) = self.config.builtins_map_path { - self.patched_builtins_map - .write_to_file(builtins_map_path, self.config.builtins_map_original_names)?; - } - Ok(()) - } - - pub fn patched_builtins_map(&self, module: &str) -> Result, WError> { - self.patched_builtins_map - .builtins_map(module, self.config.builtins_map_original_names) - } - - pub fn patched_module(self) -> Module { - self.patched_module - } -} - -#[derive(Debug)] -pub struct Builtin { - pub name: String, - pub original_function_id: Option, - pub function_type_id: Option, -} - -impl Builtin { - pub fn new(name: String) -> Self { - Builtin { - name, - original_function_id: None, - function_type_id: None, - } - } - - pub fn import_name(&self) -> String { - format!("{}{}", BUILTIN_PREFIX, self.name) - } -} - -fn function_type_id_for_function_id(module: &Module, function_id: u32) -> Option { - let offset = module - .import_section() - .map(|import_section| import_section.entries().len() as u32) - .unwrap_or(0); - if function_id < offset { - return None; - } - let functions_section_type_ids = module.function_section().unwrap().entries(); - Some(functions_section_type_ids[(function_id - offset) as usize].type_ref()) -} - -fn add_function_type_id_to_builtins( - module: &Module, - builtins: &mut Vec, -) -> Result<(), WError> { - for builtin in builtins.iter_mut() { - let function_type_id = - function_type_id_for_function_id(module, builtin.original_function_id.unwrap()) - .expect("Function ID not found"); - builtin.function_type_id = Some(function_type_id); - } - Ok(()) -} - -fn retain_only_used_builtins(module: &Module, builtins: &mut Vec) -> Result<(), WError> { - let export_section = module.export_section().expect("No export section"); - - for entry in export_section.entries() { - let internal = entry.internal(); - let function_id = match internal { - Internal::Function(function_id) => *function_id, - _ => continue, - }; - let field = entry.field(); - for builtin in builtins.iter_mut() { - if field == builtin.name { - assert!(builtin.original_function_id.is_none()); - builtin.original_function_id = Some(function_id); - break; - } - } - } - - builtins.retain(|builtin| builtin.original_function_id.is_some()); - Ok(()) -} - -fn add_import_section_if_missing(module: &mut Module) -> Result<(), WError> { - if module.import_section().is_some() { - return Ok(()); - } - let import_section = ImportSection::with_entries(vec![]); - let import_section_idx = find_type_section_idx(&module).unwrap() + 1; - module - .sections_mut() - .insert(import_section_idx, Section::Import(import_section)); - Ok(()) -} - -fn prepend_builtin_to_import_section(module: &mut Module, builtin: &Builtin) -> Result<(), WError> { - let import_name = builtin.import_name(); - let external = External::Function(builtin.function_type_id.unwrap()); - let import_entry = ImportEntry::new("env".to_string(), import_name, external); - module - .import_section_mut() - .unwrap() - .entries_mut() - .insert(0, import_entry); - Ok(()) -} - -fn prepend_builtin_to_names_section(module: &mut Module, builtin: &Builtin) -> Result<(), WError> { - let import_name = builtin.import_name(); - let names_section = module - .names_section_mut() - .expect("Names section not present"); - let function_names_section = match names_section { - NameSection::Function(function_names_section) => function_names_section, - _ => xbail!(WError::InternalError("Unexpected names section")), - }; - prepend_function_name(function_names_section, import_name)?; - Ok(()) -} - -fn patch_module( - module: Module, - builtins_names: &[&str], -) -> Result<(Module, PatchedBuiltinsMap), WError> { - let mut module = module - .parse_names() - .map_err(|_| WError::InternalError("Unable to parse names"))?; - - let mut builtins: Vec<_> = builtins_names - .iter() - .map(|x| Builtin::new(x.to_string())) - .collect(); - - retain_only_used_builtins(&module, &mut builtins)?; - add_function_type_id_to_builtins(&module, &mut builtins)?; - - add_import_section_if_missing(&mut module)?; - for (builtin_idx, builtin) in builtins.iter_mut().enumerate() { - prepend_builtin_to_import_section(&mut module, &builtin)?; - prepend_builtin_to_names_section(&mut module, &builtin)?; - shift_function_ids(&mut module, 1)?; - let original_function_id = builtin.original_function_id.unwrap() + builtin_idx as u32 + 1; - let new_function_id = 0; - replace_function_id(&mut module, original_function_id, new_function_id)?; - } - - let mut patched_builtins_map = PatchedBuiltinsMap::with_capacity(builtins.len()); - for builtin in builtins { - patched_builtins_map.insert(builtin.name.clone(), builtin.import_name()); - } - Ok((module, patched_builtins_map)) -} diff --git a/lucet-builtins/wasmonkey/src/sections.rs b/lucet-builtins/wasmonkey/src/sections.rs deleted file mode 100644 index 8d984463c..000000000 --- a/lucet-builtins/wasmonkey/src/sections.rs +++ /dev/null @@ -1,8 +0,0 @@ -use parity_wasm::elements::{Module, Section}; - -pub fn find_type_section_idx(module: &Module) -> Option { - module.sections().iter().position(|section| match section { - Section::Type(_) => true, - _ => false, - }) -} diff --git a/lucet-builtins/wasmonkey/src/symbols.rs b/lucet-builtins/wasmonkey/src/symbols.rs deleted file mode 100644 index 5bbd46c58..000000000 --- a/lucet-builtins/wasmonkey/src/symbols.rs +++ /dev/null @@ -1,131 +0,0 @@ -use errors::*; -use goblin::elf::Elf; -use goblin::mach::{self, Mach, MachO}; -use goblin::Object; -use patcher::BUILTIN_PREFIX; -use std::fs::File; -use std::io::Read; -use std::path::Path; - -#[derive(Clone, Debug, Default)] -pub struct ExtractedSymbol { - pub name: String, -} - -#[derive(Clone, Debug, Default)] -pub struct ExtractedSymbols { - pub symbols: Vec, -} - -impl From> for ExtractedSymbols { - fn from(symbols: Vec) -> Self { - ExtractedSymbols { symbols } - } -} - -impl ExtractedSymbols { - pub fn builtins_names(&self) -> Vec<&str> { - let builtins_names: Vec<&str> = self.symbols - .iter() - .filter(|symbol| symbol.name.starts_with(BUILTIN_PREFIX)) - .map(|symbol| &symbol.name[BUILTIN_PREFIX.len()..]) - .collect(); - builtins_names - } - - pub fn merge_additional(mut self, additional_names: &[String]) -> Self { - let mut additional_symbols: Vec<_> = additional_names - .into_iter() - .map(|name| ExtractedSymbol { - name: name.to_string(), - }) - .collect(); - self.symbols.append(&mut additional_symbols); - self.symbols.dedup_by(|a, b| a.name == b.name); - self - } -} - -fn parse_elf(elf: &Elf) -> Result { - let mut symbols = vec![]; - - for symbol in elf.dynsyms - .iter() - .filter(|symbol| symbol.st_info == 0x12 || symbol.st_info == 0x22) - { - let name = elf.dynstrtab - .get(symbol.st_name) - .ok_or(WError::ParseError)? - .map_err(|_| WError::ParseError)? - .to_string(); - let extracted_symbol = ExtractedSymbol { name }; - symbols.push(extracted_symbol); - } - Ok(symbols.into()) -} - -fn parse_macho(macho: &MachO) -> Result { - let mut symbols = vec![]; - - // Start by finding the boundaries of the text section - let mut text_offset = None; - let mut text_size = None; - for section in macho.segments.sections() { - for segment in section { - if let Ok(( - mach::segment::Section { - sectname: [b'_', b'_', b't', b'e', b'x', b't', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - segname: [b'_', b'_', b'T', b'E', b'X', b'T', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - size, - offset, - .. - }, - _, - )) = segment - { - text_offset = Some(offset as usize); - text_size = Some(size as usize); - } - } - } - let text_offset = text_offset.ok_or(WError::ParseError)?; - let text_size = text_size.ok_or(WError::ParseError)?; - - // Extract the symbols we are interested in - for symbol in macho.symbols.as_ref().ok_or(WError::ParseError)?.iter() { - match symbol { - Ok(( - name, - mach::symbols::Nlist { - n_type: 0xf, - n_sect: 1, - n_value, - .. - }, - )) if name.len() > 1 && name.starts_with('_') => - { - let extracted_symbol = ExtractedSymbol { - name: name[1..].to_string(), - }; - let offset = n_value as usize; - if offset < text_offset || offset >= text_offset + text_size { - continue; - } - symbols.push(extracted_symbol); - } - _ => {} - } - } - Ok(symbols.into()) -} - -pub fn extract_symbols>(path: P) -> Result { - let mut buffer = Vec::new(); - File::open(path)?.read_to_end(&mut buffer)?; - let symbols = match Object::parse(&buffer).map_err(|_| WError::ParseError)? { - Object::Mach(Mach::Binary(macho)) => parse_macho(&macho), - Object::Elf(elf) => parse_elf(&elf), - _ => xbail!(WError::Unsupported), - }?; - Ok(symbols) -} diff --git a/lucet-builtins/wasmonkey/src/tests/mod.rs b/lucet-builtins/wasmonkey/src/tests/mod.rs deleted file mode 100644 index 63ed2bae8..000000000 --- a/lucet-builtins/wasmonkey/src/tests/mod.rs +++ /dev/null @@ -1,46 +0,0 @@ -use super::*; -use siphasher::sip::SipHasher13; -use std::hash::Hasher; -use std::path::{Path, PathBuf}; - -lazy_static! { - static ref TESTS_DIR: PathBuf = Path::new(file!()).parent().unwrap().canonicalize().unwrap(); -} - -#[test] -fn patch_nothing() { - let path_in = TESTS_DIR.join("test_1.wasm"); - let config = PatcherConfig::default(); - let patcher = Patcher::from_file(config, path_in).unwrap(); - let mut hasher = SipHasher13::new(); - hasher.write(&patcher.into_bytes().unwrap()); - assert_eq!(hasher.finish(), 1401932366200566186); -} - -#[test] -fn patch_one() { - let path_in = TESTS_DIR.join("test_1.wasm"); - let mut config = PatcherConfig::default(); - config.builtins_additional = ["builtin_memmove", "builtin_nonexistent", "not_a_builtin"] - .into_iter() - .map(|s| s.to_string()) - .collect(); - let patcher = Patcher::from_file(config, path_in).unwrap(); - let mut hasher = SipHasher13::new(); - hasher.write(&patcher.into_bytes().unwrap()); - assert_eq!(hasher.finish(), 12884721342785729260); -} - -#[test] -fn patch_some() { - let path_in = TESTS_DIR.join("test_1.wasm"); - let mut config = PatcherConfig::default(); - config.builtins_additional = ["builtin_memmove", "builtin_memcpy", "builtin_strcmp"] - .into_iter() - .map(|s| s.to_string()) - .collect(); - let patcher = Patcher::from_file(config, path_in).unwrap(); - let mut hasher = SipHasher13::new(); - hasher.write(&patcher.into_bytes().unwrap()); - assert_eq!(hasher.finish(), 13205801729184435761); -} diff --git a/lucet-builtins/wasmonkey/src/tests/test_1.wasm b/lucet-builtins/wasmonkey/src/tests/test_1.wasm deleted file mode 100644 index 1bac61b0e..000000000 Binary files a/lucet-builtins/wasmonkey/src/tests/test_1.wasm and /dev/null differ diff --git a/lucet-idl/.gitignore b/lucet-idl/.gitignore deleted file mode 100644 index eb5a316cb..000000000 --- a/lucet-idl/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target diff --git a/lucet-idl/Cargo.toml b/lucet-idl/Cargo.toml deleted file mode 100644 index 270fe204b..000000000 --- a/lucet-idl/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "lucet-idl" -version = "0.1.0" -description = "Describe interfaces between WebAssembly guest programs and lucet-runtime hosts" -authors = ["Pat Hickey ", "Frank Denis "] -repository = "https://github.com/fastly/lucet" -license = "Apache-2.0 WITH LLVM-exception" -edition = "2018" - -[lib] -crate-type=["rlib"] - -[dependencies] diff --git a/lucet-idl/src/lexer.rs b/lucet-idl/src/lexer.rs deleted file mode 100644 index a2f7e9199..000000000 --- a/lucet-idl/src/lexer.rs +++ /dev/null @@ -1,367 +0,0 @@ -use super::types::{AtomType, Location}; -use std::str::CharIndices; - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum Token<'a> { - LPar, // ( - RPar, // ) - LBrace, // { - RBrace, // } - LBracket, // [ - RBracket, // ] - Star, // * - Colon, // : - Semi, // ; - Comma, // , - Hash, // # - Equals, // = - Keyword(Keyword), - Atom(AtomType), - Word(&'a str), - Quote(&'a str), // Found between balanced "". No escaping. -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum Keyword { - Struct, // 'struct' - TaggedUnion, // 'taggedunion' - Enum, // 'enum' - Type, // 'type' -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub struct LocatedToken<'a> { - pub token: Token<'a>, - pub location: Location, -} - -fn token(token: Token, location: Location) -> Result { - Ok(LocatedToken { token, location }) -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum LexError { - InvalidChar(char), - UnterminatedComment, - UnterminatedQuote, -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub struct LocatedError { - pub error: LexError, - pub location: Location, -} - -fn error<'a>(error: LexError, location: Location) -> Result, LocatedError> { - Err(LocatedError { error, location }) -} - -pub struct Lexer<'a> { - source: &'a str, - chars: CharIndices<'a>, - lookahead: Option, - pos: usize, - line_number: usize, - column_start: usize, - tab_compensation: usize, -} - -impl<'a> Lexer<'a> { - pub fn new(s: &'a str) -> Lexer { - let mut lex = Lexer { - source: s, - chars: s.char_indices(), - lookahead: None, - pos: 0, - line_number: 1, - column_start: 0, - tab_compensation: 0, - }; - lex.next_ch(); - lex - } - - fn next_ch(&mut self) -> Option { - if self.lookahead == Some('\n') { - self.line_number += 1; - self.column_start = self.pos + 1; // Next column starts a fresh line - self.tab_compensation = 0; - } else if self.lookahead == Some('\t') { - self.tab_compensation += 7; // One column for the position of the char itself, add 7 more for a tabwidth of 8 - } - match self.chars.next() { - Some((idx, ch)) => { - self.pos = idx; - self.lookahead = Some(ch); - } - None => { - self.pos = self.source.len(); - self.lookahead = None; - } - } - self.lookahead - } - - fn loc(&self) -> Location { - Location { - line: self.line_number, - column: self.pos - self.column_start + self.tab_compensation, - } - } - - fn looking_at(&self, prefix: &str) -> bool { - self.source[self.pos..].starts_with(prefix) - } - - fn scan_char(&mut self, tok: Token<'a>) -> Result, LocatedError> { - assert!(self.lookahead.is_some()); - let loc = self.loc(); - self.next_ch(); - token(tok, loc) - } - - pub fn rest_of_line(&mut self) -> &'a str { - let begin = self.pos; - loop { - match self.next_ch() { - None | Some('\n') => return &self.source[begin..self.pos], - _ => {} - } - } - } - - fn scan_word(&mut self) -> Result, LocatedError> { - let begin = self.pos; - let loc = self.loc(); - assert!(self.lookahead == Some('_') || self.lookahead.unwrap().is_alphabetic()); - loop { - match self.next_ch() { - Some('_') => {} - Some(ch) if ch.is_alphanumeric() => {} - _ => break, - } - } - let text = &self.source[begin..self.pos]; - token( - match text { - "struct" => Token::Keyword(Keyword::Struct), - "taggedunion" => Token::Keyword(Keyword::TaggedUnion), - "enum" => Token::Keyword(Keyword::Enum), - "type" => Token::Keyword(Keyword::Type), - "i8" => Token::Atom(AtomType::I8), - "i16" => Token::Atom(AtomType::I16), - "i32" => Token::Atom(AtomType::I32), - "i64" => Token::Atom(AtomType::I64), - "u8" => Token::Atom(AtomType::U8), - "u16" => Token::Atom(AtomType::U16), - "u32" => Token::Atom(AtomType::U32), - "u64" => Token::Atom(AtomType::U64), - "f32" => Token::Atom(AtomType::F32), - "f64" => Token::Atom(AtomType::F64), - _ => Token::Word(text), - }, - loc, - ) - } - - fn scan_comment(&mut self) -> Result<(), LocatedError> { - assert!(self.lookahead == Some('/')); - let loc = self.loc(); - loop { - match self.next_ch() { - None => Err(LocatedError { - error: LexError::UnterminatedComment, - location: loc, - })?, - Some('*') => { - if self.looking_at("*/") { - self.next_ch(); // Consume the slash - self.next_ch(); // Move to next token for outer loop - break; - } - } - Some(_) => {} - } - } - Ok(()) - } - - fn scan_quote(&mut self) -> Result, LocatedError> { - let begin = self.pos; - let loc = self.loc(); - assert!(self.lookahead == Some('"')); - loop { - match self.next_ch() { - None => Err(LocatedError { - error: LexError::UnterminatedQuote, - location: loc, - })?, - Some('"') => { - self.next_ch(); - break; - } - _ => {} - } - } - let text = &self.source[(begin + 1)..(self.pos - 1)]; - token(Token::Quote(text), loc) - } - - pub fn next(&mut self) -> Option, LocatedError>> { - loop { - let loc = self.loc(); - return match self.lookahead { - None => None, - Some(c) => Some(match c { - '(' => self.scan_char(Token::LPar), - ')' => self.scan_char(Token::RPar), - '{' => self.scan_char(Token::LBrace), - '}' => self.scan_char(Token::RBrace), - '[' => self.scan_char(Token::LBracket), - ']' => self.scan_char(Token::RBracket), - '*' => self.scan_char(Token::Star), - ':' => self.scan_char(Token::Colon), - ';' => self.scan_char(Token::Semi), - ',' => self.scan_char(Token::Comma), - '#' => self.scan_char(Token::Hash), - '=' => self.scan_char(Token::Equals), - '/' => { - if self.looking_at("//") { - self.rest_of_line(); - continue; - } else if self.looking_at("/*") { - match self.scan_comment() { - Ok(()) => continue, - Err(e) => return Some(Err(e)), - } - } else { - self.next_ch(); - error(LexError::InvalidChar('/'), loc) - } - } - '"' => self.scan_quote(), - ch if ch.is_alphabetic() => self.scan_word(), - ch if ch.is_whitespace() => { - self.next_ch(); - continue; - } - _ => { - self.next_ch(); - error(LexError::InvalidChar(c), loc) - } - }), - }; - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn token( - token: Token, - line: usize, - column: usize, - ) -> Option> { - Some(super::token(token, Location { line, column })) - } - - fn error<'a>( - err: LexError, - line: usize, - column: usize, - ) -> Option, LocatedError>> { - Some(super::error(err, Location { line, column })) - } - - #[test] - fn atoms() { - let mut lex = Lexer::new("i8 i16 i32 i64\nu8 u16 u32 u64\nf32 f64"); - assert_eq!(lex.next(), token(Token::Atom(AtomType::I8), 1, 0)); - assert_eq!(lex.next(), token(Token::Atom(AtomType::I16), 1, 3)); - assert_eq!(lex.next(), token(Token::Atom(AtomType::I32), 1, 7)); - assert_eq!(lex.next(), token(Token::Atom(AtomType::I64), 1, 11)); - assert_eq!(lex.next(), token(Token::Atom(AtomType::U8), 2, 0)); - assert_eq!(lex.next(), token(Token::Atom(AtomType::U16), 2, 3)); - assert_eq!(lex.next(), token(Token::Atom(AtomType::U32), 2, 7)); - assert_eq!(lex.next(), token(Token::Atom(AtomType::U64), 2, 11)); - assert_eq!(lex.next(), token(Token::Atom(AtomType::F32), 3, 0)); - assert_eq!(lex.next(), token(Token::Atom(AtomType::F64), 3, 4)); - assert_eq!(lex.next(), None); - } - - #[test] - fn keywords() { - let mut lex = Lexer::new("struct\ntaggedunion\nenum type"); - assert_eq!(lex.next(), token(Token::Keyword(Keyword::Struct), 1, 0)); - assert_eq!( - lex.next(), - token(Token::Keyword(Keyword::TaggedUnion), 2, 0) - ); - assert_eq!(lex.next(), token(Token::Keyword(Keyword::Enum), 3, 0)); - assert_eq!(lex.next(), token(Token::Keyword(Keyword::Type), 3, 5)); - assert_eq!(lex.next(), None); - } - - #[test] - fn comments() { - let mut lex = Lexer::new("the quick // brown fox\njumped\n//over the three\nlazy//dogs"); - assert_eq!(lex.next(), token(Token::Word("the"), 1, 0)); - assert_eq!(lex.next(), token(Token::Word("quick"), 1, 4)); - assert_eq!(lex.next(), token(Token::Word("jumped"), 2, 0)); - assert_eq!(lex.next(), token(Token::Word("lazy"), 4, 0)); - assert_eq!(lex.next(), None); - - let mut lex = Lexer::new("line1 //\nsym_2/#\n\t\tl3///333"); - assert_eq!(lex.next(), token(Token::Word("line1"), 1, 0)); - assert_eq!(lex.next(), token(Token::Word("sym_2"), 2, 0)); - assert_eq!(lex.next(), error(LexError::InvalidChar('/'), 2, 5)); - assert_eq!(lex.next(), token(Token::Hash, 2, 6)); - assert_eq!(lex.next(), token(Token::Word("l3"), 3, 16)); // Two tabs = 16 columns - assert_eq!(lex.next(), None); - - let mut lex = Lexer::new("a /* b */ c"); - assert_eq!(lex.next(), token(Token::Word("a"), 1, 0)); - assert_eq!(lex.next(), token(Token::Word("c"), 1, 10)); - - let mut lex = Lexer::new("a /* b \n*/ c\n/*"); - assert_eq!(lex.next(), token(Token::Word("a"), 1, 0)); - assert_eq!(lex.next(), token(Token::Word("c"), 2, 3)); - assert_eq!(lex.next(), error(LexError::UnterminatedComment, 3, 0)); - } - - #[test] - fn quotes() { - let mut lex = Lexer::new("a \"bc\" d"); - assert_eq!(lex.next(), token(Token::Word("a"), 1, 0)); - assert_eq!(lex.next(), token(Token::Quote("bc"), 1, 2)); - assert_eq!(lex.next(), token(Token::Word("d"), 1, 7)); - - let mut lex = Lexer::new("a \"b\nc\" d"); - assert_eq!(lex.next(), token(Token::Word("a"), 1, 0)); - assert_eq!(lex.next(), token(Token::Quote("b\nc"), 1, 2)); - assert_eq!(lex.next(), token(Token::Word("d"), 2, 3)); - - let mut lex = Lexer::new("a \"b"); - assert_eq!(lex.next(), token(Token::Word("a"), 1, 0)); - assert_eq!(lex.next(), error(LexError::UnterminatedQuote, 1, 2)); - } - #[test] - fn punctuation() { - let mut lex = Lexer::new("{} () [] *#=:,;"); - assert_eq!(lex.next(), token(Token::LBrace, 1, 0)); - assert_eq!(lex.next(), token(Token::RBrace, 1, 1)); - assert_eq!(lex.next(), token(Token::LPar, 1, 3)); - assert_eq!(lex.next(), token(Token::RPar, 1, 4)); - assert_eq!(lex.next(), token(Token::LBracket, 1, 6)); - assert_eq!(lex.next(), token(Token::RBracket, 1, 7)); - assert_eq!(lex.next(), token(Token::Star, 1, 9)); - assert_eq!(lex.next(), token(Token::Hash, 1, 10)); - assert_eq!(lex.next(), token(Token::Equals, 1, 11)); - assert_eq!(lex.next(), token(Token::Colon, 1, 12)); - assert_eq!(lex.next(), token(Token::Comma, 1, 13)); - assert_eq!(lex.next(), token(Token::Semi, 1, 14)); - assert_eq!(lex.next(), None); - } -} diff --git a/lucet-idl/src/lib.rs b/lucet-idl/src/lib.rs deleted file mode 100644 index 69c4e1961..000000000 --- a/lucet-idl/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod lexer; -pub mod parser; -pub mod types; -pub mod validate; diff --git a/lucet-idl/src/main.rs b/lucet-idl/src/main.rs deleted file mode 100644 index e7a11a969..000000000 --- a/lucet-idl/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/lucet-idl/src/parser.rs b/lucet-idl/src/parser.rs deleted file mode 100644 index 63c04a865..000000000 --- a/lucet-idl/src/parser.rs +++ /dev/null @@ -1,945 +0,0 @@ -use super::lexer::{Keyword, LexError, Lexer, LocatedError, LocatedToken, Token}; -use super::types::{AtomType, Attr, Location}; -use std::error::Error; -use std::fmt; - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum SyntaxDecl { - Struct { - name: String, - members: Vec, - attrs: Vec, - location: Location, - }, - TaggedUnion { - name: String, - variants: Vec, - attrs: Vec, - location: Location, - }, - Enum { - name: String, - variants: Vec, - attrs: Vec, - location: Location, - }, - Alias { - name: String, - what: SyntaxRef, - attrs: Vec, - location: Location, - }, -} - -impl SyntaxDecl { - pub fn name(&self) -> &str { - match self { - SyntaxDecl::Struct { name, .. } => &name, - SyntaxDecl::TaggedUnion { name, .. } => &name, - SyntaxDecl::Enum { name, .. } => &name, - SyntaxDecl::Alias { name, .. } => &name, - } - } - pub fn location(&self) -> &Location { - match self { - SyntaxDecl::Struct { location, .. } => &location, - SyntaxDecl::TaggedUnion { location, .. } => &location, - SyntaxDecl::Enum { location, .. } => &location, - SyntaxDecl::Alias { location, .. } => &location, - } - } -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum SyntaxRef { - Atom { - atom: AtomType, - location: Location, - }, - Ptr { - to: Box, - location: Location, - }, - Name { - name: String, - location: Location, - }, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct StructMember { - pub name: String, - pub type_: SyntaxRef, - pub attrs: Vec, - pub location: Location, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct UnionVariant { - pub name: String, - pub type_: Option, - pub attrs: Vec, - pub location: Location, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct EnumVariant { - pub name: String, - pub attrs: Vec, - pub location: Location, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct ParseError { - pub location: Location, - pub message: String, -} - -impl fmt::Display for ParseError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "Parse error at line {} column {}: {}", - self.location.line, self.location.column, self.message - ) - } -} - -impl Error for ParseError { - fn description(&self) -> &str { - "Parse error" - } -} - -macro_rules! parse_err { - ($loc:expr, $msg: expr ) => { - Err(ParseError { - location: $loc.clone(), - message: $msg.to_string(), - }) - }; - - ($loc:expr, $fmt:expr, $( $arg:expr),+ ) => { - Err(ParseError { - location: $loc.clone(), - message: format!( $fmt, $( $arg ),+ ), - }) - }; -} -macro_rules! err_ctx { - ($ctx:expr, $res:expr) => { - match $res { - Ok(a) => Ok(a), - Err(ParseError { location, message }) => Err(ParseError { - location, - message: format!("in {}:\n{}", $ctx, message), - }), - } - }; -} - -pub struct Parser<'a> { - lex: Lexer<'a>, - lookahead: Option>, - pub lex_error: Option, - location: Location, -} - -impl<'a> Parser<'a> { - pub fn new(text: &'a str) -> Parser { - Parser { - lex: Lexer::new(text), - lookahead: None, - lex_error: None, - location: Location { line: 0, column: 0 }, - } - } - fn consume(&mut self) -> Token<'a> { - self.lookahead.take().expect("no token to consume") - } - fn token(&mut self) -> Option> { - while self.lookahead == None { - match self.lex.next() { - Some(Ok(LocatedToken { token, location })) => { - self.location = location; - self.lookahead = Some(token) - } - Some(Err(LocatedError { error, location })) => { - self.location = location; - self.lex_error = Some(error); - break; - } - None => break, - } - } - self.lookahead - } - - fn match_token(&mut self, want: Token<'a>, err_msg: &str) -> Result, ParseError> { - if self.token() == Some(want) { - Ok(self.consume()) - } else { - parse_err!(self.location, err_msg) - } - } - - fn match_a_word(&mut self, err_msg: &str) -> Result<&'a str, ParseError> { - match self.token() { - Some(Token::Word(text)) => { - self.consume(); - Ok(text) - } - t => parse_err!(self.location, "{}, got {:?}", err_msg, t), - } - } - - fn match_attr_body(&mut self) -> Result { - let location = self.location; - self.match_token(Token::LBracket, "expected attribute start [")?; - let key = self.match_a_word("expected attribute key")?; - self.match_token(Token::Equals, "expected =")?; - let val = match self.token() { - Some(Token::Word(text)) => text, - Some(Token::Quote(text)) => text, - _ => parse_err!(self.location, "expected word or quoted string")?, - }; - self.consume(); - self.match_token(Token::RBracket, "expected ]")?; - Ok(Attr::new(key, val, location)) - } - - fn match_struct_body(&mut self) -> Result, ParseError> { - let mut members = Vec::new(); - let mut attrs = Vec::new(); - loop { - match self.token() { - Some(Token::RBrace) => { - self.consume(); - break; - } - Some(Token::Hash) => { - self.consume(); - attrs.push(self.match_attr_body()?); - } - Some(Token::Word(member_name)) => { - let location = self.location; - self.consume(); - self.match_token(Token::Colon, "expected :")?; - let member_ref = self.match_ref("expected member type")?; - members.push(StructMember { - name: member_name.to_string(), - type_: member_ref, - attrs: attrs.clone(), - location, - }); - attrs.clear(); - match self.token() { - Some(Token::Comma) => { - self.consume(); - continue; - } - Some(Token::RBrace) => { - self.consume(); - break; - } - _ => parse_err!(self.location, "in struct body:\nexpected , or }}")?, - } - } - _ => parse_err!(self.location, "in struct body:\nexpected member name or }}")?, - } - } - Ok(members) - } - - fn match_tagged_union_body(&mut self) -> Result, ParseError> { - let mut variants = Vec::new(); - let mut attrs = Vec::new(); - loop { - match self.token() { - Some(Token::RBrace) => { - self.consume(); - break; - } - Some(Token::Hash) => { - self.consume(); - attrs.push(self.match_attr_body()?); - } - Some(Token::Word(variant_name)) => { - let location = self.location; - self.consume(); - self.match_token(Token::Colon, "expected :")?; - - let type_ = match self.token() { - Some(Token::LPar) => { - self.consume(); - self.match_token(Token::RPar, "expected )")?; - None - } - _ => Some(self.match_ref("expected member type or ()")?), - }; - - variants.push(UnionVariant { - name: variant_name.to_owned(), - type_, - attrs: attrs.clone(), - location, - }); - attrs.clear(); - match self.token() { - Some(Token::Comma) => { - self.consume(); - continue; - } - Some(Token::RBrace) => { - self.consume(); - break; - } - _ => parse_err!(self.location, "expected , or }}")?, - } - } - _ => parse_err!(self.location, "expected variant")?, - } - } - Ok(variants) - } - - fn match_enum_body(&mut self) -> Result, ParseError> { - let mut names = Vec::new(); - let mut attrs = Vec::new(); - loop { - match self.token() { - Some(Token::RBrace) => { - self.consume(); - break; - } - Some(Token::Hash) => { - self.consume(); - attrs.push(self.match_attr_body()?); - } - Some(Token::Word(name)) => { - let location = self.location; - self.consume(); - names.push(EnumVariant { - name: name.to_owned(), - attrs: attrs.clone(), - location, - }); - attrs.clear(); - match self.token() { - Some(Token::Comma) => { - self.consume(); - continue; - } - Some(Token::RBrace) => { - self.consume(); - break; - } - _ => parse_err!(self.location, "expected , or }}")?, - } - } - _ => parse_err!(self.location, "expected variant")?, - } - } - Ok(names) - } - - pub fn match_decl(&mut self, err_msg: &str) -> Result, ParseError> { - let mut attrs = Vec::new(); - loop { - match self.token() { - Some(Token::Keyword(Keyword::Struct)) => { - let location = self.location; - self.consume(); - let name = err_ctx!(err_msg, self.match_a_word("expected struct name"))?; - err_ctx!(err_msg, self.match_token(Token::LBrace, "expected {"))?; - let members = err_ctx!(err_msg, self.match_struct_body())?; - return Ok(Some(SyntaxDecl::Struct { - name: name.to_owned(), - members, - attrs, - location, - })); - } - Some(Token::Keyword(Keyword::TaggedUnion)) => { - let location = self.location; - self.consume(); - let name = err_ctx!(err_msg, self.match_a_word("expected tagged union name"))?; - err_ctx!(err_msg, self.match_token(Token::LBrace, "expected {"))?; - let variants = err_ctx!(err_msg, self.match_tagged_union_body())?; - return Ok(Some(SyntaxDecl::TaggedUnion { - name: name.to_owned(), - variants, - attrs, - location, - })); - } - Some(Token::Keyword(Keyword::Enum)) => { - let location = self.location; - self.consume(); - let name = err_ctx!(err_msg, self.match_a_word("expected enum name"))?; - err_ctx!(err_msg, self.match_token(Token::LBrace, "expected {"))?; - let variants = err_ctx!(err_msg, self.match_enum_body())?; - return Ok(Some(SyntaxDecl::Enum { - name: name.to_owned(), - variants, - attrs, - location, - })); - } - Some(Token::Keyword(Keyword::Type)) => { - let location = self.location; - self.consume(); - let name = err_ctx!(err_msg, self.match_a_word("expected type name"))?; - err_ctx!(err_msg, self.match_token(Token::Equals, "expected ="))?; - let what = self.match_ref("type value")?; - return Ok(Some(SyntaxDecl::Alias { - name: name.to_owned(), - what, - attrs, - location, - })); - } - Some(Token::Hash) => { - self.consume(); - attrs.push(self.match_attr_body()?); - continue; - } - Some(_) => return parse_err!(self.location, "expected keyword or attribute"), - None => return Ok(None), - } - } - } - - pub fn match_decls(&mut self) -> Result, ParseError> { - let mut decls = Vec::new(); - loop { - match self.match_decl("declaration") { - Ok(Some(decl)) => decls.push(decl), - Ok(None) => break, - Err(e) => Err(e)?, - } - } - Ok(decls) - } - - fn match_ref(&mut self, err_msg: &str) -> Result { - match self.token() { - Some(Token::Atom(atom)) => { - let location = self.location; - self.consume(); - Ok(SyntaxRef::Atom { atom, location }) - } - Some(Token::Word(name)) => { - let location = self.location; - self.consume(); - Ok(SyntaxRef::Name { - name: name.to_owned(), - location, - }) - } - Some(Token::Star) => { - let location = self.location; - self.consume(); - let ref_of = self.match_ref(err_msg)?; - Ok(SyntaxRef::Ptr { - to: Box::new(ref_of), - location, - }) - } - _ => err_ctx!( - err_msg, - parse_err!(self.location, "expected atom, ref, or type name") - ), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn structs() { - let mut parser = Parser::new("struct foo {}"); - assert_eq!( - parser - .match_decl("empty struct") - .expect("valid parse") - .expect("valid decl"), - SyntaxDecl::Struct { - name: "foo".to_string(), - members: Vec::new(), - attrs: Vec::new(), - location: Location { line: 1, column: 0 }, - } - ); - let mut parser = Parser::new("struct foo {a: i32 }"); - // column ruler: 0 7 12 15 - assert_eq!( - parser - .match_decl("foo a i32") - .expect("valid parse") - .expect("valid decl"), - SyntaxDecl::Struct { - name: "foo".to_string(), - members: vec![StructMember { - name: "a".to_owned(), - type_: SyntaxRef::Atom { - atom: AtomType::I32, - location: Location { - line: 1, - column: 15, - }, - }, - attrs: Vec::new(), - location: Location { - line: 1, - column: 12, - }, - }], - attrs: Vec::new(), - location: Location { line: 1, column: 0 }, - } - ); - let mut parser = Parser::new("struct foo {b: i32, }"); - // 0 7 12 15 - assert_eq!( - parser - .match_decl("foo b i32 with trailing comma") - .expect("valid parse") - .expect("valid decl"), - SyntaxDecl::Struct { - name: "foo".to_string(), - members: vec![StructMember { - name: "b".to_owned(), - type_: SyntaxRef::Atom { - atom: AtomType::I32, - location: Location { - line: 1, - column: 15, - }, - }, - attrs: Vec::new(), - location: Location { - line: 1, - column: 12, - }, - }], - attrs: Vec::new(), - location: Location { line: 1, column: 0 }, - } - ); - let mut parser = Parser::new("struct c { d: f64, e: *u8 }"); - // 0 7 11 14 19 22 - assert_eq!( - parser - .match_decl("struct c") - .expect("valid parse") - .expect("valid decl"), - SyntaxDecl::Struct { - name: "c".to_string(), - members: vec![ - StructMember { - name: "d".to_owned(), - type_: SyntaxRef::Atom { - atom: AtomType::F64, - location: Location { - line: 1, - column: 14, - }, - }, - attrs: Vec::new(), - location: Location { - line: 1, - column: 11, - }, - }, - StructMember { - name: "e".to_owned(), - type_: SyntaxRef::Ptr { - to: Box::new(SyntaxRef::Atom { - atom: AtomType::U8, - location: Location { - line: 1, - column: 23, - }, - }), - location: Location { - line: 1, - column: 22, - }, - }, - attrs: Vec::new(), - location: Location { - line: 1, - column: 19, - }, - }, - ], - attrs: Vec::new(), - location: Location { line: 1, column: 0 }, - } - ); - - // Test out attributes: - let mut parser = Parser::new("#[key1=val1] struct foo {}"); - assert_eq!( - parser - .match_decl("empty struct") - .expect("valid parse") - .expect("valid decl"), - SyntaxDecl::Struct { - name: "foo".to_string(), - members: Vec::new(), - attrs: vec![Attr::new("key1", "val1", Location { line: 1, column: 0 })], - location: Location { - line: 1, - column: 13, - }, - } - ); - let mut parser = Parser::new("#[key2=\"1 value with spaces!\"]\nstruct foo {}"); - assert_eq!( - parser - .match_decl("empty struct") - .expect("valid parse") - .expect("valid decl"), - SyntaxDecl::Struct { - name: "foo".to_string(), - members: Vec::new(), - attrs: vec![Attr::new( - "key2", - "1 value with spaces!", - Location { line: 1, column: 0 }, - )], - location: Location { line: 2, column: 0 }, - } - ); - let mut parser = Parser::new("#[key1=val1]\n\t#[key2 = \"val2\" ]\nstruct foo {}"); - assert_eq!( - parser - .match_decl("empty struct") - .expect("valid parse") - .expect("valid decl"), - SyntaxDecl::Struct { - name: "foo".to_string(), - members: Vec::new(), - attrs: vec![ - Attr::new("key1", "val1", Location { line: 1, column: 0 }), - Attr::new("key2", "val2", Location { line: 2, column: 8 }), - ], - location: Location { line: 3, column: 0 }, - } - ); - let mut parser = Parser::new("struct foo {\n\t#[key=val]\n\tmem: f32,\n}"); - assert_eq!( - parser - .match_decl("empty struct") - .expect("valid parse") - .expect("valid decl"), - SyntaxDecl::Struct { - name: "foo".to_string(), - members: vec![StructMember { - name: "mem".to_owned(), - type_: SyntaxRef::Atom { - atom: AtomType::F32, - location: Location { - line: 3, - column: 13, - }, - }, - attrs: vec![Attr::new("key", "val", Location { line: 2, column: 8 })], - location: Location { line: 3, column: 8 }, - }], - attrs: Vec::new(), // - location: Location { line: 1, column: 0 }, - } - ); - } - - #[test] - fn tagged_unions() { - let mut parser = Parser::new("taggedunion foo {}"); - assert_eq!( - parser - .match_decl("empty tagged union") - .expect("valid parse") - .expect("valid decl"), - SyntaxDecl::TaggedUnion { - name: "foo".to_owned(), - variants: Vec::new(), - attrs: Vec::new(), - location: Location { line: 1, column: 0 }, - }, - ); - let mut parser = Parser::new("taggedunion bar {a: (), }"); - // 0 12 17 - assert_eq!( - parser - .match_decl("tagged union, trailing comma") - .expect("valid parse") - .expect("valid decl"), - SyntaxDecl::TaggedUnion { - name: "bar".to_owned(), - variants: vec![UnionVariant { - name: "a".to_owned(), - type_: None, - attrs: Vec::new(), - location: Location { - line: 1, - column: 17, - }, - }], - attrs: Vec::new(), - location: Location { line: 1, column: 0 }, - }, - ); - let mut parser = Parser::new("taggedunion bat {a : ( ) }"); - // 0 12 17 - assert_eq!( - parser - .match_decl("tagged union, no trailing comma") - .expect("valid parse") - .expect("valid decl"), - SyntaxDecl::TaggedUnion { - name: "bat".to_owned(), - variants: vec![UnionVariant { - name: "a".to_owned(), - type_: None, - attrs: Vec::new(), - location: Location { - line: 1, - column: 17, - }, - }], - attrs: Vec::new(), - location: Location { line: 1, column: 0 }, - }, - ); - let mut parser = Parser::new("taggedunion baz {a:(), b:f32, }"); - // 0 12 17 23 - assert_eq!( - parser - .match_decl("2 member tagged union, trailing comma") - .expect("valid parse") - .expect("valid decl"), - SyntaxDecl::TaggedUnion { - name: "baz".to_owned(), - variants: vec![ - UnionVariant { - name: "a".to_owned(), - type_: None, - attrs: Vec::new(), - location: Location { - line: 1, - column: 17, - }, - }, - UnionVariant { - name: "b".to_owned(), - type_: Some(SyntaxRef::Atom { - atom: AtomType::F32, - location: Location { - line: 1, - column: 25, - }, - }), - attrs: Vec::new(), - location: Location { - line: 1, - column: 23, - }, - }, - ], - attrs: Vec::new(), - location: Location { line: 1, column: 0 }, - }, - ); - let mut parser = Parser::new("taggedunion acab {a:(), b: something_else }"); - // 0 12 18 24 27 - assert_eq!( - parser - .match_decl("2 member tagged union, no trailing comma") - .expect("valid parse") - .expect("valid decl"), - SyntaxDecl::TaggedUnion { - name: "acab".to_owned(), - variants: vec![ - UnionVariant { - name: "a".to_owned(), - type_: None, - attrs: Vec::new(), - location: Location { - line: 1, - column: 18, - }, - }, - UnionVariant { - name: "b".to_owned(), - type_: Some(SyntaxRef::Name { - name: "something_else".to_owned(), - location: Location { - line: 1, - column: 27, - }, - }), - attrs: Vec::new(), - location: Location { - line: 1, - column: 24, - }, - }, - ], - attrs: Vec::new(), - location: Location { line: 1, column: 0 }, - }, - ); - - let mut parser = Parser::new("#[attr1=ftp]\ntaggedunion acab {\n#[yes=\"all of them\"] are_complicit: (),\n#[especially= PPB\n]and_always: lie}"); - assert_eq!( - parser - .match_decl("2 member tagged union, no trailing comma, with attributes") - .expect("valid parse") - .expect("valid decl"), - SyntaxDecl::TaggedUnion { - name: "acab".to_owned(), - variants: vec![ - UnionVariant { - name: "are_complicit".to_owned(), - type_: None, - attrs: vec![Attr::new( - "yes", - "all of them", - Location { line: 3, column: 0 }, - )], - location: Location { - line: 3, - column: 21, - }, - }, - UnionVariant { - name: "and_always".to_owned(), - type_: Some(SyntaxRef::Name { - name: "lie".to_owned(), - location: Location { - line: 5, - column: 13, - }, - }), - attrs: vec![Attr::new( - "especially", - "PPB", - Location { line: 4, column: 0 }, - )], - location: Location { line: 5, column: 1 }, - }, - ], - attrs: vec![Attr::new("attr1", "ftp", Location { line: 1, column: 0 })], - location: Location { line: 2, column: 0 }, - }, - ); - } - - #[test] - fn enums() { - let mut parser = Parser::new("enum foo {}"); - // 0 5 - assert_eq!( - parser - .match_decl("empty enum") - .expect("valid parse") - .expect("valid decl"), - SyntaxDecl::Enum { - name: "foo".to_owned(), - variants: Vec::new(), - attrs: Vec::new(), - location: Location { line: 1, column: 0 }, - }, - ); - let mut parser = Parser::new("enum foo {first,}"); - // 0 5 10 - assert_eq!( - parser - .match_decl("one entry enum, trailing comma") - .expect("valid parse") - .expect("valid decl"), - SyntaxDecl::Enum { - name: "foo".to_owned(), - variants: vec![EnumVariant { - name: "first".to_owned(), - attrs: Vec::new(), - location: Location { - line: 1, - column: 10, - }, - }], - attrs: Vec::new(), - location: Location { line: 1, column: 0 }, - }, - ); - let mut parser = Parser::new("enum bar {first}"); - // 0 5 10 - assert_eq!( - parser - .match_decl("one entry enum, no trailing comma") - .expect("valid parse") - .expect("valid decl"), - SyntaxDecl::Enum { - name: "bar".to_owned(), - variants: vec![EnumVariant { - name: "first".to_owned(), - attrs: Vec::new(), - location: Location { - line: 1, - column: 10, - }, - }], - attrs: Vec::new(), - location: Location { line: 1, column: 0 }, - }, - ); - let mut parser = Parser::new("enum baz { one, two, three\n, four, }"); - // 0 5 11 16 21 0 2 - assert_eq!( - parser - .match_decl("four entry enum, trailing comma") - .expect("valid parse") - .expect("valid decl"), - SyntaxDecl::Enum { - name: "baz".to_owned(), - variants: vec![ - EnumVariant { - name: "one".to_owned(), - attrs: Vec::new(), - location: Location { - line: 1, - column: 11, - }, - }, - EnumVariant { - name: "two".to_owned(), - attrs: Vec::new(), - location: Location { - line: 1, - column: 16, - }, - }, - EnumVariant { - name: "three".to_owned(), - attrs: Vec::new(), - location: Location { - line: 1, - column: 21, - }, - }, - EnumVariant { - name: "four".to_owned(), - attrs: Vec::new(), - location: Location { line: 2, column: 2 }, - }, - ], - attrs: Vec::new(), - location: Location { line: 1, column: 0 }, - }, - ); - } -} diff --git a/lucet-idl/src/types.rs b/lucet-idl/src/types.rs deleted file mode 100644 index 7c747a4c3..000000000 --- a/lucet-idl/src/types.rs +++ /dev/null @@ -1,36 +0,0 @@ -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum AtomType { - U8, - U16, - U32, - U64, - I8, - I16, - I32, - I64, - F32, - F64, -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] -pub struct Location { - pub line: usize, - pub column: usize, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct Attr { - pub key: String, - pub val: String, - pub location: Location, -} - -impl Attr { - pub fn new(key: &str, val: &str, location: Location) -> Attr { - Attr { - key: key.to_owned(), - val: val.to_owned(), - location, - } - } -} diff --git a/lucet-idl/src/validate.rs b/lucet-idl/src/validate.rs deleted file mode 100644 index 185cd84f6..000000000 --- a/lucet-idl/src/validate.rs +++ /dev/null @@ -1,665 +0,0 @@ -use super::parser::{SyntaxDecl, SyntaxRef}; -use super::types::{AtomType, Attr, Location}; -use std::collections::HashMap; -use std::error::Error; -use std::fmt; - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub struct DatatypeId(pub usize); - -impl fmt::Display for DatatypeId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum DatatypeRef { - Defined(DatatypeId), - Atom(AtomType), - Ptr(Box), -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct NamedMember { - pub type_: R, - pub name: String, - pub attrs: Vec, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum Datatype { - Struct { - members: Vec>, - attrs: Vec, - }, - TaggedUnion { - members: Vec>>, - attrs: Vec, - }, - Enum { - members: Vec>, - attrs: Vec, - }, - Alias { - to: DatatypeRef, - attrs: Vec, - }, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct Name { - pub name: String, - pub location: Location, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct DataDescription { - pub names: Vec, - pub datatypes: HashMap, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum ValidationError { - NameAlreadyExists { - name: String, - at_location: Location, - previous_location: Location, - }, - NameNotFound { - name: String, - use_location: Location, - }, - Empty { - name: String, - location: Location, - }, - Infinite { - name: String, - location: Location, - }, -} - -impl fmt::Display for ValidationError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - ValidationError::NameAlreadyExists { - name, - at_location, - previous_location, - } => write!( - f, - "Redefinition of name {} at line {} - previous definition was at line {}", - name, at_location.line, previous_location.line - ), - ValidationError::NameNotFound { name, use_location } => { - write!(f, "Name {} not found at line {}", name, use_location.line) - } - ValidationError::Empty { name, location } => { - write!(f, "Empty definition for {} at line {}", name, location.line) - } - ValidationError::Infinite { name, location } => write!( - f, - "Circular reference for {} at line {}", - name, location.line - ), - } - } -} - -impl Error for ValidationError { - fn description(&self) -> &str { - "Validation error" - } -} - -impl DataDescription { - fn new() -> Self { - Self { - names: Vec::new(), - datatypes: HashMap::new(), - } - } - - fn introduce_name( - &mut self, - name: &str, - location: &Location, - ) -> Result { - if let Some(existing) = self.id_for_name(&name) { - let prev = self - .names - .get(existing.0) - .expect("lookup told us name exists"); - Err(ValidationError::NameAlreadyExists { - name: name.to_owned(), - at_location: *location, - previous_location: prev.location, - }) - } else { - let id = self.names.len(); - self.names.push(Name { - name: name.to_owned(), - location: *location, - }); - Ok(DatatypeId(id)) - } - } - - fn id_for_name(&self, name: &str) -> Option { - for (id, n) in self.names.iter().enumerate() { - if n.name == name { - return Some(DatatypeId(id)); - } - } - None - } - - fn get_ref(&self, syntax_ref: &SyntaxRef) -> Result { - match syntax_ref { - SyntaxRef::Atom { atom, .. } => Ok(DatatypeRef::Atom(*atom)), - SyntaxRef::Ptr { to, .. } => Ok(DatatypeRef::Ptr(Box::new(self.get_ref(&to)?))), - SyntaxRef::Name { name, location } => match self.id_for_name(name) { - Some(id) => Ok(DatatypeRef::Defined(id)), - None => Err(ValidationError::NameNotFound { - name: name.clone(), - use_location: *location, - }), - }, - } - } - - fn define_datatype(&mut self, id: DatatypeId, dt: Datatype) { - if let Some(prev_def) = self.datatypes.insert(id.0, dt) { - panic!("id {} already defined: {:?}", id, prev_def) - } - } - - fn define_decl(&mut self, id: DatatypeId, decl: &SyntaxDecl) -> Result<(), ValidationError> { - match decl { - SyntaxDecl::Struct { - name, - members, - attrs, - location, - } => { - let mut uniq_membs = HashMap::new(); - let mut dtype_members = Vec::new(); - if members.is_empty() { - Err(ValidationError::Empty { - name: name.clone(), - location: *location, - })? - } - for mem in members { - // Ensure that each member name is unique: - if let Some(existing) = uniq_membs.insert(mem.name.clone(), mem) { - Err(ValidationError::NameAlreadyExists { - name: mem.name.clone(), - at_location: mem.location, - previous_location: existing.location, - })? - } - // Get the DatatypeRef for the member, which ensures that it refers only to - // defined types: - let type_ = self.get_ref(&mem.type_)?; - // build the struct with this as the member: - dtype_members.push(NamedMember { - type_, - name: mem.name.clone(), - attrs: mem.attrs.clone(), - }) - } - self.define_datatype( - id, - Datatype::Struct { - members: dtype_members, - attrs: attrs.clone(), - }, - ) - } - SyntaxDecl::TaggedUnion { - name, - variants, - attrs, - location, - } => { - let mut uniq_vars = HashMap::new(); - let mut dtype_members = Vec::new(); - if variants.is_empty() { - Err(ValidationError::Empty { - name: name.clone(), - location: *location, - })? - } - for var in variants { - // Ensure that each member name is unique: - if let Some(existing) = uniq_vars.insert(var.name.clone(), var) { - Err(ValidationError::NameAlreadyExists { - name: var.name.clone(), - at_location: var.location, - previous_location: existing.location, - })? - } - // Get the DatatypeRef for the member, which ensures that it refers only to - // defined types: - let type_ = if let Some(ref t) = var.type_ { - Some(self.get_ref(t)?) - } else { - None - }; - // build the struct with this as the member: - dtype_members.push(NamedMember { - type_, - name: var.name.clone(), - attrs: var.attrs.clone(), - }) - } - self.define_datatype( - id, - Datatype::TaggedUnion { - members: dtype_members, - attrs: attrs.clone(), - }, - ) - } - SyntaxDecl::Enum { - name, - variants, - attrs, - location, - } => { - let mut uniq_vars = HashMap::new(); - let mut dtype_members = Vec::new(); - if variants.is_empty() { - Err(ValidationError::Empty { - name: name.clone(), - location: *location, - })? - } - for var in variants { - // Ensure that each member name is unique: - if let Some(existing) = uniq_vars.insert(var.name.clone(), var) { - Err(ValidationError::NameAlreadyExists { - name: var.name.clone(), - at_location: var.location, - previous_location: existing.location, - })? - } - // build the struct with this as the member: - dtype_members.push(NamedMember { - type_: (), - name: var.name.clone(), - attrs: var.attrs.clone(), - }) - } - self.define_datatype( - id, - Datatype::Enum { - members: dtype_members, - attrs: attrs.clone(), - }, - ) - } - SyntaxDecl::Alias { what, attrs, .. } => { - let to = self.get_ref(what)?; - self.define_datatype( - id, - Datatype::Alias { - to, - attrs: attrs.clone(), - }, - ); - } - } - Ok(()) - } - - fn dfs_walk( - &self, - id: DatatypeId, - visited: &mut [bool], - ordered: &mut Option<&mut Vec>, - ) -> Result<(), ()> { - if visited[id.0] { - Err(())? - } - visited[id.0] = true; - match self.datatypes.get(&id.0).expect("datatype is defined") { - Datatype::Struct { members, .. } => { - for mem in members { - if let DatatypeRef::Defined(id) = mem.type_ { - self.dfs_walk(id, visited, ordered)? - } - } - } - Datatype::TaggedUnion { members, .. } => { - for mem in members { - if let Some(DatatypeRef::Defined(id)) = mem.type_ { - self.dfs_walk(id, visited, ordered)? - } - } - } - Datatype::Alias { to, .. } => { - if let DatatypeRef::Defined(id) = to { - self.dfs_walk(*id, visited, ordered)? - } - } - Datatype::Enum { .. } => {} - } - if let Some(ordered) = ordered.as_mut() { - if !ordered.contains(&id) { - ordered.push(id) - } - } - visited[id.0] = false; - Ok(()) - } - - pub fn ordered_dependencies(&self) -> Result, ()> { - let mut visited = Vec::new(); - visited.resize(self.names.len(), false); - let mut ordered = Vec::with_capacity(visited.capacity()); - for id in self.datatypes.keys() { - let _ = self.dfs_walk(DatatypeId(*id), &mut visited, &mut Some(&mut ordered)); - } - Ok(ordered) - } - - fn dfs_find_cycle(&self, id: DatatypeId) -> Result<(), ()> { - let mut visited = Vec::new(); - visited.resize(self.names.len(), false); - self.dfs_walk(id, &mut visited, &mut None) - } - - fn ensure_finite(&self, id: DatatypeId, decl: &SyntaxDecl) -> Result<(), ValidationError> { - self.dfs_find_cycle(id) - .map_err(|_| ValidationError::Infinite { - name: decl.name().to_owned(), - location: *decl.location(), - }) - } - - pub fn validate(decls: &[SyntaxDecl]) -> Result { - let mut desc = Self::new(); - let mut idents: Vec = Vec::new(); - for decl in decls { - idents.push(desc.introduce_name(decl.name(), decl.location())?) - } - - for (decl, id) in decls.iter().zip(&idents) { - desc.define_decl(id.clone(), decl)? - } - - for (decl, id) in decls.iter().zip(idents) { - desc.ensure_finite(id, decl)? - } - - Ok(desc) - } -} - -#[cfg(test)] -mod tests { - use super::super::parser::Parser; - use super::*; - - fn data_description(syntax: &str) -> Result { - let mut parser = Parser::new(syntax); - let decls = parser.match_decls().expect("parses"); - DataDescription::validate(&decls) - } - - #[test] - fn structs() { - assert!(data_description("struct foo { a: i32}").is_ok()); - assert!(data_description("struct foo { a: i32, b: f32 }").is_ok()); - - { - let d = data_description("struct foo { a: i32, b: f32 }").unwrap(); - let members = match &d.datatypes[&0] { - Datatype::Struct { members, .. } => members, - _ => panic!("Unexpected type"), - }; - assert_eq!(members[0].name, "a"); - assert_eq!(members[1].name, "b"); - match &members[0].type_ { - DatatypeRef::Atom(AtomType::I32) => (), - _ => panic!("Unexpected type"), - }; - match &members[1].type_ { - DatatypeRef::Atom(AtomType::F32) => (), - _ => panic!("Unexpected type"), - }; - } - - // Refer to a struct defined previously: - assert!(data_description("struct foo { a: i32, b: f64 } struct bar { a: foo }").is_ok()); - // Refer to a struct defined afterwards: - assert!(data_description("struct foo { a: i32, b: bar} struct bar { a: i32 }").is_ok()); - - // Refer to itself - assert!(data_description("struct list { next: *list, thing: i32 }").is_ok()); - - // No members - assert_eq!( - data_description("struct foo {}").err().unwrap(), - ValidationError::Empty { - name: "foo".to_owned(), - location: Location { line: 1, column: 0 }, - } - ); - - // Duplicate member in struct - assert_eq!( - data_description("struct foo { \na: i32, \na: f64}") - .err() - .unwrap(), - ValidationError::NameAlreadyExists { - name: "a".to_owned(), - at_location: Location { line: 3, column: 0 }, - previous_location: Location { line: 2, column: 0 }, - } - ); - - // Duplicate definition of struct - assert_eq!( - data_description("struct foo { a: i32 }\nstruct foo { a: i32 } ") - .err() - .unwrap(), - ValidationError::NameAlreadyExists { - name: "foo".to_owned(), - at_location: Location { line: 2, column: 0 }, - previous_location: Location { line: 1, column: 0 }, - } - ); - - // Refer to type that is not declared - assert_eq!( - data_description("struct foo { \nb: bar }").err().unwrap(), - ValidationError::NameNotFound { - name: "bar".to_owned(), - use_location: Location { line: 2, column: 3 }, - } - ); - } - - #[test] - fn tagged_unions() { - assert!(data_description("taggedunion foo { a: () }").is_ok()); - assert!(data_description("taggedunion foo { a: i32 }").is_ok()); - assert!(data_description("taggedunion foo { a: i32, b: f32 }").is_ok()); - assert!(data_description("taggedunion foo { a: i32, b: () }").is_ok()); - - { - let d = data_description("taggedunion foo { a: i32, b: () }").unwrap(); - let members = match &d.datatypes[&0] { - Datatype::TaggedUnion { members, .. } => members, - _ => panic!("Unexpected type"), - }; - assert_eq!(members[0].name, "a"); - assert_eq!(members[1].name, "b"); - match &members[0].type_ { - Some(DatatypeRef::Atom(AtomType::I32)) => (), - _ => panic!("Unexpected type"), - }; - match &members[1].type_ { - None => (), - _ => panic!("Unexpected type"), - }; - } - - // Recursive - assert!(data_description("taggedunion cons { succ: *cons, nil: () }").is_ok()); - - // Refer to a taggedunion defined previously: - assert!( - data_description("taggedunion foo { a: i32, b: f64 } taggedunion bar { a: foo }") - .is_ok() - ); - // Refer to a taggedunion defined afterwards: - assert!( - data_description("taggedunion foo { a: i32, b: bar} taggedunion bar { a: i32 }") - .is_ok() - ); - - // No members - assert_eq!( - data_description("taggedunion foo {}").err().unwrap(), - ValidationError::Empty { - name: "foo".to_owned(), - location: Location { line: 1, column: 0 }, - } - ); - - // Duplicate member in taggedunion - assert_eq!( - data_description("taggedunion foo { \na: i32, \na: f64}") - .err() - .unwrap(), - ValidationError::NameAlreadyExists { - name: "a".to_owned(), - at_location: Location { line: 3, column: 0 }, - previous_location: Location { line: 2, column: 0 }, - } - ); - - // Duplicate definition of name "foo" - assert_eq!( - data_description("taggedunion foo { a: i32 }\nstruct foo { a: i32 } ") - .err() - .unwrap(), - ValidationError::NameAlreadyExists { - name: "foo".to_owned(), - at_location: Location { line: 2, column: 0 }, - previous_location: Location { line: 1, column: 0 }, - } - ); - - // Refer to type that is not declared - assert_eq!( - data_description("taggedunion foo { \nb: bar }") - .err() - .unwrap(), - ValidationError::NameNotFound { - name: "bar".to_owned(), - use_location: Location { line: 2, column: 3 }, - } - ); - } - - #[test] - fn enums() { - assert!(data_description("enum foo { a }").is_ok()); - assert!(data_description("enum foo { a, b }").is_ok()); - - { - let d = data_description("enum foo { a, b }").unwrap(); - let members = match &d.datatypes[&0] { - Datatype::Enum { members, .. } => members, - _ => panic!("Unexpected type"), - }; - assert_eq!(members[0].name, "a"); - assert_eq!(members[1].name, "b"); - } - - // No members - assert_eq!( - data_description("enum foo {}").err().unwrap(), - ValidationError::Empty { - name: "foo".to_owned(), - location: Location { line: 1, column: 0 }, - } - ); - - // Duplicate member in enum - assert_eq!( - data_description("enum foo { \na,\na }").err().unwrap(), - ValidationError::NameAlreadyExists { - name: "a".to_owned(), - at_location: Location { line: 3, column: 0 }, - previous_location: Location { line: 2, column: 0 }, - } - ); - - // Duplicate definition of enum - assert_eq!( - data_description("enum foo { a }\nenum foo { a } ") - .err() - .unwrap(), - ValidationError::NameAlreadyExists { - name: "foo".to_owned(), - at_location: Location { line: 2, column: 0 }, - previous_location: Location { line: 1, column: 0 }, - } - ); - } - - #[test] - fn aliases() { - assert!(data_description("type foo = i32").is_ok()); - assert!(data_description("type foo = *f64").is_ok()); - assert!(data_description("type foo = ************f64").is_ok()); - - assert!(data_description("type foo = *bar\nenum bar { a }").is_ok()); - - assert!( - data_description("type link = *list\nstruct list { next: link, thing: i32 }").is_ok() - ); - } - - #[test] - fn infinite() { - assert_eq!( - data_description("type foo = bar\ntype bar = foo") - .err() - .unwrap(), - ValidationError::Infinite { - name: "foo".to_owned(), - location: Location { line: 1, column: 0 }, - } - ); - - assert_eq!( - data_description("type foo = bar\nstruct bar { a: foo }") - .err() - .unwrap(), - ValidationError::Infinite { - name: "foo".to_owned(), - location: Location { line: 1, column: 0 }, - } - ); - - assert_eq!( - data_description( - "type foo = bar\nstruct bar { a: baz }\ntaggedunion baz { c: i32, e: foo }" - ) - .err() - .unwrap(), - ValidationError::Infinite { - name: "foo".to_owned(), - location: Location { line: 1, column: 0 }, - } - ); - } -} diff --git a/lucet-module-data/Cargo.toml b/lucet-module-data/Cargo.toml deleted file mode 100644 index dc8d813a1..000000000 --- a/lucet-module-data/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "lucet-module-data" -version = "0.1.0" -authors = ["Pat Hickey "] -repository = "https://github.com/fastly/lucet" -edition = "2018" - -[dependencies] -failure = "0.1" -serde = { version = "1.0", features = ["derive"] } -bincode = "~1.0.1" diff --git a/lucet-module-data/src/error.rs b/lucet-module-data/src/error.rs deleted file mode 100644 index f20757a72..000000000 --- a/lucet-module-data/src/error.rs +++ /dev/null @@ -1,12 +0,0 @@ -use failure::Fail; - -/// Module data (de)serialization errors. -#[derive(Debug, Fail)] -pub enum Error { - #[fail(display = "Sparse data contained a page with length other than 4096")] - IncorrectPageSize, - #[fail(display = "Deserialization error: {}", _0)] - DeserializationError(#[cause] bincode::Error), - #[fail(display = "Serialization error: {}", _0)] - SerializationError(#[cause] bincode::Error), -} diff --git a/lucet-module-data/src/globals.rs b/lucet-module-data/src/globals.rs deleted file mode 100644 index 590c0d947..000000000 --- a/lucet-module-data/src/globals.rs +++ /dev/null @@ -1,140 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// A WebAssembly global along with its export specification. -/// -/// The lifetime parameter exists to support zero-copy deserialization for the `&str` fields at the -/// leaves of the structure. For a variant with owned types at the leaves, see -/// [`OwnedGlobalSpec`](owned/struct.OwnedGlobalSpec.html). -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct GlobalSpec<'a> { - #[serde(borrow)] - global: Global<'a>, - export: Option<&'a str>, -} - -impl<'a> GlobalSpec<'a> { - pub fn new(global: Global<'a>, export: Option<&'a str>) -> Self { - Self { global, export } - } - - /// Create a new global definition with an initial value and an optional export name. - pub fn new_def(init_val: i64, export: Option<&'a str>) -> Self { - Self::new( - Global::Def { - def: GlobalDef::new(init_val), - }, - export, - ) - } - - /// Create a new global import definition with a module and field name, and an optional export - /// name. - pub fn new_import(module: &'a str, field: &'a str, export: Option<&'a str>) -> Self { - Self::new(Global::Import { module, field }, export) - } - - pub fn global(&self) -> &Global { - &self.global - } - - pub fn export(&self) -> Option<&str> { - self.export - } -} - -/// A WebAssembly global is either defined locally, or is defined in relation to a field of another -/// WebAssembly module. -/// -/// Lucet currently does not support import globals, but we support the metadata for future -/// compatibility. -/// -/// The lifetime parameter exists to support zero-copy deserialization for the `&str` fields at the -/// leaves of the structure. For a variant with owned types at the leaves, see -/// [`OwnedGlobal`](owned/struct.OwnedGlobal.html). -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum Global<'a> { - Def { def: GlobalDef }, - Import { module: &'a str, field: &'a str }, -} - -/// A global definition. -/// -/// Currently we cast everything to an `i64`, but in the future this may have explicit variants for -/// the different WebAssembly scalar types. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct GlobalDef { - init_val: i64, -} - -impl GlobalDef { - pub fn new(init_val: i64) -> Self { - Self { init_val } - } - - pub fn init_val(&self) -> i64 { - self.init_val - } -} - -///////////////////////////////////////////////////////////////////////////////////////////////////////// - -/// A variant of [`GlobalSpec`](../struct.GlobalSpec.html) with owned strings throughout. -/// -/// This type is useful when directly building up a value to be serialized. -pub struct OwnedGlobalSpec { - global: OwnedGlobal, - export: Option, -} - -impl OwnedGlobalSpec { - pub fn new(global: OwnedGlobal, export: Option) -> Self { - Self { global, export } - } - - /// Create a new global definition with an initial value and an optional export name. - pub fn new_def(init_val: i64, export: Option) -> Self { - Self::new( - OwnedGlobal::Def { - def: GlobalDef::new(init_val), - }, - export, - ) - } - - /// Create a new global import definition with a module and field name, and an optional export - /// name. - pub fn new_import(module: String, field: String, export: Option) -> Self { - Self::new(OwnedGlobal::Import { module, field }, export) - } - - /// Create a [`GlobalSpec`](../struct.GlobalSpec.html) backed by the values in this - /// `OwnedGlobalSpec`. - pub fn to_ref<'a>(&'a self) -> GlobalSpec<'a> { - let export = match &self.export { - Some(e) => Some(e.as_str()), - None => None, - }; - GlobalSpec::new(self.global.to_ref(), export) - } -} - -/// A variant of [`Global`](../struct.Global.html) with owned strings throughout. -/// -/// This type is useful when directly building up a value to be serialized. -pub enum OwnedGlobal { - Def { def: GlobalDef }, - Import { module: String, field: String }, -} - -impl OwnedGlobal { - /// Create a [`Global`](../struct.Global.html) backed by the values in this `OwnedGlobal`. - pub fn to_ref<'a>(&'a self) -> Global<'a> { - match self { - OwnedGlobal::Def { def } => Global::Def { def: def.clone() }, - OwnedGlobal::Import { module, field } => Global::Import { - module: module.as_str(), - field: field.as_str(), - }, - } - } -} diff --git a/lucet-module-data/src/lib.rs b/lucet-module-data/src/lib.rs deleted file mode 100644 index 624f685f7..000000000 --- a/lucet-module-data/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -//! Common types for representing Lucet module data and metadata. -//! -//! These types are used both in `lucetc` and `lucet-runtime`, with values serialized in -//! [`bincode`](https://github.com/TyOverby/bincode) format to the compiled Lucet modules. - -mod error; -mod globals; -mod linear_memory; -mod module_data; - -pub use crate::error::Error; -pub use crate::globals::{Global, GlobalDef, GlobalSpec}; -pub use crate::linear_memory::{HeapSpec, SparseData}; -pub use crate::module_data::ModuleData; - -/// Owned variants of the module data types, useful for serialization and testing. -pub mod owned { - pub use crate::globals::OwnedGlobalSpec; - pub use crate::linear_memory::OwnedSparseData; - pub use crate::module_data::OwnedModuleData; -} diff --git a/lucet-module-data/src/module_data.rs b/lucet-module-data/src/module_data.rs deleted file mode 100644 index b0710961f..000000000 --- a/lucet-module-data/src/module_data.rs +++ /dev/null @@ -1,115 +0,0 @@ -use crate::{ - globals::GlobalSpec, - linear_memory::{HeapSpec, SparseData}, - Error, -}; -use serde::{Deserialize, Serialize}; - -/// The metadata (and some data) for a Lucet module. -/// -/// The lifetime parameter exists to support zero-copy deserialization for the `&str` and `&[u8]` -/// fields at the leaves of the structure. For a variant with owned types at the leaves, see -/// [`OwnedModuleData`](owned/struct.OwnedModuleData.html). -/// -/// The goal is for this structure to eventually include everything except the code for the guest -/// functions themselves. -#[derive(Debug, Serialize, Deserialize)] -pub struct ModuleData<'a> { - heap_spec: HeapSpec, - #[serde(borrow)] - sparse_data: SparseData<'a>, - #[serde(borrow)] - globals_spec: Vec>, -} - -impl<'a> ModuleData<'a> { - pub fn new( - heap_spec: HeapSpec, - sparse_data: SparseData<'a>, - globals_spec: Vec>, - ) -> Self { - Self { - heap_spec, - sparse_data, - globals_spec, - } - } - - pub fn heap_spec(&self) -> &HeapSpec { - &self.heap_spec - } - - pub fn sparse_data(&self) -> &SparseData<'a> { - &self.sparse_data - } - - pub fn globals_spec(&self) -> &[GlobalSpec<'a>] { - &self.globals_spec - } - - /// Serialize to (https://github.com/TyOverby/bincode). - pub fn serialize(&self) -> Result, Error> { - bincode::serialize(self).map_err(Error::SerializationError) - } - - /// Deserialize from [`bincode`](https://github.com/TyOverby/bincode). - pub fn deserialize(buf: &'a [u8]) -> Result, Error> { - bincode::deserialize(buf).map_err(Error::DeserializationError) - } -} - -use crate::{globals::OwnedGlobalSpec, linear_memory::OwnedSparseData}; - -/// The metadata (and some data) for a Lucet module. -/// -/// This is a version of [`ModuleData`](../struct.ModuleData.html) with owned types throughout, -/// rather than references to support zero-copy deserialization. This type is useful when directly -/// building up a value to be serialized. -pub struct OwnedModuleData { - heap_spec: HeapSpec, - sparse_data: OwnedSparseData, - globals_spec: Vec, -} - -impl OwnedModuleData { - pub fn new( - heap_spec: HeapSpec, - sparse_data: OwnedSparseData, - globals_spec: Vec, - ) -> Self { - Self { - heap_spec, - sparse_data, - globals_spec, - } - } - - /// Create a [`ModuleData`](../struct.ModuleData.html) backed by the values in this - /// `OwnedModuleData`. - pub fn to_ref<'a>(&'a self) -> ModuleData<'a> { - ModuleData::new( - self.heap_spec.clone(), - self.sparse_data.to_ref(), - self.globals_spec.iter().map(|gs| gs.to_ref()).collect(), - ) - } - - pub fn empty() -> Self { - Self::new( - HeapSpec::new(0, 0, 0, None), - OwnedSparseData::new(vec![]).unwrap(), - vec![], - ) - } - - pub fn with_heap_spec(mut self, heap_spec: HeapSpec) -> Self { - self.heap_spec = heap_spec; - self - } -} - -impl Default for OwnedModuleData { - fn default() -> Self { - OwnedModuleData::empty() - } -} diff --git a/lucet-module/Cargo.toml b/lucet-module/Cargo.toml new file mode 100644 index 000000000..41736ec14 --- /dev/null +++ b/lucet-module/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "lucet-module" +version = "0.5.0" +description = "A structured interface for Lucet modules" +homepage = "https://github.com/fastly/lucet" +repository = "https://github.com/fastly/lucet" +license = "Apache-2.0 WITH LLVM-exception" +categories = ["wasm"] +authors = ["Lucet team "] +edition = "2018" + +[dependencies] +anyhow = "1.0" +cranelift-entity = { path = "../cranelift/cranelift-entity", version = "0.51.0" } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +bincode = "1.1.4" +num-derive = "0.3.0" +num-traits = "0.2.8" +minisign = "0.5.11" +object = "0.14.0" +byteorder = "1.3" +memoffset = "0.5.1" +thiserror = "1.0.4" +serde-big-array = "0.2.0" +derivative = "1.0.3" diff --git a/lucet-module/src/bindings.rs b/lucet-module/src/bindings.rs new file mode 100644 index 000000000..aa5106b6d --- /dev/null +++ b/lucet-module/src/bindings.rs @@ -0,0 +1,154 @@ +use crate::Error; +use serde_json::{self, Map, Value}; +use std::collections::{hash_map::Entry, HashMap}; +use std::fs; +use std::path::Path; + +#[derive(Debug, Clone)] +pub struct Bindings { + bindings: HashMap>, +} + +impl Bindings { + pub fn new(bindings: HashMap>) -> Bindings { + Self { bindings: bindings } + } + + pub fn env(env: HashMap) -> Bindings { + let mut bindings = HashMap::new(); + bindings.insert("env".to_owned(), env); + Self::new(bindings) + } + + pub fn empty() -> Bindings { + Self::new(HashMap::new()) + } + + pub fn from_json(v: &Value) -> Result { + match v.as_object() { + Some(modules) => Self::parse_modules_json_obj(modules), + None => Err(Error::ParseJsonObjError)?, + } + } + + pub fn from_str(s: &str) -> Result { + let top: Value = serde_json::from_str(s)?; + Ok(Self::from_json(&top)?) + } + + pub fn from_file>(path: P) -> Result { + let contents = fs::read_to_string(path.as_ref())?; + Ok(Self::from_str(&contents)?) + } + + pub fn extend(&mut self, other: &Bindings) -> Result<(), Error> { + for (modname, othermodbindings) in other.bindings.iter() { + match self.bindings.entry(modname.clone()) { + Entry::Occupied(mut e) => { + let existing = e.get_mut(); + for (bindname, binding) in othermodbindings { + match existing.entry(bindname.clone()) { + Entry::Vacant(e) => { + e.insert(binding.clone()); + } + Entry::Occupied(e) => { + if binding != e.get() { + Err(Error::RebindError { + key: e.key().to_owned(), + binding: binding.to_owned(), + attempt: e.get().to_owned(), + })?; + } + } + } + } + } + Entry::Vacant(e) => { + e.insert(othermodbindings.clone()); + } + } + } + Ok(()) + } + + pub fn translate(&self, module: &str, symbol: &str) -> Result<&str, Error> { + match self.bindings.get(module) { + Some(m) => match m.get(symbol) { + Some(s) => Ok(s), + None => Err(Error::UnknownSymbol { + module: module.to_owned(), + symbol: symbol.to_owned(), + }), + }, + None => Err(Error::UnknownModule { + module: module.to_owned(), + symbol: symbol.to_owned(), + }), + } + } + + fn parse_modules_json_obj(m: &Map) -> Result { + let mut res = HashMap::new(); + for (modulename, values) in m { + match values.as_object() { + Some(methods) => { + let methodmap = Self::parse_methods_json_obj(methods)?; + res.insert(modulename.to_owned(), methodmap); + } + None => { + Err(Error::ParseError { + key: modulename.to_owned(), + value: values.to_string(), + })?; + } + } + } + Ok(Self::new(res)) + } + + fn parse_methods_json_obj(m: &Map) -> Result, Error> { + let mut res = HashMap::new(); + for (method, i) in m { + match i.as_str() { + Some(importbinding) => { + res.insert(method.to_owned(), importbinding.to_owned()); + } + None => { + Err(Error::ParseError { + key: method.to_owned(), + value: i.to_string(), + })?; + } + } + } + Ok(res) + } + + pub fn to_string(&self) -> Result { + let s = serde_json::to_string(&self.to_json())?; + Ok(s) + } + + pub fn to_json(&self) -> Value { + Value::from(self.serialize_modules_json_obj()) + } + + fn serialize_modules_json_obj(&self) -> Map { + let mut m = Map::new(); + for (modulename, values) in self.bindings.iter() { + m.insert( + modulename.to_owned(), + Value::from(Self::serialize_methods_json_obj(values)), + ); + } + m + } + + fn serialize_methods_json_obj(methods: &HashMap) -> Map { + let mut m = Map::new(); + for (methodname, symbol) in methods.iter() { + m.insert(methodname.to_owned(), Value::from(symbol.to_owned())); + } + m + } +} diff --git a/lucet-module/src/error.rs b/lucet-module/src/error.rs new file mode 100644 index 000000000..e9e062bbf --- /dev/null +++ b/lucet-module/src/error.rs @@ -0,0 +1,34 @@ +use thiserror::Error; + +/// Module data (de)serialization errors. +#[derive(Debug, Error)] +pub enum Error { + #[error("Deserialization error")] + DeserializationError(#[source] bincode::Error), + #[error("I/O error")] + IOError(#[source] std::io::Error), + #[error("Sparse data contained a page with length other than 4096")] + IncorrectPageSize, + #[error("Module signature error")] + ModuleSignatureError(#[source] minisign::PError), + #[error("Parse error at {key}::{value:?}")] + ParseError { key: String, value: String }, + #[error("Parse json error")] + ParseJsonError(#[from] serde_json::error::Error), + #[error("Top-level json must be an object")] + ParseJsonObjError, + #[error("Parse string error")] + ParseStringError(#[from] std::io::Error), + #[error("Cannot re-bind {key} from {binding} to {attempt}")] + RebindError { + key: String, + binding: String, + attempt: String, + }, + #[error("Serialization error")] + SerializationError(#[source] bincode::Error), + #[error("Unknown module for symbol `{module}::{symbol}")] + UnknownModule { module: String, symbol: String }, + #[error("Unknown symbol `{module}::{symbol}`")] + UnknownSymbol { module: String, symbol: String }, +} diff --git a/lucet-module/src/functions.rs b/lucet-module/src/functions.rs new file mode 100644 index 000000000..87f0b68d9 --- /dev/null +++ b/lucet-module/src/functions.rs @@ -0,0 +1,192 @@ +use crate::traps::{TrapManifest, TrapSite}; +use cranelift_entity::entity_impl; +use serde::{Deserialize, Serialize}; + +use std::slice::from_raw_parts; + +/// FunctionIndex is an identifier for a function, imported, exported, or external. The space of +/// FunctionIndex is shared for all of these, so `FunctionIndex(N)` may identify exported function +/// #2, `FunctionIndex(N + 1)` may identify an internal function, and `FunctionIndex(N + 2)` may +/// identify an imported function. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +pub struct FunctionIndex(u32); + +impl FunctionIndex { + pub fn from_u32(idx: u32) -> FunctionIndex { + FunctionIndex(idx) + } + pub fn as_u32(&self) -> u32 { + self.0 + } +} + +/// ImportFunction describes an internal function - its internal function index and the name/module +/// pair that function should be found in. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +pub struct ImportFunction<'a> { + pub fn_idx: FunctionIndex, + pub module: &'a str, + pub name: &'a str, +} + +/// ExportFunction describes an exported function - its internal function index and a name that +/// function has been exported under. +#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +pub struct ExportFunction<'a> { + pub fn_idx: FunctionIndex, + #[serde(borrow)] + pub names: Vec<&'a str>, +} + +pub struct OwnedExportFunction { + pub fn_idx: FunctionIndex, + pub names: Vec, +} + +impl OwnedExportFunction { + pub fn to_ref<'a>(&'a self) -> ExportFunction<'a> { + ExportFunction { + fn_idx: self.fn_idx.clone(), + names: self.names.iter().map(|x| x.as_str()).collect(), + } + } +} + +pub struct OwnedImportFunction { + pub fn_idx: FunctionIndex, + pub module: String, + pub name: String, +} + +impl OwnedImportFunction { + pub fn to_ref<'a>(&'a self) -> ImportFunction<'a> { + ImportFunction { + fn_idx: self.fn_idx.clone(), + module: self.module.as_str(), + name: self.name.as_str(), + } + } +} + +/// UniqueSignatureIndex names a signature after collapsing duplicate signatures to a single +/// identifier, whereas SignatureIndex is directly what the original module specifies, and may +/// specify duplicates of types that are structurally equal. +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize, Deserialize)] +pub struct UniqueSignatureIndex(u32); +entity_impl!(UniqueSignatureIndex); + +/// FunctionPointer serves entirely as a safer way to work with function pointers than as raw u64 +/// or usize values. It also avoids the need to write them as `fn` types, which cannot be freely +/// cast from one to another with `as`. If you need to call a `FunctionPointer`, use `as_usize()` +/// and transmute the resulting usize to a `fn` type with appropriate signature. +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize, Deserialize)] +pub struct FunctionPointer(usize); + +impl FunctionPointer { + pub fn from_usize(ptr: usize) -> FunctionPointer { + FunctionPointer(ptr) + } + pub fn as_usize(&self) -> usize { + self.0 + } +} + +/// Information about the corresponding function. +/// +/// This is split from but closely related to a [`FunctionSpec`]. The distinction is largely for +/// serialization/deserialization simplicity, as [`FunctionSpec`] contains fields that need +/// cooperation from a loader, with manual layout and serialization as a result. +/// [`FunctionMetadata`] is the remainder of fields that can be automatically +/// serialized/deserialied and are small enough copying isn't a large concern. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct FunctionMetadata<'a> { + pub signature: UniqueSignatureIndex, + /// the "name" field is some human-friendly name, not necessarily the same as used to reach + /// this function (through an export, for example), and may not even indicate that a function + /// is exported at all. + /// TODO: at some point when possible, this field ought to be set from the names section of a + /// wasm module. At the moment that information is lost at parse time. + #[serde(borrow)] + pub name: Option<&'a str>, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct OwnedFunctionMetadata { + pub signature: UniqueSignatureIndex, + pub name: Option, +} + +impl OwnedFunctionMetadata { + pub fn to_ref(&self) -> FunctionMetadata<'_> { + FunctionMetadata { + signature: self.signature.clone(), + name: self.name.as_ref().map(|n| n.as_str()), + } + } +} + +pub struct FunctionHandle { + pub ptr: FunctionPointer, + pub id: FunctionIndex, +} + +// The layout of this struct is very tightly coupled to lucetc's `write_function_manifest`! +// +// Specifically, `write_function_manifest` sets up relocations on `code_addr` and `traps_addr`. +// It does not explicitly serialize a correctly formed `FunctionSpec`, because addresses +// for these fields do not exist until the object is loaded in the future. +// +// So `write_function_manifest` has implicit knowledge of the layout of this structure +// (including padding bytes between `code_len` and `traps_addr`) +#[repr(C)] +#[derive(Clone, Debug)] +pub struct FunctionSpec { + code_addr: u64, + code_len: u32, + traps_addr: u64, + traps_len: u64, +} + +impl FunctionSpec { + pub fn new(code_addr: u64, code_len: u32, traps_addr: u64, traps_len: u64) -> Self { + FunctionSpec { + code_addr, + code_len, + traps_addr, + traps_len, + } + } + pub fn ptr(&self) -> FunctionPointer { + FunctionPointer::from_usize(self.code_addr as usize) + } + pub fn code_len(&self) -> u32 { + self.code_len + } + pub fn traps_len(&self) -> u64 { + self.traps_len + } + pub fn contains(&self, addr: u64) -> bool { + addr >= self.code_addr && (addr - self.code_addr) < (self.code_len as u64) + } + pub fn relative_addr(&self, addr: u64) -> Option { + if let Some(offset) = addr.checked_sub(self.code_addr) { + if offset < (self.code_len as u64) { + // self.code_len is u32, so if the above check succeeded + // offset must implicitly be <= u32::MAX - the following + // conversion will not truncate bits + return Some(offset as u32); + } + } + + None + } + pub fn traps(&self) -> Option> { + let traps_ptr = self.traps_addr as *const TrapSite; + if !traps_ptr.is_null() { + let traps_slice = unsafe { from_raw_parts(traps_ptr, self.traps_len as usize) }; + Some(TrapManifest::new(traps_slice)) + } else { + None + } + } +} diff --git a/lucet-module/src/globals.rs b/lucet-module/src/globals.rs new file mode 100644 index 000000000..801d0c9d8 --- /dev/null +++ b/lucet-module/src/globals.rs @@ -0,0 +1,164 @@ +use serde::{Deserialize, Serialize}; + +/// A WebAssembly global along with its export specification. +/// +/// The lifetime parameter exists to support zero-copy deserialization for the `&str` fields at the +/// leaves of the structure. For a variant with owned types at the leaves, see +/// [`OwnedGlobalSpec`](owned/struct.OwnedGlobalSpec.html). +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct GlobalSpec<'a> { + #[serde(borrow)] + global: Global<'a>, + export_names: Vec<&'a str>, +} + +impl<'a> GlobalSpec<'a> { + pub fn new(global: Global<'a>, export_names: Vec<&'a str>) -> Self { + Self { + global, + export_names, + } + } + + /// Create a new global definition with an initial value and export names. + pub fn new_def(init_val: i64, export_names: Vec<&'a str>) -> Self { + Self::new(Global::Def(GlobalDef::I64(init_val)), export_names) + } + + /// Create a new global import definition with a module and field name, and export names. + pub fn new_import(module: &'a str, field: &'a str, export_names: Vec<&'a str>) -> Self { + Self::new(Global::Import { module, field }, export_names) + } + + pub fn global(&self) -> &Global<'_> { + &self.global + } + + pub fn export_names(&self) -> &[&str] { + &self.export_names + } + + pub fn is_internal(&self) -> bool { + self.export_names.len() == 0 + } +} + +/// A WebAssembly global is either defined locally, or is defined in relation to a field of another +/// WebAssembly module. +/// +/// The lifetime parameter exists to support zero-copy deserialization for the `&str` fields at the +/// leaves of the structure. For a variant with owned types at the leaves, see +/// [`OwnedGlobal`](owned/struct.OwnedGlobal.html). +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Global<'a> { + Def(GlobalDef), + Import { module: &'a str, field: &'a str }, +} + +/// Definition for a global in this module (not imported). +#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] +pub enum GlobalDef { + I32(i32), + I64(i64), + F32(f32), + F64(f64), +} + +impl GlobalDef { + pub fn init_val(&self) -> GlobalValue { + match self { + GlobalDef::I32(i) => GlobalValue { i_32: *i }, + GlobalDef::I64(i) => GlobalValue { i_64: *i }, + GlobalDef::F32(f) => GlobalValue { f_32: *f }, + GlobalDef::F64(f) => GlobalValue { f_64: *f }, + } + } +} + +#[derive(Copy, Clone)] +pub union GlobalValue { + pub i_32: i32, + pub i_64: i64, + pub f_32: f32, + pub f_64: f64, +} + +impl std::fmt::Debug for GlobalValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Because GlobalValue is a union of primitives, there won't be anything wrong, + // representation-wise, with printing the underlying data as an i64, f64, or + // another primitive. This still may incur UB by doing something like trying to + // read data from an uninitialized memory, if the union is initialized with a + // 32-bit value, and then read as a 64-bit value (as this code is about to do). + // + // In short, avoid using ``::fmt, please. + + writeln!(f, "GlobalValue {{")?; + unsafe { + writeln!(f, " i_32: {},", self.i_32)?; + writeln!(f, " i_64: {},", self.i_64)?; + writeln!(f, " f_32: {},", self.f_32)?; + writeln!(f, " f_64: {},", self.f_64)?; + } + writeln!(f, "}}") + } +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +/// A variant of [`GlobalSpec`](../struct.GlobalSpec.html) with owned strings throughout. +/// +/// This type is useful when directly building up a value to be serialized. +pub struct OwnedGlobalSpec { + global: OwnedGlobal, + export_names: Vec, +} + +impl OwnedGlobalSpec { + pub fn new(global: OwnedGlobal, export_names: Vec) -> Self { + Self { + global, + export_names, + } + } + + /// Create a new global definition with an initial value and export names. + pub fn new_def(init_val: i64, export_names: Vec) -> Self { + Self::new(OwnedGlobal::Def(GlobalDef::I64(init_val)), export_names) + } + + /// Create a new global import definition with a module and field name, and export names. + pub fn new_import(module: String, field: String, export_names: Vec) -> Self { + Self::new(OwnedGlobal::Import { module, field }, export_names) + } + + /// Create a [`GlobalSpec`](../struct.GlobalSpec.html) backed by the values in this + /// `OwnedGlobalSpec`. + pub fn to_ref<'a>(&'a self) -> GlobalSpec<'a> { + GlobalSpec::new( + self.global.to_ref(), + self.export_names.iter().map(|x| x.as_str()).collect(), + ) + } +} + +/// A variant of [`Global`](../struct.Global.html) with owned strings throughout. +/// +/// This type is useful when directly building up a value to be serialized. +pub enum OwnedGlobal { + Def(GlobalDef), + Import { module: String, field: String }, +} + +impl OwnedGlobal { + /// Create a [`Global`](../struct.Global.html) backed by the values in this `OwnedGlobal`. + pub fn to_ref<'a>(&'a self) -> Global<'a> { + match self { + OwnedGlobal::Def(def) => Global::Def(def.clone()), + OwnedGlobal::Import { module, field } => Global::Import { + module: module.as_str(), + field: field.as_str(), + }, + } + } +} diff --git a/lucet-module/src/lib.rs b/lucet-module/src/lib.rs new file mode 100644 index 000000000..83a6e988d --- /dev/null +++ b/lucet-module/src/lib.rs @@ -0,0 +1,44 @@ +//! Common types for representing Lucet modules. +//! +//! These types are used both in `lucetc` and `lucet-runtime`, with values serialized in +//! [`bincode`](https://github.com/TyOverby/bincode) format to the compiled Lucet modules. + +#![deny(bare_trait_objects)] + +pub mod bindings; +pub mod error; +mod functions; +mod globals; +mod linear_memory; +mod module; +mod module_data; +mod runtime; +mod signature; +mod tables; +mod traps; +mod types; +mod version_info; + +pub use crate::error::Error; +pub use crate::functions::{ + ExportFunction, FunctionHandle, FunctionIndex, FunctionMetadata, FunctionPointer, FunctionSpec, + ImportFunction, UniqueSignatureIndex, +}; +pub use crate::globals::{Global, GlobalDef, GlobalSpec, GlobalValue}; +pub use crate::linear_memory::{HeapSpec, LinearMemorySpec, SparseData}; +pub use crate::module::{Module, SerializedModule, LUCET_MODULE_SYM}; +pub use crate::module_data::{ModuleData, ModuleFeatures, MODULE_DATA_SYM}; +pub use crate::runtime::InstanceRuntimeData; +pub use crate::signature::{ModuleSignature, PublicKey}; +pub use crate::tables::TableElement; +pub use crate::traps::{TrapCode, TrapManifest, TrapSite}; +pub use crate::types::{Signature, ValueType}; +pub use crate::version_info::VersionInfo; + +/// Owned variants of the module data types, useful for serialization and testing. +pub mod owned { + pub use crate::functions::{OwnedExportFunction, OwnedFunctionMetadata, OwnedImportFunction}; + pub use crate::globals::OwnedGlobalSpec; + pub use crate::linear_memory::{OwnedLinearMemorySpec, OwnedSparseData}; + pub use crate::module_data::OwnedModuleData; +} diff --git a/lucet-module-data/src/linear_memory.rs b/lucet-module/src/linear_memory.rs similarity index 85% rename from lucet-module-data/src/linear_memory.rs rename to lucet-module/src/linear_memory.rs index 5dd79702d..d2107c421 100644 --- a/lucet-module-data/src/linear_memory.rs +++ b/lucet-module/src/linear_memory.rs @@ -1,6 +1,37 @@ use crate::Error; use serde::{Deserialize, Serialize}; +/// Specification of the linear memory of a module +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LinearMemorySpec<'a> { + /// Specification of the heap used to implement the linear memory + pub heap: HeapSpec, + /// Initialization values for linear memory + #[serde(borrow)] + pub initializer: SparseData<'a>, +} + +/// Specification of the linear memory of a module +/// +/// This is a version of [`LinearMemorySpec`](../struct.LinearMemorySpec.html) with an +/// `OwnedSparseData` for the initializer. +/// This type is useful when directly building up a value to be serialized. +pub struct OwnedLinearMemorySpec { + /// Specification of the heap used to implement the linear memory + pub heap: HeapSpec, + /// Initialization values for linear memory + pub initializer: OwnedSparseData, +} + +impl OwnedLinearMemorySpec { + pub fn to_ref<'a>(&'a self) -> LinearMemorySpec<'a> { + LinearMemorySpec { + heap: self.heap.clone(), + initializer: self.initializer.to_ref(), + } + } +} + /// Specifications about the heap of a Lucet module. #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct HeapSpec { diff --git a/lucet-module/src/module.rs b/lucet-module/src/module.rs new file mode 100644 index 000000000..238e2bcb0 --- /dev/null +++ b/lucet-module/src/module.rs @@ -0,0 +1,30 @@ +use crate::functions::FunctionSpec; +use crate::module_data::ModuleData; +use crate::tables::TableElement; +use crate::version_info::VersionInfo; + +pub const LUCET_MODULE_SYM: &str = "lucet_module"; + +/// Module is the exposed structure that contains all the data backing a Lucet-compiled object. +#[derive(Debug)] +pub struct Module<'a> { + pub version: VersionInfo, + pub module_data: ModuleData<'a>, + pub tables: &'a [&'a [TableElement]], + pub function_manifest: &'a [FunctionSpec], +} + +/// SerializedModule is a serialization-friendly form of Module, in that the `module_data_*` fields +/// here refer to a serialized `ModuleData`, while `tables_*` and `function_manifest_*` refer to +/// the actual tables and function manifest written in the binary. +#[repr(C)] +#[derive(Debug)] +pub struct SerializedModule { + pub version: VersionInfo, + pub module_data_ptr: u64, + pub module_data_len: u64, + pub tables_ptr: u64, + pub tables_len: u64, + pub function_manifest_ptr: u64, + pub function_manifest_len: u64, +} diff --git a/lucet-module/src/module_data.rs b/lucet-module/src/module_data.rs new file mode 100644 index 000000000..fa1c3f157 --- /dev/null +++ b/lucet-module/src/module_data.rs @@ -0,0 +1,285 @@ +use crate::{ + functions::{ + ExportFunction, FunctionIndex, FunctionMetadata, ImportFunction, OwnedFunctionMetadata, + }, + globals::GlobalSpec, + linear_memory::{HeapSpec, LinearMemorySpec, SparseData}, + types::Signature, + Error, +}; +use derivative::Derivative; +use minisign::SignatureBones; +use serde::{Deserialize, Serialize}; +use serde_big_array::big_array; + +pub const MODULE_DATA_SYM: &str = "lucet_module_data"; + +big_array! { + BigArray; + SignatureBones::BYTES, +} + +/// The metadata (and some data) for a Lucet module. +/// +/// The lifetime parameter exists to support zero-copy deserialization for the `&str` and `&[u8]` +/// fields at the leaves of the structure. For a variant with owned types at the leaves, see +/// [`OwnedModuleData`](owned/struct.OwnedModuleData.html). +/// +/// The goal is for this structure to eventually include everything except the code for the guest +/// functions themselves. +#[derive(Derivative, Serialize, Deserialize)] +#[derivative(Debug)] +pub struct ModuleData<'a> { + #[serde(borrow)] + linear_memory: Option>, + #[serde(borrow)] + globals_spec: Vec>, + #[serde(borrow)] + function_info: Vec>, + #[serde(borrow)] + import_functions: Vec>, + #[serde(borrow)] + export_functions: Vec>, + signatures: Vec, + #[serde(with = "BigArray")] + #[derivative(Debug = "ignore")] + module_signature: [u8; SignatureBones::BYTES], + features: ModuleFeatures, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct ModuleFeatures { + pub sse3: bool, + pub ssse3: bool, + pub sse41: bool, + pub sse42: bool, + pub avx: bool, + pub bmi1: bool, + pub bmi2: bool, + pub lzcnt: bool, + pub popcnt: bool, + _hidden: (), +} + +impl ModuleFeatures { + pub fn none() -> Self { + Self { + sse3: false, + ssse3: false, + sse41: false, + sse42: false, + avx: false, + bmi1: false, + bmi2: false, + lzcnt: false, + popcnt: false, + _hidden: (), + } + } +} + +impl<'a> ModuleData<'a> { + pub fn new( + linear_memory: Option>, + globals_spec: Vec>, + function_info: Vec>, + import_functions: Vec>, + export_functions: Vec>, + signatures: Vec, + features: ModuleFeatures, + ) -> Self { + Self { + linear_memory, + globals_spec, + function_info, + import_functions, + export_functions, + signatures, + module_signature: [0u8; SignatureBones::BYTES], + features, + } + } + + pub fn heap_spec(&self) -> Option<&HeapSpec> { + if let Some(ref linear_memory) = self.linear_memory { + Some(&linear_memory.heap) + } else { + None + } + } + + pub fn sparse_data(&self) -> Option<&SparseData<'a>> { + if let Some(ref linear_memory) = self.linear_memory { + Some(&linear_memory.initializer) + } else { + None + } + } + + pub fn globals_spec(&self) -> &[GlobalSpec<'a>] { + &self.globals_spec + } + + pub fn function_info(&self) -> &[FunctionMetadata<'a>] { + &self.function_info + } + + pub fn import_functions(&self) -> &[ImportFunction<'_>] { + &self.import_functions + } + + pub fn export_functions(&self) -> &[ExportFunction<'_>] { + &self.export_functions + } + + // Function index here is a different index space than `get_func_from_idx`, which + // uses function index as an index into a table of function elements. + // + // This is an index of all functions in the module. + pub fn get_signature(&self, fn_id: FunctionIndex) -> &Signature { + let sig_idx = self.function_info[fn_id.as_u32() as usize].signature; + &self.signatures[sig_idx.as_u32() as usize] + } + + pub fn get_export_func_id(&self, name: &str) -> Option { + self.export_functions + .iter() + .find(|export| export.names.contains(&name)) + .map(|export| export.fn_idx) + } + + pub fn signatures(&self) -> &[Signature] { + &self.signatures + } + + pub fn get_module_signature(&self) -> &[u8] { + &self.module_signature + } + + pub fn features(&self) -> &ModuleFeatures { + &self.features + } + + pub fn patch_module_signature( + module_data_bin: &'a [u8], + module_signature: &[u8], + ) -> Result, Error> { + assert_eq!(module_signature.len(), SignatureBones::BYTES); + let mut module_data = Self::deserialize(module_data_bin)?; + module_data + .module_signature + .copy_from_slice(module_signature); + let patched_module_data_bin = module_data.serialize()?; + assert_eq!(patched_module_data_bin.len(), module_data_bin.len()); + Ok(patched_module_data_bin) + } + + pub fn clear_module_signature(module_data_bin: &'a [u8]) -> Result, Error> { + let module_signature = vec![0u8; SignatureBones::BYTES]; + Self::patch_module_signature(module_data_bin, &module_signature) + } + + /// Serialize to [`bincode`](https://github.com/TyOverby/bincode). + pub fn serialize(&self) -> Result, Error> { + bincode::serialize(self).map_err(Error::SerializationError) + } + + /// Deserialize from [`bincode`](https://github.com/TyOverby/bincode). + pub fn deserialize(buf: &'a [u8]) -> Result, Error> { + bincode::deserialize(buf).map_err(Error::DeserializationError) + } +} + +use crate::{ + functions::{OwnedExportFunction, OwnedImportFunction}, + globals::OwnedGlobalSpec, + linear_memory::{OwnedLinearMemorySpec, OwnedSparseData}, +}; + +/// The metadata (and some data) for a Lucet module. +/// +/// This is a version of [`ModuleData`](../struct.ModuleData.html) with owned types throughout, +/// rather than references to support zero-copy deserialization. This type is useful when directly +/// building up a value to be serialized. +pub struct OwnedModuleData { + linear_memory: Option, + globals_spec: Vec, + function_info: Vec, + imports: Vec, + exports: Vec, + signatures: Vec, + features: ModuleFeatures, +} + +impl OwnedModuleData { + pub fn new( + linear_memory: Option, + globals_spec: Vec, + function_info: Vec, + imports: Vec, + exports: Vec, + signatures: Vec, + features: ModuleFeatures, + ) -> Self { + Self { + linear_memory, + globals_spec, + function_info, + imports, + exports, + signatures, + features, + } + } + + /// Create a [`ModuleData`](../struct.ModuleData.html) backed by the values in this + /// `OwnedModuleData`. + pub fn to_ref<'a>(&'a self) -> ModuleData<'a> { + ModuleData::new( + if let Some(ref owned_linear_memory) = self.linear_memory { + Some(owned_linear_memory.to_ref()) + } else { + None + }, + self.globals_spec.iter().map(|gs| gs.to_ref()).collect(), + self.function_info + .iter() + .map(|info| info.to_ref()) + .collect(), + self.imports.iter().map(|imp| imp.to_ref()).collect(), + self.exports.iter().map(|exp| exp.to_ref()).collect(), + self.signatures.clone(), + self.features.clone(), + ) + } + + pub fn empty() -> Self { + Self::new( + None, + vec![], + vec![], + vec![], + vec![], + vec![], + ModuleFeatures::none(), + ) + } + + pub fn with_heap_spec(mut self, heap_spec: HeapSpec) -> Self { + if let Some(ref mut linear_memory) = self.linear_memory { + linear_memory.heap = heap_spec; + } else { + self.linear_memory = Some(OwnedLinearMemorySpec { + heap: heap_spec, + initializer: OwnedSparseData::new(vec![]).unwrap(), + }); + } + self + } +} + +impl Default for OwnedModuleData { + fn default() -> Self { + OwnedModuleData::empty() + } +} diff --git a/lucet-module/src/runtime.rs b/lucet-module/src/runtime.rs new file mode 100644 index 000000000..864344dc9 --- /dev/null +++ b/lucet-module/src/runtime.rs @@ -0,0 +1,8 @@ +/// This struct describes the handful of fields that Lucet-compiled programs may directly interact with, but +/// are provided through VMContext. +#[repr(C)] +#[repr(align(8))] +pub struct InstanceRuntimeData { + pub globals_ptr: *mut i64, + pub instruction_count: u64, +} diff --git a/lucet-module/src/signature.rs b/lucet-module/src/signature.rs new file mode 100644 index 000000000..cb1af504f --- /dev/null +++ b/lucet-module/src/signature.rs @@ -0,0 +1,197 @@ +use crate::error::Error::{self, IOError, ModuleSignatureError}; +use crate::module::{SerializedModule, LUCET_MODULE_SYM}; +use crate::module_data::MODULE_DATA_SYM; +use crate::ModuleData; +use byteorder::{ByteOrder, LittleEndian}; +use memoffset::offset_of; +pub use minisign::{PublicKey, SecretKey}; +use minisign::{SignatureBones, SignatureBox}; +use object::*; +use std::fs::{File, OpenOptions}; +use std::io::{self, Cursor, Read, Seek, SeekFrom, Write}; +use std::path::Path; + +pub struct ModuleSignature; + +impl ModuleSignature { + pub fn verify>( + so_path: P, + pk: &PublicKey, + module_data: &ModuleData, + ) -> Result<(), Error> { + let signature_box: SignatureBox = + SignatureBones::from_bytes(&module_data.get_module_signature()) + .map_err(|e| ModuleSignatureError(e))? + .into(); + + let mut raw_module_and_data = + RawModuleAndData::from_file(&so_path).map_err(|e| IOError(e))?; + let cleared_module_data_bin = + ModuleData::clear_module_signature(raw_module_and_data.module_data_bin())?; + raw_module_and_data.patch_module_data(&cleared_module_data_bin); + + minisign::verify( + &pk, + &signature_box, + Cursor::new(&raw_module_and_data.obj_bin), + true, + false, + ) + .map_err(|e| ModuleSignatureError(e)) + } + + pub fn sign>(path: P, sk: &SecretKey) -> Result<(), Error> { + let raw_module_and_data = RawModuleAndData::from_file(&path).map_err(|e| IOError(e))?; + let signature_box = minisign::sign( + None, + sk, + Cursor::new(&raw_module_and_data.obj_bin), + true, + None, + None, + ) + .map_err(|e| ModuleSignatureError(e))?; + let signature_bones: SignatureBones = signature_box.into(); + let patched_module_data_bin = ModuleData::patch_module_signature( + raw_module_and_data.module_data_bin(), + &signature_bones.to_bytes(), + )?; + raw_module_and_data + .write_patched_module_data(&path, &patched_module_data_bin) + .map_err(|e| IOError(e))?; + Ok(()) + } +} + +#[allow(dead_code)] +struct SymbolData { + offset: usize, + len: usize, +} + +struct RawModuleAndData { + pub obj_bin: Vec, + pub module_data_offset: usize, + pub module_data_len: usize, +} + +impl RawModuleAndData { + pub fn from_file>(path: P) -> Result { + let mut obj_bin: Vec = Vec::new(); + File::open(&path)?.read_to_end(&mut obj_bin)?; + + let native_data_symbol_data = + Self::symbol_data(&obj_bin, LUCET_MODULE_SYM, true)?.ok_or(io::Error::new( + io::ErrorKind::InvalidInput, + format!("`{}` symbol not present", LUCET_MODULE_SYM), + ))?; + + // While `module_data` is the first field of the `SerializedModule` that `lucet_module` points + // to, it is a virtual address, not a file offset. The translation is somewhat tricky at + // the moment, so just look at the corresponding `lucet_module_data` symbol for now. + let module_data_symbol_data = + Self::symbol_data(&obj_bin, MODULE_DATA_SYM, true)?.ok_or(io::Error::new( + io::ErrorKind::InvalidInput, + format!("`{}` symbol not present", MODULE_DATA_SYM), + ))?; + + let module_data_len = LittleEndian::read_u64( + &obj_bin[(native_data_symbol_data.offset + + offset_of!(SerializedModule, module_data_len))..], + ) as usize; + + Ok(RawModuleAndData { + obj_bin, + module_data_offset: module_data_symbol_data.offset, + module_data_len: module_data_len, + }) + } + + pub fn module_data_bin(&self) -> &[u8] { + &self.obj_bin[self.module_data_offset as usize + ..self.module_data_offset as usize + self.module_data_len] + } + + pub fn module_data_bin_mut(&mut self) -> &mut [u8] { + &mut self.obj_bin[self.module_data_offset as usize + ..self.module_data_offset as usize + self.module_data_len] + } + + pub fn patch_module_data(&mut self, module_data_bin: &[u8]) { + self.module_data_bin_mut().copy_from_slice(&module_data_bin); + } + + pub fn write_patched_module_data>( + &self, + path: P, + patched_module_data_bin: &[u8], + ) -> Result<(), io::Error> { + let mut fp = OpenOptions::new() + .write(true) + .create_new(false) + .open(&path)?; + fp.seek(SeekFrom::Start(self.module_data_offset as u64))?; + fp.write_all(&patched_module_data_bin)?; + Ok(()) + } + + // Retrieving the offset of a symbol is not supported by the object crate. + // In Mach-O, actual file offsets are encoded, whereas Elf encodes virtual + // addresses, requiring extra steps to retrieve the section, its base + // address as well as the section offset. + + // Elf + #[cfg(all(target_family = "unix", not(target_os = "macos")))] + fn symbol_data( + obj_bin: &[u8], + symbol_name: &str, + _mangle: bool, + ) -> Result, io::Error> { + let obj = object::ElfFile::parse(obj_bin) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?; + let symbol_map = obj.symbol_map(); + for symbol in symbol_map + .symbols() + .iter() + .filter(|sym| sym.kind() == SymbolKind::Data) + .filter(|sym| sym.name() == Some(symbol_name)) + { + if let Some(section_index) = symbol.section_index() { + let section = &obj.elf().section_headers[section_index.0]; + let offset = (symbol.address() - section.sh_addr + section.sh_offset) as usize; + let len = symbol.size() as usize; + return Ok(Some(SymbolData { offset, len })); + } + } + Ok(None) + } + + // Mach-O + #[cfg(target_os = "macos")] + fn symbol_data( + obj_bin: &[u8], + symbol_name: &str, + mangle: bool, + ) -> Result, io::Error> { + let obj = object::File::parse(obj_bin) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?; + let symbol_map = obj.symbol_map(); + let mangled_symbol_name = format!("_{}", symbol_name); + let symbol_name = if mangle { + &mangled_symbol_name + } else { + symbol_name + }; + if let Some(symbol) = symbol_map + .symbols() + .iter() + .filter(|sym| sym.kind() == SymbolKind::Data || sym.kind() == SymbolKind::Unknown) + .find(|sym| sym.name() == Some(symbol_name)) + { + let offset = symbol.address() as usize; + let len = symbol.size() as usize; + return Ok(Some(SymbolData { offset, len })); + } + Ok(None) + } +} diff --git a/lucet-module/src/tables.rs b/lucet-module/src/tables.rs new file mode 100644 index 000000000..b8ecb0d0f --- /dev/null +++ b/lucet-module/src/tables.rs @@ -0,0 +1,14 @@ +use crate::functions::FunctionPointer; + +#[repr(C)] +#[derive(Clone, Debug)] +pub struct TableElement { + ty: u64, + func: u64, +} + +impl TableElement { + pub fn function_pointer(&self) -> FunctionPointer { + FunctionPointer::from_usize(self.func as usize) + } +} diff --git a/lucet-module/src/traps.rs b/lucet-module/src/traps.rs new file mode 100644 index 000000000..28f01c60e --- /dev/null +++ b/lucet-module/src/traps.rs @@ -0,0 +1,63 @@ +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; + +/// The type of a WebAssembly +/// [trap](http://webassembly.github.io/spec/core/intro/overview.html#trap). +#[repr(u32)] +#[derive(Copy, Clone, Debug, FromPrimitive, PartialEq)] +pub enum TrapCode { + StackOverflow = 0, + HeapOutOfBounds = 1, + OutOfBounds = 2, + IndirectCallToNull = 3, + BadSignature = 4, + IntegerOverflow = 5, + IntegerDivByZero = 6, + BadConversionToInteger = 7, + Interrupt = 8, + TableOutOfBounds = 9, + Unreachable = 10, +} + +impl TrapCode { + pub fn try_from_u32(v: u32) -> Option { + Self::from_u32(v) + } +} + +/// Trap information for an address in a compiled function +/// +/// To support zero-copy deserialization of trap tables, this +/// must be repr(C) [to avoid cases where Rust may change the +/// layout in some future version, mangling the interpretation +/// of an old TrapSite struct] +#[repr(C)] +#[derive(Clone, Debug)] +pub struct TrapSite { + pub offset: u32, + pub code: TrapCode, +} + +/// A collection of trap sites, typically obtained from a +/// single function (see [`FunctionSpec::traps`]) +#[repr(C)] +#[derive(Clone, Debug)] +pub struct TrapManifest<'a> { + pub traps: &'a [TrapSite], +} + +impl<'a> TrapManifest<'a> { + pub fn new(traps: &'a [TrapSite]) -> TrapManifest<'_> { + TrapManifest { traps } + } + pub fn lookup_addr(&self, addr: u32) -> Option { + // predicate to find the trapsite for the addr via binary search + let f = |ts: &TrapSite| ts.offset.cmp(&addr); + + if let Ok(i) = self.traps.binary_search_by(f) { + Some(self.traps[i].code) + } else { + None + } + } +} diff --git a/lucet-module/src/types.rs b/lucet-module/src/types.rs new file mode 100644 index 000000000..e2f2fe681 --- /dev/null +++ b/lucet-module/src/types.rs @@ -0,0 +1,72 @@ +use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; + +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub enum ValueType { + I32, + I64, + F32, + F64, +} + +impl Display for ValueType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ValueType::I32 => write!(f, "I32"), + ValueType::I64 => write!(f, "I64"), + ValueType::F32 => write!(f, "F32"), + ValueType::F64 => write!(f, "F64"), + } + } +} + +/// A signature for a function in a wasm module. +/// +/// Note that this does not explicitly name VMContext as a parameter! It is assumed that all wasm +/// functions take VMContext as their first parameter. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct Signature { + pub params: Vec, + // In the future, wasm may permit this to be a Vec of ValueType + pub ret_ty: Option, +} + +impl Display for Signature { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "(")?; + for (i, p) in self.params.iter().enumerate() { + if i == 0 { + write!(f, "{}", p)?; + } else { + write!(f, ", {}", p)?; + } + } + write!(f, ") -> ")?; + match self.ret_ty { + Some(ty) => write!(f, "{}", ty), + None => write!(f, "()"), + } + } +} + +#[macro_export] +macro_rules! lucet_signature { + ((() -> ())) => { + $crate::Signature { + params: vec![], + ret_ty: None + } + }; + (($($arg_ty:ident),*) -> ()) => { + $crate::Signature { + params: vec![$($crate::ValueType::$arg_ty),*], + ret_ty: None, + } + }; + (($($arg_ty:ident),*) -> $ret_ty:ident) => { + $crate::Signature { + params: vec![$($crate::ValueType::$arg_ty),*], + ret_ty: Some($crate::ValueType::$ret_ty), + } + }; +} diff --git a/lucet-module/src/version_info.rs b/lucet-module/src/version_info.rs new file mode 100644 index 000000000..94ec4112b --- /dev/null +++ b/lucet-module/src/version_info.rs @@ -0,0 +1,128 @@ +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use std::cmp::min; +use std::fmt; +use std::io; + +/// VersionInfo is information about a Lucet module to allow the Lucet runtime to determine if or +/// how the module can be loaded, if so requested. The information here describes implementation +/// details in runtime support for `lucetc`-produced modules, and nothing higher level. +#[repr(C)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VersionInfo { + major: u16, + minor: u16, + patch: u16, + reserved: u16, + /// `version_hash` is either all nulls or the first eight ascii characters of the git commit + /// hash of wherever this Version is coming from. In the case of a compiled lucet module, this + /// hash will come from the git commit that the lucetc producing it came from. In a runtime + /// context, it will be the git commit of lucet-runtime built into the embedder. + /// + /// The version hash will typically populated only in release builds, but may blank even in + /// that case: if building from a packaged crate, or in a build environment that does not have + /// "git" installed, `lucetc` and `lucet-runtime` will fall back to an empty hash. + version_hash: [u8; 8], +} + +impl fmt::Display for VersionInfo { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "{}.{}.{}", self.major, self.minor, self.patch)?; + if u64::from_ne_bytes(self.version_hash) != 0 { + write!( + fmt, + "-{}", + std::str::from_utf8(&self.version_hash).unwrap_or("INVALID") + )?; + } + Ok(()) + } +} + +impl VersionInfo { + pub fn new(major: u16, minor: u16, patch: u16, version_hash: [u8; 8]) -> VersionInfo { + VersionInfo { + major, + minor, + patch, + reserved: 0x8000, + version_hash, + } + } + + /// A more permissive version check than for version equality. This check will allow an `other` + /// version that is more specific than `self`, but matches for data that is available. + pub fn compatible_with(&self, other: &VersionInfo) -> bool { + if !(self.valid() || other.valid()) { + return false; + } + + if self.major == other.major && self.minor == other.minor && self.patch == other.patch { + if self.version_hash == [0u8; 8] { + // we aren't bound to a specific git commit, so anything goes. + true + } else { + self.version_hash == other.version_hash + } + } else { + false + } + } + + pub fn write_to(&self, w: &mut W) -> io::Result<()> { + w.write_u16::(self.major)?; + w.write_u16::(self.minor)?; + w.write_u16::(self.patch)?; + w.write_u16::(self.reserved)?; + w.write(&self.version_hash).and_then(|written| { + if written != self.version_hash.len() { + Err(io::Error::new( + io::ErrorKind::Other, + "unable to write full version hash", + )) + } else { + Ok(()) + } + }) + } + + pub fn read_from(r: &mut R) -> io::Result { + let mut version_hash = [0u8; 8]; + Ok(VersionInfo { + major: r.read_u16::()?, + minor: r.read_u16::()?, + patch: r.read_u16::()?, + reserved: r.read_u16::()?, + version_hash: { + r.read_exact(&mut version_hash)?; + version_hash + }, + }) + } + + pub fn valid(&self) -> bool { + self.reserved == 0x8000 + } + + pub fn current(current_hash: &'static [u8]) -> Self { + let mut version_hash = [0u8; 8]; + + for i in 0..min(version_hash.len(), current_hash.len()) { + version_hash[i] = current_hash[i]; + } + + // The reasoning for this is as follows: + // `SerializedModule`, in version before version information was introduced, began with a + // pointer - `module_data_ptr`. This pointer would be relocated to somewhere in user space + // for the embedder of `lucet-runtime`. On x86_64, hopefully, that's userland code in some + // OS, meaning the pointer will be a pointer to user memory, and will be below + // 0x8000_0000_0000_0000. By setting `reserved` to `0x8000`, we set what would be the + // highest bit in `module_data_ptr` in an old `lucet-runtime` and guarantee a segmentation + // fault when loading these newer modules with version information. + VersionInfo::new( + env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(), + env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(), + env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(), + version_hash, + ) + } +} diff --git a/lucet-module/tests/bindings.rs b/lucet-module/tests/bindings.rs new file mode 100644 index 000000000..58ea6da61 --- /dev/null +++ b/lucet-module/tests/bindings.rs @@ -0,0 +1,54 @@ +use lucet_module::bindings::Bindings; +use std::collections::HashMap; +use std::path::PathBuf; + +fn test_file(f: &str) -> PathBuf { + PathBuf::from(format!("tests/bindings/{}", f)) +} + +#[test] +fn explicit() { + let mut explicit_map = HashMap::new(); + explicit_map.insert(String::from("hello"), String::from("goodbye")); + let map = Bindings::env(explicit_map); + + let result = map.translate("env", "hello").unwrap(); + assert!(result == "goodbye"); + + let result = map.translate("env", "nonexistent"); + assert!( + result.is_err(), + "explicit import map returned value for non-existent symbol" + ); +} + +#[test] +fn explicit_from_nonexistent_file() { + let fail_map = Bindings::from_file(&test_file("nonexistent_bindings.json")); + assert!( + fail_map.is_err(), + "ImportMap::explicit_from_file did not fail on a non-existent file" + ); +} + +#[test] +fn explicit_from_garbage_file() { + let fail_map = Bindings::from_file(&test_file("garbage.json")); + assert!( + fail_map.is_err(), + "ImportMap::explicit_from_file did not fail on a garbage file" + ); +} + +#[test] +fn explicit_from_file() { + let map = Bindings::from_file(&test_file("bindings_test.json")) + .expect("load valid bindings from file"); + let result = map.translate("env", "hello").expect("hello has a binding"); + assert!(result == "json is cool"); + + assert!( + map.translate("env", "nonexistent").is_err(), + "bindings from file returned value for non-existent symbol" + ); +} diff --git a/lucetc/tests/bindings/bad_bindings.json b/lucet-module/tests/bindings/bad_bindings.json similarity index 100% rename from lucetc/tests/bindings/bad_bindings.json rename to lucet-module/tests/bindings/bad_bindings.json diff --git a/lucetc/tests/bindings/bindings_test.json b/lucet-module/tests/bindings/bindings_test.json similarity index 100% rename from lucetc/tests/bindings/bindings_test.json rename to lucet-module/tests/bindings/bindings_test.json diff --git a/lucetc/tests/bindings/garbage.json b/lucet-module/tests/bindings/garbage.json similarity index 100% rename from lucetc/tests/bindings/garbage.json rename to lucet-module/tests/bindings/garbage.json diff --git a/lucet-module/tests/version.rs b/lucet-module/tests/version.rs new file mode 100644 index 000000000..3480c74c3 --- /dev/null +++ b/lucet-module/tests/version.rs @@ -0,0 +1,18 @@ +use lucet_module::VersionInfo; + +#[test] +fn version_equality() { + let precise = VersionInfo::new(0, 1, 2, [0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61]); + + let imprecise = VersionInfo::new(0, 1, 2, [0, 0, 0, 0, 0, 0, 0, 0]); + + // first, these are two different versions. + assert_ne!(precise, imprecise); + + // something running a version only as detailed as `major.minor.patch` can run a matching + // version that may include a commit hash + assert!(imprecise.compatible_with(&precise)); + + // something running a version `major.minor.patch-commit` rejects a version less specific + assert!(!precise.compatible_with(&imprecise)); +} diff --git a/lucet-analyze/.gitignore b/lucet-objdump/.gitignore similarity index 100% rename from lucet-analyze/.gitignore rename to lucet-objdump/.gitignore diff --git a/lucet-objdump/Cargo.toml b/lucet-objdump/Cargo.toml new file mode 100644 index 000000000..90a02d826 --- /dev/null +++ b/lucet-objdump/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "lucet-objdump" +version = "0.5.0" +description = "Analyze object files emitted by the Lucet compiler" +homepage = "https://github.com/fastly/lucet" +repository = "https://github.com/fastly/lucet" +license = "Apache-2.0 WITH LLVM-exception" +categories = ["wasm"] +authors = ["Lucet team "] +edition = "2018" + +[dependencies] +goblin="0.0.24" +byteorder="1.2.1" +colored="1.8.0" +lucet-module = { path = "../lucet-module", version = "=0.5.0" } + +[package.metadata.deb] +name = "fst-lucet-objdump" +maintainer = "Lucet team " +depends = "$auto" +priority = "optional" +assets = [ + ["target/release/lucet-objdump", "/opt/fst-lucet-objdump/bin/lucet-objdump", "755"], + ["LICENSE", "/opt/fst-lucet-objdump/share/doc/lucet-objdump/", "644"], +] diff --git a/lucet-analyze/LICENSE b/lucet-objdump/LICENSE similarity index 100% rename from lucet-analyze/LICENSE rename to lucet-objdump/LICENSE diff --git a/lucet-objdump/src/main.rs b/lucet-objdump/src/main.rs new file mode 100644 index 000000000..58350c136 --- /dev/null +++ b/lucet-objdump/src/main.rs @@ -0,0 +1,557 @@ +#![deny(bare_trait_objects)] + +use lucet_module::{ + FunctionSpec, Module, ModuleData, SerializedModule, TableElement, TrapManifest, TrapSite, + VersionInfo, +}; + +use byteorder::{LittleEndian, ReadBytesExt}; +use colored::Colorize; +use goblin::{elf, Object}; +use std::env; +use std::fs::File; +use std::io::Cursor; +use std::io::Read; +use std::mem; + +#[derive(Debug)] +struct ArtifactSummary<'a> { + buffer: &'a Vec, + elf: &'a elf::Elf<'a>, + symbols: StandardSymbols, + data_segments: Option, + serialized_module: Option, + exported_functions: Vec<&'a str>, + imported_symbols: Vec<&'a str>, +} + +#[derive(Debug)] +struct StandardSymbols { + lucet_module: Option, +} + +#[derive(Debug)] +struct DataSegments { + segments: Vec, +} + +#[derive(Debug)] +struct DataSegment { + offset: u32, + len: u32, + data: Vec, +} + +impl<'a> ArtifactSummary<'a> { + fn new(buffer: &'a Vec, elf: &'a elf::Elf<'_>) -> Self { + Self { + buffer: buffer, + elf: elf, + symbols: StandardSymbols { lucet_module: None }, + data_segments: None, + serialized_module: None, + exported_functions: Vec::new(), + imported_symbols: Vec::new(), + } + } + + fn read_memory(&self, addr: u64, size: u64) -> Option<&'a [u8]> { + for header in &self.elf.program_headers { + if header.p_type == elf::program_header::PT_LOAD { + // Bounds check the entry + if addr >= header.p_vaddr && (addr + size) <= (header.p_vaddr + header.p_memsz) { + let start = (addr - header.p_vaddr + header.p_offset) as usize; + let end = start + size as usize; + + return Some(&self.buffer[start..end]); + } + } + } + + None + } + + fn gather(&mut self) { + for ref sym in self.elf.syms.iter() { + let name = self + .elf + .strtab + .get(sym.st_name) + .unwrap_or(Ok("(no name)")) + .expect("strtab entry"); + + match name { + "lucet_module" => self.symbols.lucet_module = Some(sym.clone()), + _ => { + if sym.st_bind() == elf::sym::STB_GLOBAL { + if sym.is_function() { + self.exported_functions.push(name.clone()); + } else if sym.st_shndx == elf::section_header::SHN_UNDEF as usize { + self.imported_symbols.push(name.clone()); + } + } + } + } + } + + self.serialized_module = self.symbols.lucet_module.as_ref().map(|module_sym| { + let buffer = self + .read_memory( + module_sym.st_value, + mem::size_of::() as u64, + ) + .unwrap(); + let mut rdr = Cursor::new(buffer); + + let version = VersionInfo::read_from(&mut rdr).unwrap(); + + SerializedModule { + version, + module_data_ptr: rdr.read_u64::().unwrap(), + module_data_len: rdr.read_u64::().unwrap(), + tables_ptr: rdr.read_u64::().unwrap(), + tables_len: rdr.read_u64::().unwrap(), + function_manifest_ptr: rdr.read_u64::().unwrap(), + function_manifest_len: rdr.read_u64::().unwrap(), + } + }); + } + + fn get_func_name_for_addr(&self, addr: u64) -> Option<&str> { + for ref sym in self.elf.syms.iter() { + if sym.is_function() && sym.st_value == addr { + let name = self + .elf + .strtab + .get(sym.st_name) + .unwrap_or(Ok("(no name)")) + .expect("strtab entry"); + + return Some(name); + } + } + None + } +} + +fn main() { + let path = env::args().nth(1).unwrap(); + let mut fd = File::open(path).expect("open"); + let mut buffer = Vec::new(); + fd.read_to_end(&mut buffer).expect("read"); + let object = Object::parse(&buffer).expect("parse"); + + if let Object::Elf(eo) = object { + let mut summary = ArtifactSummary::new(&buffer, &eo); + summary.gather(); + print_summary(summary); + } else { + println!("Expected Elf!"); + } +} + +/// Parse a trap manifest for function `f`, if it has one. +/// +/// `parse_trap_manifest` may very understandably be confusing. Why not use `f.traps()`? In +/// `lucet-objdump` the module has been accessed by reading the file and following structures as +/// they exist at rest. This means pointers are not relocated, so slices that would be valid when +/// loaded through the platform's loader currently have pointers that are not valid for memory +/// access. +/// +/// In particular, trap pointers are correct with respect to 0 being the start of the file (or, +/// buffer, after reading), which means we can (and must) rebuild a correct slice from the buffer. +fn parse_trap_manifest<'a>( + summary: &'a ArtifactSummary<'a>, + f: &FunctionSpec, +) -> Option> { + if let Some(faulty_trap_manifest) = f.traps() { + let trap_ptr = faulty_trap_manifest.traps.as_ptr(); + let traps_count = faulty_trap_manifest.traps.len(); + let traps_byte_count = traps_count * std::mem::size_of::>(); + if let Some(traps_byte_slice) = + summary.read_memory(trap_ptr as u64, traps_byte_count as u64) + { + let real_trap_ptr = traps_byte_slice.as_ptr() as *const TrapSite; + Some(TrapManifest { + traps: unsafe { std::slice::from_raw_parts(real_trap_ptr, traps_count) }, + }) + } else { + println!( + "Failed to read trap bytes for function {:?}, at {:p}", + f, trap_ptr + ); + None + } + } else { + None + } +} + +fn load_module<'b, 'a: 'b>( + summary: &'a ArtifactSummary<'a>, + serialized_module: &SerializedModule, + tables: &'b [&[TableElement]], +) -> Module<'b> { + let module_data_bytes = summary + .read_memory( + serialized_module.module_data_ptr, + serialized_module.module_data_len, + ) + .unwrap(); + + let module_data = + ModuleData::deserialize(module_data_bytes).expect("ModuleData can be deserialized"); + + let function_manifest_bytes = summary + .read_memory( + serialized_module.function_manifest_ptr, + serialized_module.function_manifest_len, + ) + .unwrap(); + let function_manifest = unsafe { + std::slice::from_raw_parts( + function_manifest_bytes.as_ptr() as *const FunctionSpec, + serialized_module.function_manifest_len as usize, + ) + }; + Module { + version: serialized_module.version.clone(), + module_data, + tables, + function_manifest, + } +} + +fn summarize_module<'a, 'b: 'a>(summary: &'a ArtifactSummary<'a>, module: &Module<'b>) { + let module_data = &module.module_data; + let tables = module.tables; + let function_manifest = module.function_manifest; + + println!(" Heap Specification:"); + if let Some(heap_spec) = module_data.heap_spec() { + println!(" {:9}: {} bytes", "Reserved", heap_spec.reserved_size); + println!(" {:9}: {} bytes", "Guard", heap_spec.guard_size); + println!(" {:9}: {} bytes", "Initial", heap_spec.initial_size); + if let Some(max_size) = heap_spec.max_size { + println!(" {:9}: {} bytes", "Maximum", max_size); + } else { + println!(" {:9}: None", "Maximum"); + } + } else { + println!(" {}", "MISSING".red().bold()); + } + + println!(""); + println!(" Sparse Page Data:"); + if let Some(sparse_page_data) = module_data.sparse_data() { + println!(" {:6}: {}", "Count", sparse_page_data.pages().len()); + let mut allempty = true; + let mut anyempty = false; + for (i, page) in sparse_page_data.pages().iter().enumerate() { + match page { + Some(page) => { + allempty = false; + println!( + " Page[{}]: {:p}, size: {}", + i, + page.as_ptr(), + if page.len() != 4096 { + format!( + "{} (page size, expected 4096)", + format!("{}", page.len()).bold().red() + ) + .red() + } else { + format!("{}", page.len()).green() + } + ); + } + None => { + anyempty = true; + } + }; + } + if allempty && sparse_page_data.pages().len() > 0 { + println!(" (all pages empty)"); + } else if anyempty { + println!(" (empty pages omitted)"); + } + } else { + println!(" {}", "MISSING!".red().bold()); + } + + println!(""); + println!("Tables:"); + if tables.len() == 0 { + println!(" No tables."); + } else { + for (i, table) in tables.iter().enumerate() { + println!(" Table {}: {:?}", i, table); + } + } + + println!(""); + println!("Signatures:"); + for (i, s) in module_data.signatures().iter().enumerate() { + println!(" Signature {}: {}", i, s); + } + + println!(""); + println!("Functions:"); + if function_manifest.len() != module_data.function_info().len() { + println!( + " {} function manifest and function info have diverging function counts", + "lucetc bug:".red().bold() + ); + println!( + " function_manifest length : {}", + function_manifest.len() + ); + println!( + " module data function count : {}", + module_data.function_info().len() + ); + println!(" Will attempt to display information about functions anyway, but trap/code information may be misaligned with symbols and signatures."); + } + + for (i, f) in function_manifest.iter().enumerate() { + let header_name = summary.get_func_name_for_addr(f.ptr().as_usize() as u64); + + if i >= module_data.function_info().len() { + // This is one form of the above-mentioned bug case + // Half the function information is missing, so just report the issue and continue. + println!( + " Function {} {}", + i, + "is missing the module data part of its declaration".red() + ); + match header_name { + Some(name) => { + println!(" ELF header name: {}", name); + } + None => { + println!(" No corresponding ELF symbol."); + } + }; + break; + } + + let colorize_name = |x: Option<&str>| match x { + Some(name) => name.green(), + None => "None".red().bold(), + }; + + let fn_meta = &module_data.function_info()[i]; + println!(" Function {} (name: {}):", i, colorize_name(fn_meta.name)); + if fn_meta.name != header_name { + println!( + " Name {} with name declared in ELF headers: {}", + "DISAGREES".red().bold(), + colorize_name(header_name) + ); + } + + println!( + " Signature (index {}): {}", + fn_meta.signature.as_u32() as usize, + module_data.signatures()[fn_meta.signature.as_u32() as usize] + ); + + println!(" Start: {:#010x}", f.ptr().as_usize()); + println!(" Code length: {} bytes", f.code_len()); + if let Some(trap_manifest) = parse_trap_manifest(&summary, f) { + let trap_count = trap_manifest.traps.len(); + + println!(" Trap information:"); + if trap_count > 0 { + println!( + " {} {} ...", + trap_manifest.traps.len(), + if trap_count == 1 { "trap" } else { "traps" }, + ); + for trap in trap_manifest.traps { + println!(" $+{:#06x}: {:?}", trap.offset, trap.code); + } + } else { + println!(" No traps for this function"); + } + } + } + + println!(""); + println!("Globals:"); + if module_data.globals_spec().len() > 0 { + for global_spec in module_data.globals_spec().iter() { + println!(" {:?}", global_spec.global()); + for name in global_spec.export_names() { + println!(" Exported as: {}", name); + } + } + } else { + println!(" None"); + } + + println!(""); + println!("Exported Functions/Symbols:"); + let mut exported_symbols = summary.exported_functions.clone(); + for export in module_data.export_functions() { + match module_data.function_info()[export.fn_idx.as_u32() as usize].name { + Some(name) => { + println!(" Internal name: {}", name); + + // The "internal name" is probably the first exported name for this function. + // Remove it from the exported_symbols list to not double-count + if let Some(idx) = exported_symbols.iter().position(|x| *x == name) { + exported_symbols.remove(idx); + } + } + None => { + println!(" No internal name"); + } + } + + // Export names do not have the guest_func_ prefix that symbol names get, and as such do + // not need to be removed from `exported_symbols` (which is built entirely from + // ELF-declared exports, with namespaced names) + println!(" Exported as: {}", export.names.join(", ")); + } + + if exported_symbols.len() > 0 { + println!(""); + println!(" Other exported symbols (from ELF headers):"); + for export in exported_symbols { + println!(" {}", export); + } + } + + println!(""); + println!("Imported Functions/Symbols:"); + let mut imported_symbols = summary.imported_symbols.clone(); + for import in module_data.import_functions() { + match module_data.function_info()[import.fn_idx.as_u32() as usize].name { + Some(name) => { + println!(" Internal name: {}", name); + } + None => { + println!(" No internal name"); + } + } + println!(" Imported as: {}/{}", import.module, import.name); + + // Remove from the imported_symbols list to not double-count imported functions + if let Some(idx) = imported_symbols.iter().position(|x| x == &import.name) { + imported_symbols.remove(idx); + } + } + + if imported_symbols.len() > 0 { + println!(""); + println!(" Other imported symbols (from ELF headers):"); + for import in &imported_symbols { + println!(" {}", import); + } + } +} + +fn print_summary(summary: ArtifactSummary<'_>) { + println!("Required Symbols:"); + println!( + " {:30}: {}", + "lucet_module", + exists_to_str(&summary.symbols.lucet_module) + ); + if let Some(ref serialized_module) = summary.serialized_module { + println!("Native module components:"); + println!( + " {:30}: {}", + "module_data_ptr", + ptr_to_str(serialized_module.module_data_ptr) + ); + println!( + " {:30}: {}", + "module_data_len", serialized_module.module_data_len + ); + println!( + " {:30}: {}", + "tables_ptr", + ptr_to_str(serialized_module.tables_ptr) + ); + println!(" {:30}: {}", "tables_len", serialized_module.tables_len); + println!( + " {:30}: {}", + "function_manifest_ptr", + ptr_to_str(serialized_module.function_manifest_ptr) + ); + println!( + " {:30}: {}", + "function_manifest_len", serialized_module.function_manifest_len + ); + + let tables_bytes = summary + .read_memory( + serialized_module.tables_ptr, + serialized_module.tables_len * mem::size_of::<&[TableElement]>() as u64, + ) + .unwrap(); + let tables = unsafe { + std::slice::from_raw_parts( + tables_bytes.as_ptr() as *const &[TableElement], + serialized_module.tables_len as usize, + ) + }; + let mut reconstructed_tables = Vec::new(); + // same situation as trap tables - these slices are valid as if the module was + // dlopen'd, but we just read it as a flat file. So read through the ELF view and use + // pointers to that for the real slices. + + for table in tables { + let table_bytes = summary + .read_memory( + table.as_ptr() as usize as u64, + (table.len() * mem::size_of::()) as u64, + ) + .unwrap(); + reconstructed_tables.push(unsafe { + std::slice::from_raw_parts( + table_bytes.as_ptr() as *const TableElement, + table.len() as usize, + ) + }); + } + + let module = load_module(&summary, serialized_module, &reconstructed_tables); + println!("\nModule:"); + summarize_module(&summary, &module); + } else { + println!("The symbol `lucet_module` is {}, so lucet-objdump cannot look at most of the interesting parts.", "MISSING".red().bold()); + } + + println!(""); + println!("Data Segments:"); + if let Some(data_segments) = summary.data_segments { + println!(" {:6}: {}", "Count", data_segments.segments.len()); + for segment in &data_segments.segments { + println!( + " {:7}: {:6} {:6}: {:6}", + "Offset", segment.offset, "Length", segment.len, + ); + } + } else { + println!(" {}", "MISSING!".red().bold()); + } +} + +fn ptr_to_str(p: u64) -> colored::ColoredString { + if p != 0 { + format!("exists; address: {:#x}", p).green() + } else { + "MISSING!".red().bold() + } +} + +fn exists_to_str(p: &Option) -> colored::ColoredString { + return match p { + Some(_) => "exists".green(), + None => "MISSING!".red().bold(), + }; +} diff --git a/lucet-runtime/Cargo.toml b/lucet-runtime/Cargo.toml index 0197eeebc..87a0f0d64 100644 --- a/lucet-runtime/Cargo.toml +++ b/lucet-runtime/Cargo.toml @@ -1,31 +1,42 @@ [package] name = "lucet-runtime" -version = "0.1.0" -description = "Pure Rust runtime for lucet WebAssembly toolchain" +version = "0.5.0" +description = "Pure Rust runtime for Lucet WebAssembly toolchain" +homepage = "https://github.com/fastly/lucet" repository = "https://github.com/fastly/lucet" -authors = ["Adam C. Foltzer ", "Pat Hickey ", "Frank Denis ", "Tyler McMullen "] license = "Apache-2.0 WITH LLVM-exception" +categories = ["wasm"] +authors = ["Lucet team "] edition = "2018" [dependencies] -libc = "0.2.48" -lucet-runtime-internals = { path = "lucet-runtime-internals" } +libc = "0.2.65" +lucet-runtime-internals = { path = "lucet-runtime-internals", version = "=0.5.0" } +lucet-module = { path = "../lucet-module", version = "=0.5.0" } num-traits = "0.2" -num-derive = "0.2" +num-derive = "0.3.0" [dev-dependencies] byteorder = "1.2" -failure = "0.1" lazy_static = "1.1" -lucet-module-data = { path = "../lucet-module-data" } -lucet-runtime-tests = { path = "lucet-runtime-tests" } -nix = "0.13" +lucetc = { path = "../lucetc", version = "=0.5.0" } +lucet-runtime-tests = { path = "lucet-runtime-tests", version = "=0.5.0" } +lucet-wasi-sdk = { path = "../lucet-wasi-sdk", version = "=0.5.0" } +nix = "0.15" +rayon = "1.0" +tempfile = "3.0" +anyhow = "1" + +[build-dependencies] +# only used for tests +cc = "1.0" [lib] name = "lucet_runtime" crate-type = ["rlib", "staticlib", "cdylib"] [package.metadata.deb] +name = "fst-lucet-runtime" maintainer = "Adam C. Foltzer " depends = "$auto" priority = "optional" @@ -34,5 +45,4 @@ assets = [ ["target/release/liblucet_runtime.rlib", "/opt/fst-lucet-runtime/lib/", "644"], ["target/release/liblucet_runtime.so", "/opt/fst-lucet-runtime/lib/", "755"], ["include/*.h", "/opt/fst-lucet-runtime/include/", "644"], - ["README.rst", "/opt/fst-lucet-runtime/share/doc/lucet-runtime/", "644"], ] diff --git a/lucet-runtime/Makefile b/lucet-runtime/Makefile deleted file mode 100644 index 8e067daca..000000000 --- a/lucet-runtime/Makefile +++ /dev/null @@ -1,42 +0,0 @@ -# This Makefile exists primarily to allow running just the test suite for lucet-runtime, rather than -# for the whole workspace. - -export GUEST_MODULE_PREFIX=$(abspath $(CURDIR)/..) - -default: build - -.PHONY: lucetc -lucetc: - cargo build -p lucetc - -LUCET_LIBC:=../lucet-libc -.PHONY: $(LUCET_LIBC) -$(LUCET_LIBC): - make -C $(LUCET_LIBC) - -LUCET_TESTS:=../tests -.PHONY: $(LUCET_TESTS) -$(LUCET_TESTS): $(LUCET_LIBC) lucetc - make -C $(LUCET_TESTS) guests - -TEST_DEPS:= $(LUCET_LIB_C) $(LUCET_TESTS) - -.PHONY: build -build: - cargo build -p lucet-runtime - -.PHONY: release -release: - cargo build --release -p lucet-runtime - -.PHONY: test -test: build $(TEST_DEPS) - cargo test --no-fail-fast -p lucet-runtime -p lucet-runtime-internals - -.PHONY: audit -audit: - cargo audit - -.PHONY: clean -clean: - cargo clean -p lucet-runtime lucet-runtime-internals lucet-runtime-tests diff --git a/lucet-runtime/build.rs b/lucet-runtime/build.rs new file mode 100644 index 000000000..8abc728f8 --- /dev/null +++ b/lucet-runtime/build.rs @@ -0,0 +1,12 @@ +use cc; + +fn main() { + build_c_api_tests(); +} + +fn build_c_api_tests() { + cc::Build::new() + .file("tests/c_api.c") + .include("include") + .compile("lucet_runtime_c_api_tests"); +} diff --git a/lucet-runtime/include/lucet.h b/lucet-runtime/include/lucet.h index f464771f2..b360784ce 100644 --- a/lucet-runtime/include/lucet.h +++ b/lucet-runtime/include/lucet.h @@ -6,7 +6,6 @@ #include #include #include -#include #include "lucet_types.h" #include "lucet_val.h" @@ -39,13 +38,18 @@ enum lucet_error lucet_instance_reset(struct lucet_instance *inst); enum lucet_error lucet_instance_run(struct lucet_instance * inst, const char * entrypoint, uintptr_t argc, - const struct lucet_val *argv); + const struct lucet_val *argv, + struct lucet_result * result_out); enum lucet_error lucet_instance_run_func_idx(struct lucet_instance * inst, uint32_t table_idx, uint32_t func_idx, uintptr_t argc, - const struct lucet_val *argv); + const struct lucet_val *argv, + struct lucet_result * result_out); + +enum lucet_error +lucet_instance_resume(struct lucet_instance *inst, void *val, struct lucet_result *result_out); enum lucet_error lucet_instance_set_fatal_handler(struct lucet_instance *inst, lucet_fatal_handler fatal_handler); @@ -56,9 +60,6 @@ enum lucet_error lucet_instance_set_fatal_handler(struct lucet_instance *inst, enum lucet_error lucet_instance_set_signal_handler(struct lucet_instance *inst, lucet_signal_handler signal_handler); -enum lucet_error lucet_instance_state(const struct lucet_instance *inst, - struct lucet_state * state_out); - enum lucet_error lucet_mmap_region_create(uint64_t instance_capacity, const struct lucet_alloc_limits *limits, struct lucet_region ** region_out); @@ -80,8 +81,6 @@ double lucet_retval_f64(const struct lucet_untyped_retval *retval); union lucet_retval_gp lucet_retval_gp(const struct lucet_untyped_retval *retval); -void lucet_state_release(struct lucet_state *state); - -const char *lucet_state_tag_name(enum lucet_state_tag tag); +const char *lucet_result_tag_name(enum lucet_result_tag tag); #endif /* LUCET_H */ diff --git a/lucet-runtime/include/lucet_types.h b/lucet-runtime/include/lucet_types.h index 717369af8..ec4b3365d 100644 --- a/lucet-runtime/include/lucet_types.h +++ b/lucet-runtime/include/lucet_types.h @@ -1,22 +1,36 @@ #ifndef LUCET_TYPES_H #define LUCET_TYPES_H +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 500 +#endif + #include #include #include #include +#ifdef __APPLE__ +#include +#else +#include +#endif + enum lucet_error { lucet_error_ok, lucet_error_invalid_argument, lucet_error_region_full, lucet_error_module, lucet_error_limits_exceeded, + lucet_error_no_linear_memory, lucet_error_symbol_not_found, lucet_error_func_not_found, lucet_error_runtime_fault, lucet_error_runtime_terminated, lucet_error_dl, + lucet_error_instance_not_returned, + lucet_error_instance_not_yielded, + lucet_error_start_yielded, lucet_error_internal, lucet_error_unsupported, }; @@ -27,32 +41,28 @@ enum lucet_signal_behavior { lucet_signal_behavior_terminate, }; -enum lucet_state_tag { - lucet_state_tag_returned, - lucet_state_tag_running, - lucet_state_tag_fault, - lucet_state_tag_terminated, -}; - enum lucet_terminated_reason { lucet_terminated_reason_signal, - lucet_terminated_reason_get_embed_ctx, + lucet_terminated_reason_ctx_not_found, + lucet_terminated_reason_yield_type_mismatch, + lucet_terminated_reason_borrow_error, lucet_terminated_reason_provided, + lucet_terminated_reason_remote, }; -enum lucet_trapcode_type { - lucet_trapcode_type_stack_overflow, - lucet_trapcode_type_heap_out_of_bounds, - lucet_trapcode_type_out_of_bounds, - lucet_trapcode_type_indirect_call_to_null, - lucet_trapcode_type_bad_signature, - lucet_trapcode_type_integer_overflow, - lucet_trapcode_type_integer_div_by_zero, - lucet_trapcode_type_bad_conversion_to_integer, - lucet_trapcode_type_interrupt, - lucet_trapcode_type_table_out_of_bounds, - lucet_trapcode_type_user, - lucet_trapcode_type_unknown, +enum lucet_trapcode { + lucet_trapcode_stack_overflow, + lucet_trapcode_heap_out_of_bounds, + lucet_trapcode_out_of_bounds, + lucet_trapcode_indirect_call_to_null, + lucet_trapcode_bad_signature, + lucet_trapcode_integer_overflow, + lucet_trapcode_integer_div_by_zero, + lucet_trapcode_bad_conversion_to_integer, + lucet_trapcode_interrupt, + lucet_trapcode_table_out_of_bounds, + lucet_trapcode_user, + lucet_trapcode_unknown, }; enum lucet_val_type { @@ -115,13 +125,8 @@ struct lucet_alloc_limits { uint64_t globals_size; }; -struct lucet_trapcode { - enum lucet_trapcode_type code; - uint16_t tag; -}; - -typedef enum lucet_signal_behavior (*lucet_signal_handler)(struct lucet_instance * inst, - const struct lucet_trapcode *trap, +typedef enum lucet_signal_behavior (*lucet_signal_handler)(struct lucet_instance * inst, + const enum lucet_trapcode trap, int signum, const siginfo_t *siginfo, const void *context); @@ -132,20 +137,20 @@ struct lucet_untyped_retval { char gp[8]; }; +#define LUCET_MODULE_ADDR_DETAILS_NAME_LEN 256 + struct lucet_module_addr_details { - bool module_code_resolvable; - bool in_module_code; - const char *file_name; - const char *sym_name; + bool module_code_resolvable; + bool in_module_code; + char file_name[LUCET_MODULE_ADDR_DETAILS_NAME_LEN]; + char sym_name[LUCET_MODULE_ADDR_DETAILS_NAME_LEN]; }; -struct lucet_runtime_fault { +struct lucet_runtime_faulted { bool fatal; - struct lucet_trapcode trapcode; + enum lucet_trapcode trapcode; uintptr_t rip_addr; struct lucet_module_addr_details rip_addr_details; - siginfo_t signal_info; - ucontext_t context; }; struct lucet_terminated { @@ -153,16 +158,29 @@ struct lucet_terminated { void * provided; }; -union lucet_state_val { - struct lucet_untyped_retval returned; - bool running; - struct lucet_runtime_fault fault; - struct lucet_terminated terminated; +struct lucet_yielded { + void *val; +}; + +union lucet_result_val { + struct lucet_untyped_retval returned; + struct lucet_yielded yielded; + struct lucet_runtime_faulted faulted; + struct lucet_terminated terminated; + enum lucet_error errored; +}; + +enum lucet_result_tag { + lucet_result_tag_returned, + lucet_result_tag_yielded, + lucet_result_tag_faulted, + lucet_result_tag_terminated, + lucet_result_tag_errored, }; -struct lucet_state { - enum lucet_state_tag tag; - union lucet_state_val val; +struct lucet_result { + enum lucet_result_tag tag; + union lucet_result_val val; }; union lucet_retval_gp { diff --git a/lucet-runtime/include/lucet_vmctx.h b/lucet-runtime/include/lucet_vmctx.h index 73917d186..76af4afe5 100644 --- a/lucet-runtime/include/lucet_vmctx.h +++ b/lucet-runtime/include/lucet_vmctx.h @@ -26,6 +26,8 @@ void *lucet_vmctx_get_delegate(struct lucet_vmctx const *); void lucet_vmctx_terminate(struct lucet_vmctx const *, void *info); +void *lucet_vmctx_yield(struct lucet_vmctx const *, void *val); + // returns the current number of wasm pages uint32_t lucet_vmctx_current_memory(struct lucet_vmctx const *); diff --git a/lucet-runtime/lucet-runtime-internals/Cargo.toml b/lucet-runtime/lucet-runtime-internals/Cargo.toml index fb108348f..ee45da261 100644 --- a/lucet-runtime/lucet-runtime-internals/Cargo.toml +++ b/lucet-runtime/lucet-runtime-internals/Cargo.toml @@ -1,25 +1,41 @@ [package] name = "lucet-runtime-internals" -version = "0.1.0" -authors = ["Adam C. Foltzer "] +version = "0.5.0" +description = "Pure Rust runtime for Lucet WebAssembly toolchain (internals)" +homepage = "https://github.com/fastly/lucet" +repository = "https://github.com/fastly/lucet" +license = "Apache-2.0 WITH LLVM-exception" +categories = ["wasm"] +authors = ["Lucet team "] edition = "2018" [dependencies] -lucet-module-data = { path = "../../lucet-module-data" } +lucet-module = { path = "../../lucet-module", version = "=0.5.0" } +lucet-runtime-macros = { path = "../lucet-runtime-macros", version = "=0.5.0" } +anyhow = "1.0" bitflags = "1.0" -failure = "0.1" +bincode = "1.1.4" +byteorder = "1.3" lazy_static = "1.1" -libc = "0.2.47" +libc = "0.2.65" libloading = "0.5" -nix = "0.13" -num-derive = "0.2" +memoffset = "0.5.1" +nix = "0.15" +num-derive = "0.3.0" num-traits = "0.2" -xfailure = "0.1" +raw-cpuid = "6.0.0" +thiserror = "1.0.4" + +# This is only a dependency to ensure that other crates don't pick a newer version as a transitive +# dependency. `0.1.3 < getrandom <= 0.1.6` cause `lazy_static` to pull in spinlock implementations +# of concurrency primitives, which for unknown reasons cause our signal handling code to +# nondeterministically segfault. The maintainers have since removed the `lazy_static` dependency +# from `getrandom`, but until a new release is cut, this keeps it on a safe version. +getrandom = "=0.1.3" [dev-dependencies] byteorder = "1.2" -memoffset = "0.2" [build-dependencies] cc = "1.0" diff --git a/lucet-runtime/lucet-runtime-internals/build.rs b/lucet-runtime/lucet-runtime-internals/build.rs index 41323408c..9c5e5418a 100644 --- a/lucet-runtime/lucet-runtime-internals/build.rs +++ b/lucet-runtime/lucet-runtime-internals/build.rs @@ -1,3 +1,7 @@ +use std::env; +use std::fs::File; +use std::path::Path; + use cc; fn main() { @@ -14,4 +18,28 @@ fn main() { cc::Build::new() .file("src/context/tests/c_child.c") .compile("context_tests_c_child"); + + let commit_file_path = Path::new(&env::var("OUT_DIR").unwrap()).join("commit_hash"); + // in debug builds we only need the file to exist, but in release builds this will be used and + // requires mutability. + #[allow(unused_variables, unused_mut)] + let mut f = File::create(&commit_file_path).unwrap(); + + // This is about the closest not-additional-feature-flag way to detect release builds. + // In debug builds, leave the `commit_hash` file empty to allow looser version checking and + // avoid impacting development workflows too much. + #[cfg(not(debug_assertions))] + { + use std::io::Write; + use std::process::Command; + + let last_commit_hash = Command::new("git") + .args(&["log", "-n", "1", "--pretty=format:%H"]) + .output() + .ok(); + + if let Some(last_commit_hash) = last_commit_hash { + f.write_all(&last_commit_hash.stdout).unwrap(); + } + } } diff --git a/lucet-runtime/lucet-runtime-internals/src/alloc/mod.rs b/lucet-runtime/lucet-runtime-internals/src/alloc/mod.rs index d9d7bb55a..68276a9da 100644 --- a/lucet-runtime/lucet-runtime-internals/src/alloc/mod.rs +++ b/lucet-runtime/lucet-runtime-internals/src/alloc/mod.rs @@ -2,10 +2,11 @@ use crate::error::Error; use crate::module::Module; use crate::region::RegionInternal; use libc::{c_void, SIGSTKSZ}; +use lucet_module::GlobalValue; use nix::unistd::{sysconf, SysconfVar}; use std::sync::{Arc, Once, Weak}; -const HOST_PAGE_SIZE_EXPECTED: usize = 4096; +pub const HOST_PAGE_SIZE_EXPECTED: usize = 4096; static mut HOST_PAGE_SIZE: usize = 0; static HOST_PAGE_SIZE_INIT: Once = Once::new(); @@ -112,7 +113,8 @@ impl Drop for Alloc { } impl Alloc { - pub fn addr_in_heap_guard(&self, addr: *const c_void) -> bool { + pub fn addr_in_guard_page(&self, addr: *const c_void) -> bool { + let addr = addr as usize; let heap = self.slot().heap as usize; let guard_start = heap + self.heap_accessible_size; let guard_end = heap + self.slot().limits.heap_address_space_size; @@ -120,7 +122,16 @@ impl Alloc { // "addr = {:p}, guard_start = {:p}, guard_end = {:p}", // addr, guard_start as *mut c_void, guard_end as *mut c_void // ); - (addr as usize >= guard_start) && ((addr as usize) < guard_end) + let stack_guard_end = self.slot().stack as usize; + let stack_guard_start = stack_guard_end - host_page_size(); + // eprintln!( + // "addr = {:p}, stack_guard_start = {:p}, stack_guard_end = {:p}", + // addr, stack_guard_start as *mut c_void, stack_guard_end as *mut c_void + // ); + let in_heap_guard = (addr >= guard_start) && (addr < guard_end); + let in_stack_guard = (addr >= stack_guard_start) && (addr < stack_guard_end); + + in_heap_guard || in_stack_guard } pub fn expand_heap(&mut self, expand_bytes: u32, module: &dyn Module) -> Result { @@ -154,25 +165,27 @@ impl Alloc { // the above makes sure this expression does not underflow let guard_remaining = self.heap_inaccessible_size - expand_pagealigned as usize; - let heap_spec = module.heap_spec(); - // The compiler specifies how much guard (memory which traps on access) must be beyond the - // end of the accessible memory. We cannot perform an expansion that would make this region - // smaller than the compiler expected it to be. - if guard_remaining < heap_spec.guard_size as usize { - bail_limits_exceeded!("expansion would leave guard memory too small"); - } + if let Some(heap_spec) = module.heap_spec() { + // The compiler specifies how much guard (memory which traps on access) must be beyond the + // end of the accessible memory. We cannot perform an expansion that would make this region + // smaller than the compiler expected it to be. + if guard_remaining < heap_spec.guard_size as usize { + bail_limits_exceeded!("expansion would leave guard memory too small"); + } - // The compiler indicates that the module has specified a maximum memory size. Don't let - // the heap expand beyond that: - if let Some(max_size) = heap_spec.max_size { - if self.heap_accessible_size + expand_pagealigned as usize > max_size as usize { - bail_limits_exceeded!( - "expansion would exceed module-specified heap limit: {:?}", - max_size - ); + // The compiler indicates that the module has specified a maximum memory size. Don't let + // the heap expand beyond that: + if let Some(max_size) = heap_spec.max_size { + if self.heap_accessible_size + expand_pagealigned as usize > max_size as usize { + bail_limits_exceeded!( + "expansion would exceed module-specified heap limit: {:?}", + max_size + ); + } } + } else { + return Err(Error::NoLinearMemory("cannot expand heap".to_owned())); } - // The runtime sets a limit on how much of the heap can be backed by real memory. Don't let // the heap expand beyond that: if self.heap_accessible_size + expand_pagealigned as usize > slot.limits.heap_memory_size { @@ -229,7 +242,7 @@ impl Alloc { } /// Return the heap as a mutable slice of 32-bit words. - pub unsafe fn heap_u32_mut(&self) -> &mut [u32] { + pub unsafe fn heap_u32_mut(&mut self) -> &mut [u32] { assert!(self.slot().heap as usize % 4 == 0, "heap is 4-byte aligned"); assert!( self.heap_accessible_size % 4 == 0, @@ -288,18 +301,18 @@ impl Alloc { } /// Return the globals as a slice. - pub unsafe fn globals(&self) -> &[i64] { + pub unsafe fn globals(&self) -> &[GlobalValue] { std::slice::from_raw_parts( - self.slot().globals as *const i64, - self.slot().limits.globals_size / 8, + self.slot().globals as *const GlobalValue, + self.slot().limits.globals_size / std::mem::size_of::(), ) } /// Return the globals as a mutable slice. - pub unsafe fn globals_mut(&mut self) -> &mut [i64] { + pub unsafe fn globals_mut(&mut self) -> &mut [GlobalValue] { std::slice::from_raw_parts_mut( - self.slot().globals as *mut i64, - self.slot().limits.globals_size / 8, + self.slot().globals as *mut GlobalValue, + self.slot().limits.globals_size / std::mem::size_of::(), ) } diff --git a/lucet-runtime/lucet-runtime-internals/src/alloc/tests.rs b/lucet-runtime/lucet-runtime-internals/src/alloc/tests.rs index 603f8ef92..76da9952b 100644 --- a/lucet-runtime/lucet-runtime-internals/src/alloc/tests.rs +++ b/lucet-runtime/lucet-runtime-internals/src/alloc/tests.rs @@ -7,7 +7,7 @@ macro_rules! alloc_tests { use $crate::alloc::Limits; use $crate::context::{Context, ContextHandle}; use $crate::instance::InstanceInternal; - use $crate::module::{HeapSpec, MockModuleBuilder}; + use $crate::module::{GlobalValue, HeapSpec, MockModuleBuilder}; use $crate::region::Region; use $crate::val::Val; @@ -348,15 +348,22 @@ macro_rules! alloc_tests { assert_eq!(stack[LIMITS_STACK_SIZE - 1], 0xFF); let globals = unsafe { inst.alloc_mut().globals_mut() }; - assert_eq!(globals.len(), LIMITS_GLOBALS_SIZE / 8); + assert_eq!( + globals.len(), + LIMITS_GLOBALS_SIZE / std::mem::size_of::() + ); - assert_eq!(globals[0], 0); - globals[0] = 0xFF; - assert_eq!(globals[0], 0xFF); + unsafe { + assert_eq!(globals[0].i_64, 0); + globals[0].i_64 = 0xFF; + assert_eq!(globals[0].i_64, 0xFF); + } - assert_eq!(globals[globals.len() - 1], 0); - globals[globals.len() - 1] = 0xFF; - assert_eq!(globals[globals.len() - 1], 0xFF); + unsafe { + assert_eq!(globals[globals.len() - 1].i_64, 0); + globals[globals.len() - 1].i_64 = 0xFF; + assert_eq!(globals[globals.len() - 1].i_64, 0xFF); + } let sigstack = unsafe { inst.alloc_mut().sigstack_mut() }; assert_eq!(sigstack.len(), libc::SIGSTKSZ); @@ -377,8 +384,7 @@ macro_rules! alloc_tests { peek_n_poke(®ion); } - /// This test shows that the reset method clears the heap and restores it to the spec - /// initial size. + /// This test shows that the reset method clears the heap and resets its protections. #[test] fn alloc_reset() { let region = TestRegion::create(1, &LIMITS).expect("region created"); @@ -402,6 +408,52 @@ macro_rules! alloc_tests { heap[heap_len - 1] = 0xFF; assert_eq!(heap[heap_len - 1], 0xFF); + // Making a new mock module here because the borrow checker doesn't like referencing + // `inst.module` while `inst.alloc()` is borrowed mutably. The `Instance` tests don't have + // this weirdness + inst.alloc_mut() + .reset_heap(module.as_ref()) + .expect("reset succeeds"); + + let reset_heap_len = inst.alloc().heap_len(); + assert_eq!(reset_heap_len, THREEPAGE_INITIAL_SIZE as usize); + + let heap = unsafe { inst.alloc_mut().heap_mut() }; + + assert_eq!(heap[0], 0); + heap[0] = 0xFF; + assert_eq!(heap[0], 0xFF); + + assert_eq!(heap[reset_heap_len - 1], 0); + heap[reset_heap_len - 1] = 0xFF; + assert_eq!(heap[reset_heap_len - 1], 0xFF); + } + + /// This test shows that the reset method clears the heap and restores it to the spec + /// initial size after growing the heap. + #[test] + fn alloc_grow_reset() { + let region = TestRegion::create(1, &LIMITS).expect("region created"); + let module = MockModuleBuilder::new() + .with_heap_spec(THREE_PAGE_MAX_HEAP) + .build(); + let mut inst = region + .new_instance(module.clone()) + .expect("new_instance succeeds"); + + let heap_len = inst.alloc().heap_len(); + assert_eq!(heap_len, THREEPAGE_INITIAL_SIZE as usize); + + let heap = unsafe { inst.alloc_mut().heap_mut() }; + + assert_eq!(heap[0], 0); + heap[0] = 0xFF; + assert_eq!(heap[0], 0xFF); + + assert_eq!(heap[heap_len - 1], 0); + heap[heap_len - 1] = 0xFF; + assert_eq!(heap[heap_len - 1], 0xFF); + let new_heap_area = inst .alloc_mut() .expand_heap( @@ -550,14 +602,13 @@ macro_rules! alloc_tests { let mut parent = ContextHandle::new(); unsafe { let heap_ptr = inst.alloc_mut().heap_mut().as_ptr() as *mut c_void; - let child = ContextHandle::create_and_init( + let mut child = ContextHandle::create_and_init( inst.alloc_mut().stack_u64_mut(), - &mut parent, - heap_touching_child as *const extern "C" fn(), + heap_touching_child as usize, &[Val::CPtr(heap_ptr)], ) .expect("context init succeeds"); - Context::swap(&mut parent, &child); + Context::swap(&mut parent, &mut child); assert_eq!(inst.alloc().heap()[0], 123); assert_eq!(inst.alloc().heap()[4095], 45); } @@ -574,7 +625,15 @@ macro_rules! alloc_tests { std::slice::from_raw_parts_mut(heap, CONTEXT_TEST_INITIAL_SIZE as usize / 8) }; let mut onthestack = [0u8; STACK_PATTERN_LENGTH]; + // While not used, this array is load-bearing! A function that executes after the + // guest completes, `instance_kill_state_exit_guest_region`, may end up using + // sufficient stack space to trample over values in this function's call frame. + // + // Padding it out with a duplicate pattern makes enough space for `onthestack` to + // not be clobbered. + let mut ignored = [0u8; STACK_PATTERN_LENGTH]; for i in 0..STACK_PATTERN_LENGTH { + ignored[i] = (i % 256) as u8; onthestack[i] = (i % 256) as u8; } heap[0] = onthestack.as_ptr() as u64; @@ -592,14 +651,13 @@ macro_rules! alloc_tests { let mut parent = ContextHandle::new(); unsafe { let heap_ptr = inst.alloc_mut().heap_mut().as_ptr() as *mut c_void; - let child = ContextHandle::create_and_init( + let mut child = ContextHandle::create_and_init( inst.alloc_mut().stack_u64_mut(), - &mut parent, - stack_pattern_child as *const extern "C" fn(), + stack_pattern_child as usize, &[Val::CPtr(heap_ptr)], ) .expect("context init succeeds"); - Context::swap(&mut parent, &child); + Context::swap(&mut parent, &mut child); let stack_pattern = inst.alloc().heap_u64()[0] as usize; assert!(stack_pattern > inst.alloc().slot().stack as usize); @@ -613,6 +671,16 @@ macro_rules! alloc_tests { } } } + + #[test] + fn drop_region_first() { + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let inst = region + .new_instance(MockModuleBuilder::new().build()) + .expect("new_instance succeeds"); + drop(region); + drop(inst); + } }; } diff --git a/lucet-runtime/lucet-runtime-internals/src/c_api.rs b/lucet-runtime/lucet-runtime-internals/src/c_api.rs index 327f325e7..576cf8c12 100644 --- a/lucet-runtime/lucet-runtime-internals/src/c_api.rs +++ b/lucet-runtime/lucet-runtime-internals/src/c_api.rs @@ -1,6 +1,6 @@ #![allow(non_camel_case_types)] -pub use self::lucet_state::*; +pub use self::lucet_result::*; pub use self::lucet_val::*; use crate::alloc::Limits; @@ -55,7 +55,8 @@ macro_rules! with_ffi_arcs { /// Marker type for the `vmctx` pointer argument. /// -/// This type should only be used with [`Vmctx::from_raw()`](struct.Vmctx.html#method.from_raw). +/// This type should only be used with [`Vmctx::from_raw()`](struct.Vmctx.html#method.from_raw) or +/// the C API. #[repr(C)] pub struct lucet_vmctx { _unused: [u8; 0], @@ -69,41 +70,58 @@ pub enum lucet_error { RegionFull, Module, LimitsExceeded, + NoLinearMemory, SymbolNotFound, FuncNotFound, RuntimeFault, RuntimeTerminated, Dl, + InstanceNotReturned, + InstanceNotYielded, + StartYielded, Internal, Unsupported, } impl From for lucet_error { fn from(e: Error) -> lucet_error { + lucet_error::from(&e) + } +} + +impl From<&Error> for lucet_error { + fn from(e: &Error) -> lucet_error { match e { Error::InvalidArgument(_) => lucet_error::InvalidArgument, Error::RegionFull(_) => lucet_error::RegionFull, Error::ModuleError(_) => lucet_error::Module, Error::LimitsExceeded(_) => lucet_error::LimitsExceeded, + Error::NoLinearMemory(_) => lucet_error::NoLinearMemory, Error::SymbolNotFound(_) => lucet_error::SymbolNotFound, Error::FuncNotFound(_, _) => lucet_error::FuncNotFound, Error::RuntimeFault(_) => lucet_error::RuntimeFault, Error::RuntimeTerminated(_) => lucet_error::RuntimeTerminated, Error::DlError(_) => lucet_error::Dl, + Error::InstanceNotReturned => lucet_error::InstanceNotReturned, + Error::InstanceNotYielded => lucet_error::InstanceNotYielded, + Error::StartYielded => lucet_error::StartYielded, Error::InternalError(_) => lucet_error::Internal, Error::Unsupported(_) => lucet_error::Unsupported, } } } +#[repr(C)] pub struct lucet_instance { _unused: [u8; 0], } +#[repr(C)] pub struct lucet_region { _unused: [u8; 0], } +#[repr(C)] pub struct lucet_dl_module { _unused: [u8; 0], } @@ -184,7 +202,7 @@ impl From<&lucet_signal_behavior> for SignalBehavior { pub type lucet_signal_handler = unsafe extern "C" fn( inst: *mut lucet_instance, - trap: *const lucet_state::lucet_trapcode, + trap: lucet_result::lucet_trapcode, signum: c_int, siginfo: *const libc::siginfo_t, context: *const c_void, @@ -192,96 +210,131 @@ pub type lucet_signal_handler = unsafe extern "C" fn( pub type lucet_fatal_handler = unsafe extern "C" fn(inst: *mut lucet_instance); -pub mod lucet_state { - use crate::c_api::lucet_val; - use crate::instance::{State, TerminationDetails}; - use crate::module::AddrDetails; - use crate::trapcode::{TrapCode, TrapCodeType}; - use libc::{c_char, c_void}; +pub struct CTerminationDetails { + pub details: *mut c_void, +} + +unsafe impl Send for CTerminationDetails {} +unsafe impl Sync for CTerminationDetails {} + +pub struct CYieldedVal { + pub val: *mut c_void, +} + +unsafe impl Send for CYieldedVal {} +unsafe impl Sync for CYieldedVal {} + +pub mod lucet_result { + use super::lucet_error; + use crate::c_api::{lucet_val, CTerminationDetails, CYieldedVal}; + use crate::error::Error; + use crate::instance::{RunResult, TerminationDetails}; + use crate::module::{AddrDetails, TrapCode}; + use libc::{c_uchar, c_void}; use num_derive::FromPrimitive; use std::ffi::CString; use std::ptr; - impl From<&State> for lucet_state { - fn from(state: &State) -> lucet_state { - match state { - State::Ready { retval } => lucet_state { - tag: lucet_state_tag::Returned, - val: lucet_state_val { + impl From> for lucet_result { + fn from(res: Result) -> lucet_result { + match res { + Ok(RunResult::Returned(retval)) => lucet_result { + tag: lucet_result_tag::Returned, + val: lucet_result_val { returned: retval.into(), }, }, - State::Running => lucet_state { - tag: lucet_state_tag::Running, - val: lucet_state_val { running: true }, + Ok(RunResult::Yielded(val)) => lucet_result { + tag: lucet_result_tag::Yielded, + val: lucet_result_val { + yielded: lucet_yielded { + val: val + .downcast_ref() + .map(|CYieldedVal { val }| *val) + .unwrap_or(ptr::null_mut()), + }, + }, }, - State::Fault { - details, - siginfo, - context, - } => lucet_state { - tag: lucet_state_tag::Fault, - val: lucet_state_val { - fault: lucet_runtime_fault { + // TODO: test this path; currently our C API tests don't include any faulting tests + Err(Error::RuntimeFault(details)) => lucet_result { + tag: lucet_result_tag::Faulted, + val: lucet_result_val { + fault: lucet_runtime_faulted { fatal: details.fatal, trapcode: details.trapcode.into(), rip_addr: details.rip_addr, - rip_addr_details: (&details.rip_addr_details).into(), - signal_info: *siginfo, - context: *context, + rip_addr_details: details.rip_addr_details.into(), }, }, }, - State::Terminated { details } => lucet_state { - tag: lucet_state_tag::Terminated, - val: lucet_state_val { + // TODO: test this path; currently our C API tests don't include any terminating tests + Err(Error::RuntimeTerminated(details)) => lucet_result { + tag: lucet_result_tag::Terminated, + val: lucet_result_val { terminated: match details { TerminationDetails::Signal => lucet_terminated { reason: lucet_terminated_reason::Signal, - provided: std::ptr::null_mut(), + provided: ptr::null_mut(), }, - TerminationDetails::GetEmbedCtx => lucet_terminated { - reason: lucet_terminated_reason::GetEmbedCtx, - provided: std::ptr::null_mut(), + TerminationDetails::CtxNotFound => lucet_terminated { + reason: lucet_terminated_reason::CtxNotFound, + provided: ptr::null_mut(), + }, + TerminationDetails::YieldTypeMismatch => lucet_terminated { + reason: lucet_terminated_reason::YieldTypeMismatch, + provided: ptr::null_mut(), + }, + TerminationDetails::BorrowError(_) => lucet_terminated { + reason: lucet_terminated_reason::BorrowError, + provided: ptr::null_mut(), }, TerminationDetails::Provided(p) => lucet_terminated { reason: lucet_terminated_reason::Provided, provided: p .downcast_ref() - .map(|v| *v) - .unwrap_or(std::ptr::null_mut()), + .map(|CTerminationDetails { details }| *details) + .unwrap_or(ptr::null_mut()), + }, + TerminationDetails::Remote => lucet_terminated { + reason: lucet_terminated_reason::Remote, + provided: std::ptr::null_mut(), }, }, }, }, + Err(e) => lucet_result { + tag: lucet_result_tag::Errored, + val: lucet_result_val { errored: e.into() }, + }, } } } #[repr(C)] #[derive(Clone, Copy)] - pub struct lucet_state { - pub tag: lucet_state_tag, - pub val: lucet_state_val, + pub struct lucet_result { + pub tag: lucet_result_tag, + pub val: lucet_result_val, } #[repr(C)] #[derive(Clone, Copy, Debug, FromPrimitive)] - pub enum lucet_state_tag { + pub enum lucet_result_tag { Returned, - Running, - Fault, + Yielded, + Faulted, Terminated, + Errored, } #[repr(C)] #[derive(Clone, Copy)] - pub union lucet_state_val { + pub union lucet_result_val { pub returned: lucet_val::lucet_untyped_retval, - // no meaning to this boolean, it's just there so the type is FFI-safe - pub running: bool, - pub fault: lucet_runtime_fault, + pub yielded: lucet_yielded, + pub fault: lucet_runtime_faulted, pub terminated: lucet_terminated, + pub errored: lucet_error, } #[repr(C)] @@ -295,24 +348,31 @@ pub mod lucet_state { #[derive(Clone, Copy)] pub enum lucet_terminated_reason { Signal, - GetEmbedCtx, + CtxNotFound, + YieldTypeMismatch, + BorrowError, Provided, + Remote, } #[repr(C)] #[derive(Clone, Copy)] - pub struct lucet_runtime_fault { + pub struct lucet_yielded { + pub val: *mut c_void, + } + + #[repr(C)] + #[derive(Clone, Copy)] + pub struct lucet_runtime_faulted { pub fatal: bool, pub trapcode: lucet_trapcode, pub rip_addr: libc::uintptr_t, pub rip_addr_details: lucet_module_addr_details, - pub signal_info: libc::siginfo_t, - pub context: libc::ucontext_t, } #[repr(C)] #[derive(Clone, Copy, Debug)] - pub enum lucet_trapcode_type { + pub enum lucet_trapcode { StackOverflow, HeapOutOfBounds, OutOfBounds, @@ -323,64 +383,49 @@ pub mod lucet_state { BadConversionToInteger, Interrupt, TableOutOfBounds, - User, + Unreachable, Unknown, } - impl From for lucet_trapcode_type { - fn from(ty: TrapCodeType) -> lucet_trapcode_type { + impl From> for lucet_trapcode { + fn from(ty: Option) -> lucet_trapcode { (&ty).into() } } - impl From<&TrapCodeType> for lucet_trapcode_type { - fn from(ty: &TrapCodeType) -> lucet_trapcode_type { - match ty { - TrapCodeType::StackOverflow => lucet_trapcode_type::StackOverflow, - TrapCodeType::HeapOutOfBounds => lucet_trapcode_type::HeapOutOfBounds, - TrapCodeType::OutOfBounds => lucet_trapcode_type::OutOfBounds, - TrapCodeType::IndirectCallToNull => lucet_trapcode_type::IndirectCallToNull, - TrapCodeType::BadSignature => lucet_trapcode_type::BadSignature, - TrapCodeType::IntegerOverflow => lucet_trapcode_type::IntegerOverflow, - TrapCodeType::IntegerDivByZero => lucet_trapcode_type::IntegerDivByZero, - TrapCodeType::BadConversionToInteger => lucet_trapcode_type::BadConversionToInteger, - TrapCodeType::Interrupt => lucet_trapcode_type::Interrupt, - TrapCodeType::TableOutOfBounds => lucet_trapcode_type::TableOutOfBounds, - TrapCodeType::User => lucet_trapcode_type::User, - TrapCodeType::Unknown => lucet_trapcode_type::Unknown, + impl From<&Option> for lucet_trapcode { + fn from(ty: &Option) -> lucet_trapcode { + if let Some(ty) = ty { + match ty { + TrapCode::StackOverflow => lucet_trapcode::StackOverflow, + TrapCode::HeapOutOfBounds => lucet_trapcode::HeapOutOfBounds, + TrapCode::OutOfBounds => lucet_trapcode::OutOfBounds, + TrapCode::IndirectCallToNull => lucet_trapcode::IndirectCallToNull, + TrapCode::BadSignature => lucet_trapcode::BadSignature, + TrapCode::IntegerOverflow => lucet_trapcode::IntegerOverflow, + TrapCode::IntegerDivByZero => lucet_trapcode::IntegerDivByZero, + TrapCode::BadConversionToInteger => lucet_trapcode::BadConversionToInteger, + TrapCode::Interrupt => lucet_trapcode::Interrupt, + TrapCode::TableOutOfBounds => lucet_trapcode::TableOutOfBounds, + TrapCode::Unreachable => lucet_trapcode::Unreachable, + } + } else { + lucet_trapcode::Unknown } } } - #[repr(C)] - #[derive(Clone, Copy, Debug)] - pub struct lucet_trapcode { - code: lucet_trapcode_type, - tag: u16, - } - - impl From for lucet_trapcode { - fn from(trap: TrapCode) -> lucet_trapcode { - (&trap).into() - } - } - - impl From<&TrapCode> for lucet_trapcode { - fn from(trap: &TrapCode) -> lucet_trapcode { - lucet_trapcode { - code: trap.ty.into(), - tag: trap.tag, - } - } - } + const ADDR_DETAILS_NAME_LEN: usize = 256; + /// Half a kilobyte is too substantial for `Copy`, but we must have it because [unions with + /// non-`Copy` fields are unstable](https://github.com/rust-lang/rust/issues/32836). #[repr(C)] - #[derive(Clone, Copy, Debug)] + #[derive(Clone, Copy)] pub struct lucet_module_addr_details { pub module_code_resolvable: bool, pub in_module_code: bool, - pub file_name: *const c_char, - pub sym_name: *const c_char, + pub file_name: [c_uchar; ADDR_DETAILS_NAME_LEN], + pub sym_name: [c_uchar; ADDR_DETAILS_NAME_LEN], } impl Default for lucet_module_addr_details { @@ -388,45 +433,49 @@ pub mod lucet_state { lucet_module_addr_details { module_code_resolvable: false, in_module_code: false, - file_name: ptr::null(), - sym_name: ptr::null(), + file_name: [0; ADDR_DETAILS_NAME_LEN], + sym_name: [0; ADDR_DETAILS_NAME_LEN], } } } impl From> for lucet_module_addr_details { fn from(details: Option) -> Self { - (&details).into() - } - } + /// Convert a string into C-compatible bytes, truncate it to length + /// `ADDR_DETAILS_NAME_LEN`, and make sure it has a trailing nul. + fn trunc_c_str_bytes(s: &str) -> Vec { + let s = CString::new(s); + let mut bytes = s.ok().map(|s| s.into_bytes_with_nul()).unwrap_or(vec![0]); + bytes.truncate(ADDR_DETAILS_NAME_LEN); + // we always have at least the 0, so this `last` can be unwrapped + *bytes.last_mut().unwrap() = 0; + bytes + } - impl From<&Option> for lucet_module_addr_details { - fn from(details: &Option) -> Self { - details + let mut ret = details .as_ref() .map(|details| lucet_module_addr_details { module_code_resolvable: true, in_module_code: details.in_module_code, - file_name: details - .file_name - .as_ref() - .and_then(|s| { - CString::new(s.clone()) - .ok() - .map(|s| s.into_raw() as *const _) - }) - .unwrap_or(ptr::null()), - sym_name: details - .sym_name - .as_ref() - .and_then(|s| { - CString::new(s.clone()) - .ok() - .map(|s| s.into_raw() as *const _) - }) - .unwrap_or(ptr::null()), + file_name: [0; ADDR_DETAILS_NAME_LEN], + sym_name: [0; ADDR_DETAILS_NAME_LEN], }) - .unwrap_or_default() + .unwrap_or_default(); + + // get truncated C-compatible bytes for each string, or "\0" if they're not present + let file_name_bytes = details + .as_ref() + .and_then(|details| details.file_name.as_ref().map(|s| trunc_c_str_bytes(s))) + .unwrap_or_else(|| vec![0]); + let sym_name_bytes = details + .and_then(|details| details.sym_name.as_ref().map(|s| trunc_c_str_bytes(s))) + .unwrap_or_else(|| vec![0]); + + // copy the bytes into the array, making sure to copy only as many as are in the string + ret.file_name[0..file_name_bytes.len()].copy_from_slice(file_name_bytes.as_slice()); + ret.sym_name[0..sym_name_bytes.len()].copy_from_slice(sym_name_bytes.as_slice()); + + ret } } } @@ -593,8 +642,8 @@ pub mod lucet_val { pub as_i64: i64, } - impl From<&UntypedRetVal> for lucet_untyped_retval { - fn from(retval: &UntypedRetVal) -> lucet_untyped_retval { + impl From for lucet_untyped_retval { + fn from(retval: UntypedRetVal) -> lucet_untyped_retval { let mut v = lucet_untyped_retval { fp: [0; 16], gp: [0; 8], diff --git a/lucet-runtime/lucet-runtime-internals/src/context/context_asm.S b/lucet-runtime/lucet-runtime-internals/src/context/context_asm.S index 7c6d01cf3..c844516ad 100644 --- a/lucet-runtime/lucet-runtime-internals/src/context/context_asm.S +++ b/lucet-runtime/lucet-runtime-internals/src/context/context_asm.S @@ -31,39 +31,83 @@ .text .globl lucet_context_bootstrap +#ifdef __ELF__ .type lucet_context_bootstrap,@function +#else +.globl _lucet_context_bootstrap +#endif .align 16 lucet_context_bootstrap: - /* Move each of the context-saved registers into the corresponding call - * argument register. See lucet_register enum for docs */ - mov %r12, %rsi - mov %r13, %rdx - mov %r14, %rcx - mov %r15, %r8 - mov %rbx, %r9 +_lucet_context_bootstrap: + // Move each of the argument values into the corresponding call + // argument register. + pop %r9 + pop %r8 + pop %rcx + pop %rdx + pop %rsi + pop %rdi + /* the next thing on the stack is the guest function - return to it */ ret +#ifdef __ELF__ .size lucet_context_bootstrap,.-lucet_context_bootstrap - +#endif .text .globl lucet_context_backstop +#ifdef __ELF__ .type lucet_context_backstop,@function +#else +.globl _lucet_context_backstop +#endif .align 16 lucet_context_backstop: - mov -16(%rbp), %rdi /* parent context to arg 1 */ - mov -8(%rbp), %rsi /* own context to arg 2 */ - mov %rax, (8*8 + 8*16 + 8*0)(%rdi) /* store return values before swapping back -- offset is offsetof(struct lucet_context, retvals) */ - mov %rdx, (8*8 + 8*16 + 8*1)(%rdi) - movdqu %xmm0, (8*8 + 8*16 + 8*2)(%rdi) /* floating-point return value */ +_lucet_context_backstop: + // Note that `rbp` here really has no relation to any stack! + // Instead, it's a pointer to the guest context. + mov (10*8 + 8*16 + 8*2 + 16)(%rbp), %rdi // load the parent context to forward values in return value registers + mov %rax, (10*8 + 8*16 + 8*0)(%rbp) /* store return values before swapping back -- offset is offsetof(struct lucet_context, retvals) */ + mov %rdx, (10*8 + 8*16 + 8*1)(%rbp) + movdqu %xmm0, (10*8 + 8*16 + 8*2)(%rbp) /* floating-point return value */ + + // load `backstop_callback`, but skip calling it if it's null + mov (10*8 + 8*16 + 8*2 + 16 + 8)(%rbp), %rsi + test %rsi, %rsi +#ifdef __ELF__ + jz no_backstop_callback@PLT +#else + jz no_backstop_callback +#endif + + // load `backstop_data`, arg 1 + mov (10*8 + 8*16 + 8*2 + 16 + 8 + 8)(%rbp), %rdi + // call `backstop_callback` + call *%rsi + +no_backstop_callback: + mov %rbp, %rdi // load the guest context to the "from" argument + mov (10*8 + 8*16 + 8*2 + 16)(%rbp), %rsi // load the parent context to the "to" argument + +#ifdef __ELF__ jmp lucet_context_swap@PLT +#else + jmp lucet_context_swap +#endif +#ifdef __ELF__ .size lucet_context_backstop,.-lucet_context_backstop +#endif .text .globl lucet_context_swap +#ifdef __ELF__ .type lucet_context_swap,@function +#else +.globl _lucet_context_swap +#endif .align 16 lucet_context_swap: +_lucet_context_swap: // store everything in offsets from rdi (1st arg) mov %rbx, (0*8)(%rdi) mov %rsp, (1*8)(%rdi) @@ -73,15 +117,16 @@ lucet_context_swap: mov %r13, (5*8)(%rdi) mov %r14, (6*8)(%rdi) mov %r15, (7*8)(%rdi) + mov %rsi, (8*8)(%rdi) - movdqu %xmm0, (8*8 + 0*16)(%rdi) - movdqu %xmm1, (8*8 + 1*16)(%rdi) - movdqu %xmm2, (8*8 + 2*16)(%rdi) - movdqu %xmm3, (8*8 + 3*16)(%rdi) - movdqu %xmm4, (8*8 + 4*16)(%rdi) - movdqu %xmm5, (8*8 + 5*16)(%rdi) - movdqu %xmm6, (8*8 + 6*16)(%rdi) - movdqu %xmm7, (8*8 + 7*16)(%rdi) + movdqu %xmm0, (10*8 + 0*16)(%rdi) + movdqu %xmm1, (10*8 + 1*16)(%rdi) + movdqu %xmm2, (10*8 + 2*16)(%rdi) + movdqu %xmm3, (10*8 + 3*16)(%rdi) + movdqu %xmm4, (10*8 + 4*16)(%rdi) + movdqu %xmm5, (10*8 + 5*16)(%rdi) + movdqu %xmm6, (10*8 + 6*16)(%rdi) + movdqu %xmm7, (10*8 + 7*16)(%rdi) // load everything from offsets from rsi (2nd arg) mov (0*8)(%rsi), %rbx @@ -93,23 +138,33 @@ lucet_context_swap: mov (6*8)(%rsi), %r14 mov (7*8)(%rsi), %r15 - movdqu (8*8 + 0*16)(%rsi), %xmm0 - movdqu (8*8 + 1*16)(%rsi), %xmm1 - movdqu (8*8 + 2*16)(%rsi), %xmm2 - movdqu (8*8 + 3*16)(%rsi), %xmm3 - movdqu (8*8 + 4*16)(%rsi), %xmm4 - movdqu (8*8 + 5*16)(%rsi), %xmm5 - movdqu (8*8 + 6*16)(%rsi), %xmm6 - movdqu (8*8 + 7*16)(%rsi), %xmm7 + movdqu (10*8 + 0*16)(%rsi), %xmm0 + movdqu (10*8 + 1*16)(%rsi), %xmm1 + movdqu (10*8 + 2*16)(%rsi), %xmm2 + movdqu (10*8 + 3*16)(%rsi), %xmm3 + movdqu (10*8 + 4*16)(%rsi), %xmm4 + movdqu (10*8 + 5*16)(%rsi), %xmm5 + movdqu (10*8 + 6*16)(%rsi), %xmm6 + movdqu (10*8 + 7*16)(%rsi), %xmm7 + + // restore rsi when we're done with the context pointer + mov (8*8)(%rsi), %rsi ret +#ifdef __ELF__ .size lucet_context_swap,.-lucet_context_swap +#endif .text .globl lucet_context_set +#ifdef __ELF__ .type lucet_context_set,@function +#else +.globl _lucet_context_set +#endif .align 16 lucet_context_set: +_lucet_context_set: // load everything from offsets from rdi (1st arg) mov (0*8)(%rdi), %rbx mov (1*8)(%rdi), %rsp @@ -118,20 +173,44 @@ lucet_context_set: mov (5*8)(%rdi), %r13 mov (6*8)(%rdi), %r14 mov (7*8)(%rdi), %r15 + mov (8*8)(%rdi), %rsi - movdqu (8*8 + 0*16)(%rdi), %xmm0 - movdqu (8*8 + 1*16)(%rdi), %xmm1 - movdqu (8*8 + 2*16)(%rdi), %xmm2 - movdqu (8*8 + 3*16)(%rdi), %xmm3 - movdqu (8*8 + 4*16)(%rdi), %xmm4 - movdqu (8*8 + 5*16)(%rdi), %xmm5 - movdqu (8*8 + 6*16)(%rdi), %xmm6 - movdqu (8*8 + 7*16)(%rdi), %xmm7 + movdqu (10*8 + 0*16)(%rdi), %xmm0 + movdqu (10*8 + 1*16)(%rdi), %xmm1 + movdqu (10*8 + 2*16)(%rdi), %xmm2 + movdqu (10*8 + 3*16)(%rdi), %xmm3 + movdqu (10*8 + 4*16)(%rdi), %xmm4 + movdqu (10*8 + 5*16)(%rdi), %xmm5 + movdqu (10*8 + 6*16)(%rdi), %xmm6 + movdqu (10*8 + 7*16)(%rdi), %xmm7 // load rdi from itself last mov (3*8)(%rdi), %rdi ret +#ifdef __ELF__ .size lucet_context_set,.-lucet_context_set +#endif + +.text +.globl lucet_context_activate +#ifdef __ELF__ +.type lucet_context_activate,@function +#else +.globl _lucet_context_activate +#endif +.align 16 +// lucet_context_activate is essentially a function with two arguments: +// in rdi, the address of this guest's "running" flag. +// in rsi, the address of the guest code to resume at. +lucet_context_activate: +_lucet_context_activate: + movb $1, (%rdi) + jmp *%rsi +#ifdef __ELF__ +.size lucet_context_activate,.-lucet_context_activate +#endif /* Mark that we don't need executable stack. */ +#if defined(__linux__) && defined(__ELF__) .section .note.GNU-stack,"",%progbits +#endif diff --git a/lucet-runtime/lucet-runtime-internals/src/context/mod.rs b/lucet-runtime/lucet-runtime-internals/src/context/mod.rs index 787208c9f..d76c94ea0 100644 --- a/lucet-runtime/lucet-runtime-internals/src/context/mod.rs +++ b/lucet-runtime/lucet-runtime-internals/src/context/mod.rs @@ -3,14 +3,14 @@ #[cfg(test)] mod tests; +use crate::instance::Instance; use crate::val::{val_to_reg, val_to_stack, RegVal, UntypedRetVal, Val}; -use failure::Fail; use nix; use nix::sys::signal; use std::arch::x86_64::{__m128, _mm_setzero_ps}; -use std::mem; use std::ptr::NonNull; -use xfailure::xbail; +use std::{mem, ptr}; +use thiserror::Error; /// Callee-saved general-purpose registers in the AMD64 ABI. /// @@ -25,15 +25,16 @@ use xfailure::xbail; /// . Since the members are all /// `u64`, this should be fine? #[repr(C)] -struct GpRegs { +pub(crate) struct GpRegs { rbx: u64, - rsp: u64, + pub(crate) rsp: u64, rbp: u64, - rdi: u64, + pub(crate) rdi: u64, r12: u64, r13: u64, r14: u64, r15: u64, + pub(crate) rsi: u64, } impl GpRegs { @@ -47,6 +48,7 @@ impl GpRegs { r13: 0, r14: 0, r15: 0, + rsi: 0, } } } @@ -94,6 +96,9 @@ impl FpRegs { /// Everything we need to make a context switch: a signal mask, and the registers and return values /// that are manipulated directly by assembly code. /// +/// A context also tracks which other context to swap back to if a child context's entrypoint function +/// returns, and can optionally contain a callback function to be run just before that swap occurs. +/// /// # Layout /// /// The `repr(C)` and order of fields in this struct are very important, as the assembly code reads @@ -110,10 +115,14 @@ impl FpRegs { /// that pointer becomes invalid, and the behavior of returning from that context becomes undefined. #[repr(C, align(64))] pub struct Context { - gpr: GpRegs, + pub(crate) gpr: GpRegs, fpr: FpRegs, retvals_gp: [u64; 2], retval_fp: __m128, + parent_ctx: *mut Context, + // TODO ACF 2019-10-23: make Instance into a generic parameter? + backstop_callback: *const unsafe extern "C" fn(*mut Instance), + backstop_data: *mut Instance, sigset: signal::SigSet, } @@ -125,6 +134,9 @@ impl Context { fpr: FpRegs::new(), retvals_gp: [0; 2], retval_fp: unsafe { _mm_setzero_ps() }, + parent_ctx: ptr::null_mut(), + backstop_callback: Context::default_backstop_callback as *const _, + backstop_data: ptr::null_mut(), sigset: signal::SigSet::empty(), } } @@ -192,25 +204,63 @@ impl ContextHandle { pub fn create_and_init( stack: &mut [u64], - parent: &mut ContextHandle, - fptr: *const extern "C" fn(), + fptr: usize, args: &[Val], ) -> Result { let mut child = ContextHandle::new(); - Context::init(stack, parent, &mut child, fptr, args)?; + Context::init(stack, &mut child, fptr, args)?; Ok(child) } } +struct CallStackBuilder<'a> { + offset: usize, + stack: &'a mut [u64], +} + +impl<'a> CallStackBuilder<'a> { + pub fn new(stack: &'a mut [u64]) -> Self { + CallStackBuilder { offset: 0, stack } + } + + fn push(&mut self, val: u64) { + self.offset += 1; + self.stack[self.stack.len() - self.offset] = val; + } + + /// Stores `args` onto the stack such that when a return address is written after, the + /// complete unit will be 16-byte aligned, as the x86_64 ABI requires. + /// + /// That is to say, `args` will be padded such that the current top of stack is 8-byte + /// aligned. + fn store_args(&mut self, args: &[u64]) { + let items_end = args.len() + self.offset; + + if items_end % 2 == 1 { + // we need to add one entry just before the arguments so that the arguments start on an + // aligned address. + self.push(0); + } + + for arg in args.iter().rev() { + self.push(*arg); + } + } + + fn offset(&self) -> usize { + self.offset + } + + fn into_inner(self) -> (&'a mut [u64], usize) { + (self.stack, self.offset) + } +} + impl Context { /// Initialize a new child context. /// /// - `stack`: The stack for the child; *must be 16-byte aligned*. /// - /// - `parent`: The context that the child will return to. Since `swap` initializes the fields - /// in its `from` argument, this will typically be an empty context from `ContextHandle::zero()` - /// that will later be passed to `swap`. - /// /// - `child`: The context for the child. The fields of this structure will be overwritten by /// `init`. /// @@ -245,13 +295,11 @@ impl Context { /// // allocating an even number of `u64`s seems to reliably yield /// // properly aligned stacks, but TODO do better /// let mut stack = vec![0u64; 1024].into_boxed_slice(); - /// let mut parent = Context::new(); /// let mut child = Context::new(); /// let res = Context::init( /// &mut *stack, - /// &mut parent, /// &mut child, - /// entrypoint as *const extern "C" fn(), + /// entrypoint as usize, /// &[Val::U64(120), Val::F32(3.14)], /// ); /// assert!(res.is_ok()); @@ -270,30 +318,92 @@ impl Context { /// // allocating an even number of `u64`s seems to reliably yield /// // properly aligned stacks, but TODO do better /// let mut stack = vec![0u64; 1024].into_boxed_slice(); - /// let mut parent = ContextHandle::new(); /// let mut child = Context::new(); /// let res = Context::init( /// &mut *stack, - /// &mut parent, /// &mut child, - /// entrypoint as *const extern "C" fn(), + /// entrypoint as usize, /// &[Val::U64(120), Val::F32(3.14)], /// ); /// assert!(res.is_ok()); /// ``` + /// + /// # Implementation details + /// + /// This prepares a stack for the child context structured as follows, assuming an 0x1000 byte + /// stack: + /// ```text + /// 0x1000: +-------------------------+ + /// 0x0ff8: | NULL | // Null added if necessary for alignment. + /// 0x0ff0: | spilled_arg_1 | // Guest arguments follow. + /// 0x0fe8: | spilled_arg_2 | + /// 0x0fe0: ~ spilled_arg_3 ~ // The three arguments here are just for show. + /// 0x0fd8: | lucet_context_backstop | <-- This forms an ABI-matching call frame for fptr. + /// 0x0fd0: | fptr | <-- The actual guest code we want to run. + /// 0x0fc8: | lucet_context_bootstrap | <-- The guest stack pointer starts here. + /// 0x0fc0: | | + /// 0x0XXX: ~ ~ // Rest of the stack needs no preparation. + /// 0x0000: | | + /// +-------------------------+ + /// ``` + /// + /// This packing of data on the stack is interwoven with noteworthy constraints on what the + /// backstop may do: + /// * The backstop must not return on the guest stack. + /// - The next value will be a spilled argument or NULL. Neither are an intended address. + /// * The backstop cannot have ABI-conforming spilled arguments. + /// - No code runs between `fptr` and `lucet_context_backstop`, so nothing exists to + /// clean up `fptr`'s arguments. `lucet_context_backstop` would have to adjust the + /// stack pointer by a variable amount, and it does not, so `rsp` will continue to + /// point to guest arguments. + /// - This is why bootstrap recieves arguments via rbp, pointing elsewhere on the stack. + /// + /// The bootstrap function must be careful, but is less constrained since it can clean up + /// and prepare a context for `fptr`. pub fn init( stack: &mut [u64], - parent: &mut Context, child: &mut Context, - fptr: *const extern "C" fn(), + fptr: usize, + args: &[Val], + ) -> Result<(), Error> { + Context::init_with_callback( + stack, + child, + Context::default_backstop_callback, + ptr::null_mut(), + fptr, + args, + ) + } + + /// The default backstop callback does nothing, and is just a marker. + extern "C" fn default_backstop_callback(_: *mut Instance) {} + + /// Similar to `Context::init()`, but allows setting a callback function to be run when the + /// guest entrypoint returns. + /// + /// After the entrypoint function returns, but before swapping back to the parent context, + /// `backstop_callback` will be run with the single argument `backstop_data`. + pub fn init_with_callback( + stack: &mut [u64], + child: &mut Context, + backstop_callback: unsafe extern "C" fn(*mut Instance), + backstop_data: *mut Instance, + fptr: usize, args: &[Val], ) -> Result<(), Error> { if !stack_is_aligned(stack) { - xbail!(Error::UnalignedStack); + return Err(Error::UnalignedStack); + } + + if backstop_callback != Context::default_backstop_callback { + child.backstop_callback = backstop_callback as *const _; + child.backstop_data = backstop_data; } let mut gp_args_ix = 0; let mut fp_args_ix = 0; + let mut gp_regs_values = [0u64; 6]; let mut spilled_args = vec![]; @@ -301,15 +411,15 @@ impl Context { match val_to_reg(arg) { RegVal::GpReg(v) => { if gp_args_ix >= 6 { - spilled_args.push(arg); + spilled_args.push(val_to_stack(arg)); } else { - child.bootstrap_gp_ix_arg(gp_args_ix, v); + gp_regs_values[gp_args_ix] = v; gp_args_ix += 1; } } RegVal::FpReg(v) => { if fp_args_ix >= 8 { - spilled_args.push(arg); + spilled_args.push(val_to_stack(arg)); } else { child.bootstrap_fp_ix_arg(fp_args_ix, v); fp_args_ix += 1; @@ -318,61 +428,57 @@ impl Context { } } - // the top of the stack; should not be used as an index, always subtracted from - let sp = stack.len(); - - let stack_start = 3 // the bootstrap ret addr, then guest func ret addr, then the backstop ret addr - + spilled_args.len() // then any args to guest func that don't fit in registers - + spilled_args.len() % 2 // padding to keep the stack 16-byte aligned when we spill an odd number of spilled arguments - + 4; // then the backstop args and terminator - - // stack-saved arguments start 3 below the top of the stack - // (TODO: a diagram would be great here) - let mut stack_args_ix = 3; - - // If there are more additional args to the guest function than available registers, they - // have to be pushed on the stack underneath the return address. - for arg in spilled_args { - let v = val_to_stack(arg); - stack[sp + stack_args_ix - stack_start] = v; - stack_args_ix += 1; + // set up an initial call stack for guests to bootstrap into and execute + let mut stack_builder = CallStackBuilder::new(stack); + + // we actually don't want to put an explicit pointer to these arguments anywhere. we'll + // line up the rest of the stack such that these are in argument position when we jump to + // `fptr`. + stack_builder.store_args(spilled_args.as_slice()); + + // the stack must be aligned in the environment we'll execute `fptr` from - this is an ABI + // requirement and can cause segfaults if not upheld. + assert_eq!( + stack_builder.offset() % 2, + 0, + "incorrect alignment for guest call frame" + ); + + // we execute the guest code via returns, so we make a "call stack" of routines like: + // -> lucet_context_backstop() + // -> fptr() + // -> lucet_context_bootstrap() + // + // with each address the start of the named function, so when the inner function + // completes it returns to begin the next function up. + stack_builder.push(lucet_context_backstop as u64); + stack_builder.push(fptr as u64); + + // add all general purpose arguments for the guest to be bootstrapped + for arg in gp_regs_values.iter() { + stack_builder.push(*arg); } - // Prepare the stack for a swap context that lands in the bootstrap function swap will ret - // into the bootstrap function - stack[sp + 0 - stack_start] = lucet_context_bootstrap as u64; + stack_builder.push(lucet_context_bootstrap as u64); - // The bootstrap function returns into the guest function, fptr - stack[sp + 1 - stack_start] = fptr as u64; + let (stack, stack_start) = stack_builder.into_inner(); - // the guest function returns into lucet_context_backstop. - stack[sp + 2 - stack_start] = lucet_context_backstop as u64; + // RSP, RBP, and sigset still remain to be initialized. + // Stack pointer: this points to the return address that will be used by `swap`, in place + // of the original (eg, in the host) return address. The return address this points to is + // the address of the first function to run on `swap`: `lucet_context_bootstrap`. + child.gpr.rsp = &mut stack[stack.len() - stack_start] as *mut u64 as u64; - // if fptr ever returns, it returns to the backstop func. backstop needs two arguments in - // its frame - first the context we are switching *out of* (which is also the one we are - // creating right now) and the ctx we switch back into. Note *parent might not be a valid - // ctx now, but it should be when this ctx is started. - stack[sp - 4] = child as *mut Context as u64; - stack[sp - 3] = parent as *mut Context as u64; - // Terminate the call chain. - stack[sp - 2] = 0; - stack[sp - 1] = 0; + child.gpr.rbp = child as *const Context as u64; - // RSP, RBP, and sigset still remain to be initialized. - // Stack pointer: this has the return address of the first function to be run on the swap. - child.gpr.rsp = &mut stack[sp - stack_start] as *mut u64 as u64; - // Frame pointer: this is only used by the backstop code. It uses it to locate the ctx and - // parent arguments set above. - child.gpr.rbp = &mut stack[sp - 2] as *mut u64 as u64; - - // Read the sigprocmask to be restored if we ever need to jump out of a signal handler. If - // this isn't possible, die. - signal::sigprocmask( + // Read the mask to be restored if we ever need to jump out of a signal handler. If this + // isn't possible, die. + signal::pthread_sigmask( signal::SigmaskHow::SIG_SETMASK, None, Some(&mut child.sigset), ) - .expect("sigprocmask could not be retrieved"); + .expect("pthread_sigmask could not be retrieved"); Ok(()) } @@ -386,13 +492,17 @@ impl Context { /// pointer is then replaced by the value saved in `to.gpr.rsp`, so when `swap` returns, it will /// return to the pointer saved in `to`'s stack. /// - /// If `to` was freshly initialized by passing it as the child to `init`, `swap` will return to - /// the function that bootstraps arguments and then calls the entrypoint that was passed to - /// `init`. + /// If `to` was freshly initialized by passing it as the `child` argument to `init`, `swap` will + /// return to the function that bootstraps arguments and then calls the entrypoint that was + /// passed to `init`. /// /// If `to` was previously passed as the `from` argument to another call to `swap`, the program /// will return as if from that _first_ call to `swap`. /// + /// The address of `from` will be saved as `to.parent_ctx`. If `to` was initialized by `init`, + /// it will swap back to the `from` context when the entrypoint function returns via + /// `lucet_context_backstop`. + /// /// # Safety /// /// The value in `to.gpr.rsp` must be a valid pointer into the stack that was originally passed @@ -403,12 +513,16 @@ impl Context { /// of the function passed to `init`, or be unaltered from when they were previously written by /// `swap`. /// + /// If `to` was initialized by `init`, the `from` context must not be moved, dropped, or + /// otherwise invalidated while in the `to` context unless `to`'s entrypoint function never + /// returns. + /// /// If `from` is never returned to, `swap`ped to, or `set` to, resources could leak due to /// implicit `drop`s never being called: /// /// ```no_run /// # use lucet_runtime_internals::context::Context; - /// fn f(x: Box, child: &Context) { + /// fn f(x: Box, child: &mut Context) { /// let mut xs = vec![187; 410757864530]; /// xs[0] += *x; /// @@ -435,17 +549,17 @@ impl Context { /// let mut child = Context::new(); /// Context::init( /// &mut stack, - /// &mut parent, /// &mut child, - /// entrypoint as *const extern "C" fn(), + /// entrypoint as usize, /// &[], /// ).unwrap(); /// - /// unsafe { Context::swap(&mut parent, &child); } + /// unsafe { Context::swap(&mut parent, &mut child); } /// ``` #[inline] - pub unsafe fn swap(from: &mut Context, to: &Context) { - lucet_context_swap(from as *mut Context, to as *const Context); + pub unsafe fn swap(from: &mut Context, to: &mut Context) { + to.parent_ctx = from; + lucet_context_swap(from as *mut _, to as *mut _); } /// Swap to another context without saving the current context. @@ -476,13 +590,14 @@ impl Context { /// /// ## Returning /// - /// If `to` is a context freshly initialized by `init`, at least one of the following must be - /// true, otherwise the program will return to a context with uninitialized registers: + /// If `to` is a context freshly initialized by `init` (as opposed to a context populated only + /// by `swap`, such as a host context), at least one of the following must be true, otherwise + /// the program will return to a context with uninitialized registers: /// /// - The `fptr` argument to `init` is a function that never returns /// - /// - The `parent` argument to `init` was passed as the `from` argument to `swap` before this - /// call to `set` + /// - A valid context must have been passed as the `from` argument to `swap` when entering the + /// current context before this call to `set` /// /// ## Resource leaks /// @@ -515,7 +630,7 @@ impl Context { /// `!` as a type like that is currently experimental. #[inline] pub unsafe fn set_from_signal(to: &Context) -> Result<(), nix::Error> { - signal::sigprocmask(signal::SigmaskHow::SIG_SETMASK, Some(&to.sigset), None)?; + signal::pthread_sigmask(signal::SigmaskHow::SIG_SETMASK, Some(&to.sigset), None)?; Context::set(to) } @@ -551,31 +666,6 @@ impl Context { UntypedRetVal::new(gp, fp) } - /// Put one of the first 6 general-purpose arguments into a `Context` register. - /// - /// Although these registers are callee-saved registers rather than argument registers, they get - /// moved into argument registers by `lucet_context_bootstrap`. - /// - /// - `ix`: ABI general-purpose argument number - /// - `arg`: argument value - fn bootstrap_gp_ix_arg(&mut self, ix: usize, arg: u64) { - match ix { - // rdi lives across bootstrap - 0 => self.gpr.rdi = arg, - // bootstraps into rsi - 1 => self.gpr.r12 = arg, - // bootstraps into rdx - 2 => self.gpr.r13 = arg, - // bootstraps into rcx - 3 => self.gpr.r14 = arg, - // bootstraps into r8 - 4 => self.gpr.r15 = arg, - // bootstraps into r9 - 5 => self.gpr.rbx = arg, - _ => panic!("unexpected gp register index {}", ix), - } - } - /// Put one of the first 8 floating-point arguments into a `Context` register. /// /// - `ix`: ABI floating-point argument number @@ -596,10 +686,10 @@ impl Context { } /// Errors that may arise when working with contexts. -#[derive(Debug, Fail)] +#[derive(Debug, Error)] pub enum Error { /// Raised when the bottom of the stack provided to `Context::init` is not 16-byte aligned - #[fail(display = "context initialized with unaligned stack")] + #[error("context initialized with unaligned stack")] UnalignedStack, } @@ -626,10 +716,16 @@ extern "C" { fn lucet_context_backstop(); /// Saves the current context and performs the context switch. Implemented in assembly. - fn lucet_context_swap(from: *mut Context, to: *const Context); + fn lucet_context_swap(from: *mut Context, to: *mut Context); /// Performs the context switch; implemented in assembly. /// /// Never returns because the current context is discarded. fn lucet_context_set(to: *const Context) -> !; + + /// Enables termination for the instance, after performing a context switch. + /// + /// Takes the guest return address as an argument as a consequence of implementation details, + /// see `Instance::swap_and_return` for more. + pub(crate) fn lucet_context_activate(); } diff --git a/lucet-runtime/lucet-runtime-internals/src/context/tests/c_child.rs b/lucet-runtime/lucet-runtime-internals/src/context/tests/c_child.rs index dd33f548d..6c881c02c 100644 --- a/lucet-runtime/lucet-runtime-internals/src/context/tests/c_child.rs +++ b/lucet-runtime/lucet-runtime-internals/src/context/tests/c_child.rs @@ -53,14 +53,13 @@ macro_rules! init_and_swap { unsafe { let child = Box::into_raw(Box::new(ContextHandle::create_and_init( &mut *$stack, - parent_regs.as_mut().unwrap(), - $fn as *const extern "C" fn(), + $fn as usize, &[$( $args ),*], ).unwrap())); child_regs = child; - Context::swap(parent_regs.as_mut().unwrap(), child_regs.as_ref().unwrap()); + Context::swap(parent_regs.as_mut().unwrap(), child_regs.as_mut().unwrap()); } } } @@ -110,7 +109,7 @@ fn call_child_twice() { arg1_val = 10; unsafe { - Context::swap(parent_regs.as_mut().unwrap(), child_regs.as_ref().unwrap()); + Context::swap(parent_regs.as_mut().unwrap(), child_regs.as_mut().unwrap()); } assert_eq!( diff --git a/lucet-runtime/lucet-runtime-internals/src/context/tests/mod.rs b/lucet-runtime/lucet-runtime-internals/src/context/tests/mod.rs index b01edaf10..c98930b16 100644 --- a/lucet-runtime/lucet-runtime-internals/src/context/tests/mod.rs +++ b/lucet-runtime/lucet-runtime-internals/src/context/tests/mod.rs @@ -7,9 +7,9 @@ use std::slice; #[test] fn context_offsets_correct() { assert_eq!(offset_of!(Context, gpr), 0); - assert_eq!(offset_of!(Context, fpr), 8 * 8); - assert_eq!(offset_of!(Context, retvals_gp), 8 * 8 + 8 * 16); - assert_eq!(offset_of!(Context, retval_fp), 8 * 8 + 8 * 16 + 8 * 2); + assert_eq!(offset_of!(Context, fpr), 10 * 8); + assert_eq!(offset_of!(Context, retvals_gp), 10 * 8 + 8 * 16); + assert_eq!(offset_of!(Context, retval_fp), 10 * 8 + 8 * 16 + 8 * 2); } #[test] @@ -31,13 +31,7 @@ fn init_rejects_unaligned() { let mut stack_unaligned = unsafe { slice::from_raw_parts_mut(ptr, len) }; // now we have the unaligned stack, let's make sure it blows up right - let mut parent = ContextHandle::new(); - let res = ContextHandle::create_and_init( - &mut stack_unaligned, - &mut parent, - dummy as *const extern "C" fn(), - &[], - ); + let res = ContextHandle::create_and_init(&mut stack_unaligned, dummy as usize, &[]); if let Err(Error::UnalignedStack) = res { assert!(true); diff --git a/lucet-runtime/lucet-runtime-internals/src/context/tests/rust_child.rs b/lucet-runtime/lucet-runtime-internals/src/context/tests/rust_child.rs index fc982d8f7..5c44053dc 100644 --- a/lucet-runtime/lucet-runtime-internals/src/context/tests/rust_child.rs +++ b/lucet-runtime/lucet-runtime-internals/src/context/tests/rust_child.rs @@ -49,13 +49,15 @@ macro_rules! init_and_swap { unsafe { let child = ContextHandle::create_and_init( &mut *$stack, - PARENT.as_mut().unwrap(), - $fn as *const extern "C" fn(), + $fn as usize, &[$( $args ),*], ).unwrap(); CHILD = Some(child); - Context::swap(PARENT.as_mut().unwrap(), CHILD.as_ref().unwrap()); + Context::swap( + PARENT.as_mut().unwrap(), + CHILD.as_mut().unwrap(), + ); } } } @@ -72,7 +74,7 @@ extern "C" fn arg_printing_child(arg0: *mut c_void, arg1: *mut c_void) { ) .unwrap(); - unsafe { Context::swap(CHILD.as_mut().unwrap(), PARENT.as_ref().unwrap()) }; + unsafe { Context::swap(CHILD.as_mut().unwrap(), PARENT.as_mut().unwrap()) }; // Read the arguments again let arg0_val = unsafe { *(arg0 as *mut c_int) }; @@ -86,7 +88,7 @@ extern "C" fn arg_printing_child(arg0: *mut c_void, arg1: *mut c_void) { ) .unwrap(); - unsafe { Context::swap(CHILD.as_mut().unwrap(), PARENT.as_ref().unwrap()) }; + unsafe { Context::swap(CHILD.as_mut().unwrap(), PARENT.as_mut().unwrap()) }; } #[test] @@ -120,7 +122,7 @@ fn call_child_twice() { arg1_val = 10; unsafe { - Context::swap(PARENT.as_mut().unwrap(), CHILD.as_ref().unwrap()); + Context::swap(PARENT.as_mut().unwrap(), CHILD.as_mut().unwrap()); } assert_output_eq!( @@ -257,7 +259,7 @@ macro_rules! child_n_args { write!(out, $prefix).unwrap(); $( write!(out, " {}", $arg).unwrap(); - );* + )* write!(out, "\n").unwrap(); } @@ -357,7 +359,7 @@ macro_rules! child_n_fp_args { write!(out, $prefix).unwrap(); $( write!(out, " {:.1}", $arg).unwrap(); - );* + )* write!(out, "\n").unwrap(); } diff --git a/lucet-runtime/lucet-runtime-internals/src/embed_ctx.rs b/lucet-runtime/lucet-runtime-internals/src/embed_ctx.rs index 7b9f98f0e..0e12f0902 100644 --- a/lucet-runtime/lucet-runtime-internals/src/embed_ctx.rs +++ b/lucet-runtime/lucet-runtime-internals/src/embed_ctx.rs @@ -1,12 +1,14 @@ use std::any::{Any, TypeId}; +use std::cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut}; use std::collections::HashMap; /// A map that holds at most one value of any type. /// /// This is similar to the type provided by the `anymap` crate, but we can get away with simpler /// types on the methods due to our more specialized use case. +#[derive(Default)] pub struct CtxMap { - map: HashMap>, + map: HashMap>>, } impl CtxMap { @@ -18,40 +20,47 @@ impl CtxMap { self.map.contains_key(&TypeId::of::()) } - pub fn get(&self) -> Option<&T> { + pub fn try_get(&self) -> Option, BorrowError>> { self.map.get(&TypeId::of::()).map(|x| { - x.downcast_ref::() - .expect("value stored with TypeId::of:: is always type T") + x.try_borrow().map(|r| { + Ref::map(r, |b| { + b.downcast_ref::() + .expect("value stored with TypeId::of:: is always type T") + }) + }) }) } - pub fn get_mut(&mut self) -> Option<&mut T> { - self.map.get_mut(&TypeId::of::()).map(|x| { - x.downcast_mut::() - .expect("value stored with TypeId::of:: is always type T") + pub fn try_get_mut(&self) -> Option, BorrowMutError>> { + self.map.get(&TypeId::of::()).map(|x| { + x.try_borrow_mut().map(|r| { + RefMut::map(r, |b| { + b.downcast_mut::() + .expect("value stored with TypeId::of:: is always type T") + }) + }) }) } pub fn insert(&mut self, x: T) -> Option { self.map - .insert(TypeId::of::(), Box::new(x) as Box) + .insert(TypeId::of::(), RefCell::new(Box::new(x) as Box)) .map(|x_prev| { - *x_prev + *(x_prev.into_inner()) .downcast::() .expect("value stored with TypeId::of:: is always type T") }) } - pub fn new() -> Self { - CtxMap { - map: HashMap::new(), - } - } - pub fn remove(&mut self) -> Option { self.map.remove(&TypeId::of::()).map(|x| { - *x.downcast::() + *(x.into_inner()) + .downcast::() .expect("value stored with TypeId::of:: is always type T") }) } + + pub fn new() -> Self { + Self::default() + } } diff --git a/lucet-runtime/lucet-runtime-internals/src/error.rs b/lucet-runtime/lucet-runtime-internals/src/error.rs index ce2e2dee6..0e9e44afb 100644 --- a/lucet-runtime/lucet-runtime-internals/src/error.rs +++ b/lucet-runtime/lucet-runtime-internals/src/error.rs @@ -1,67 +1,75 @@ use crate::instance::{FaultDetails, TerminationDetails}; -use failure::Fail; +use thiserror::Error; /// Lucet runtime errors. -#[derive(Debug, Fail)] +#[derive(Debug, Error)] pub enum Error { - #[fail(display = "Invalid argument: {}", _0)] + #[error("Invalid argument: {0}")] InvalidArgument(&'static str), /// A [`Region`](trait.Region.html) cannot currently accommodate additional instances. - #[fail(display = "Region capacity reached: {} instances", _0)] + #[error("Region capacity reached: {0} instances")] RegionFull(usize), /// A module error occurred. - #[fail(display = "Module error: {}", _0)] + #[error("Module error: {0}")] ModuleError(ModuleError), /// A method call or module specification would exceed an instance's /// [`Limit`s](struct.Limits.html). - #[fail(display = "Instance limits exceeded: {}", _0)] + #[error("Instance limits exceeded: {0}")] LimitsExceeded(String), + /// A method call attempted to modify linear memory for an instance that + /// does not have linear memory + #[error("No linear memory available: {0}")] + NoLinearMemory(String), + /// An attempt to look up a WebAssembly function by its symbol name failed. - #[fail(display = "Symbol not found: {}", _0)] + #[error("Symbol not found: {0}")] SymbolNotFound(String), /// An attempt to look up a WebAssembly function by its table index failed. - #[fail(display = "Function not found: (table {}, func {})", _0, _1)] + #[error("Function not found: (table {0}, func {1}")] FuncNotFound(u32, u32), /// An instance aborted due to a runtime fault. - #[fail(display = "Runtime fault: {:?}", _0)] + #[error("Runtime fault: {0:?}")] RuntimeFault(FaultDetails), /// An instance terminated, potentially with extra information about the termination. /// /// This condition can arise from a hostcall explicitly calling - /// [`Vmctx::terminate()`](struct.Vmctx.html#method.terminate), or via a custom signal handler + /// [`Vmctx::terminate()`](vmctx/struct.Vmctx.html#method.terminate), or via a custom signal handler /// that returns [`SignalBehavior::Terminate`](enum.SignalBehavior.html#variant.Terminate). - #[fail(display = "Runtime terminated")] + #[error("Runtime terminated")] RuntimeTerminated(TerminationDetails), /// IO errors arising during dynamic loading with [`DlModule`](struct.DlModule.html). - #[fail(display = "Dynamic loading error: {}", _0)] - DlError(#[cause] std::io::Error), + #[error("Dynamic loading error: {0}")] + DlError(#[from] std::io::Error), + + #[error("Instance not returned")] + InstanceNotReturned, + + #[error("Instance not yielded")] + InstanceNotYielded, + + #[error("Start function yielded")] + StartYielded, /// A catch-all for internal errors that are likely unrecoverable by the runtime user. /// /// As the API matures, these will likely become rarer, replaced by new variants of this enum, /// or by panics for truly unrecoverable situations. - #[fail(display = "Internal error: {}", _0)] - InternalError(#[cause] failure::Error), + #[error("Internal error")] + InternalError(#[source] anyhow::Error), /// An unsupported feature was used. - #[fail(display = "Unsupported feature: {}", _0)] + #[error("Unsupported feature: {0}")] Unsupported(String), } -impl From for Error { - fn from(e: failure::Error) -> Error { - Error::InternalError(e) - } -} - impl From for Error { fn from(e: crate::context::Error) -> Error { Error::InternalError(e.into()) @@ -80,22 +88,22 @@ impl From for Error { } } -impl From for Error { - fn from(e: lucet_module_data::Error) -> Error { +impl From for Error { + fn from(e: lucet_module::Error) -> Error { Error::ModuleError(ModuleError::ModuleDataError(e)) } } /// Lucet module errors. -#[derive(Debug, Fail)] +#[derive(Debug, Error)] pub enum ModuleError { /// An error was found in the definition of a Lucet module. - #[fail(display = "Incorrect module definition: {}", _0)] + #[error("Incorrect module definition: {0}")] IncorrectModule(String), /// An error occurred with the module data section, likely during deserialization. - #[fail(display = "Module data error: {}", _0)] - ModuleDataError(#[cause] lucet_module_data::Error), + #[error("Module data error: {0}")] + ModuleDataError(#[from] lucet_module::Error), } #[macro_export] @@ -124,7 +132,7 @@ macro_rules! lucet_ensure { #[macro_export] macro_rules! lucet_format_err { - ($($arg:tt)*) => { $crate::error::Error::InternalError(failure::format_err!($($arg)*)) } + ($($arg:tt)*) => { $crate::error::Error::InternalError(anyhow::format_err!($($arg)*)) } } #[macro_export] diff --git a/lucet-runtime/lucet-runtime-internals/src/hostcall_macros.rs b/lucet-runtime/lucet-runtime-internals/src/hostcall_macros.rs new file mode 100644 index 000000000..9c9271ce8 --- /dev/null +++ b/lucet-runtime/lucet-runtime-internals/src/hostcall_macros.rs @@ -0,0 +1,78 @@ +/// The macro that surrounds definitions of Lucet hostcalls in Rust. +/// +/// **Note:** this macro has been deprecated and replaced by the `#[lucet_hostcall]` attribute. +/// +/// It is important to use this macro for hostcalls, rather than exporting them directly, as it +/// installs unwind protection that prevents panics from unwinding into the guest stack. +/// +/// Since this is not a proc macro, the syntax is unfortunately fairly brittle. The functions it +/// encloses must be of the form: +/// +/// ```ignore +/// #[$attr1] +/// #[$attr2] +/// ... // any number of attributes are supported; in most cases you will want `#[no_mangle]` +/// pub unsafe extern "C" fn $name( // must be `pub unsafe extern "C"` +/// &mut $vmctx, +/// $arg1: $arg1_ty, +/// $arg2: $arg2_ty, +/// ... , // trailing comma must always be present +/// ) -> $ret_ty { // return type must always be present even if it is `()` +/// // body +/// } +/// ``` +#[macro_export] +#[deprecated(since = "0.5.0", note = "Use the #[lucet_hostcall] attribute instead")] +macro_rules! lucet_hostcalls { + { + $( + $(#[$attr:meta])* + pub unsafe extern "C" fn $name:ident( + &mut $vmctx:ident + $(, $arg:ident : $arg_ty:ty )*, + ) -> $ret_ty:ty { + $($body:tt)* + } + )* + } => { + $( + #[allow(unused_mut)] + #[allow(unused_unsafe)] + #[$crate::lucet_hostcall] + $(#[$attr])* + pub unsafe extern "C" fn $name( + $vmctx: &mut lucet_runtime::vmctx::Vmctx, + $( $arg: $arg_ty ),* + ) -> $ret_ty { + $($body)* + } + )* + } +} + +/// Terminate an instance from within a hostcall, returning an optional value as an error. +/// +/// Use this instead of `panic!` when you want the instance to terminate, but not the entire host +/// program. Like `panic!`, you can pass a format string with arguments, a value that implements +/// `Any`, or nothing to return a default message. +/// +/// Upon termination, the call to `Instance::run()` will return with an +/// `Err(Error::RuntimeTerminated)` value containing the value you pass to this macro. +/// +/// This macro safely unwinds the hostcall stack out to the entrypoint of the hostcall, so any +/// resources that may have been acquired will be properly dropped. +#[macro_export] +macro_rules! lucet_hostcall_terminate { + () => { + lucet_hostcall_terminate!("lucet_hostcall_terminate") + }; + ( $payload:expr ) => { + panic!($crate::instance::TerminationDetails::provide($payload)) + }; + ( $payload:expr, ) => { + lucet_hostcall_terminate!($payload) + }; + ( $fmt:expr, $($arg:tt)+ ) => { + lucet_hostcall_terminate!(format!($fmt, $($arg),+)) + }; +} diff --git a/lucet-runtime/lucet-runtime-internals/src/instance.rs b/lucet-runtime/lucet-runtime-internals/src/instance.rs index 1fa60ba40..6d58ef779 100644 --- a/lucet-runtime/lucet-runtime-internals/src/instance.rs +++ b/lucet-runtime/lucet-runtime-internals/src/instance.rs @@ -1,28 +1,32 @@ +pub mod execution; mod siginfo_ext; pub mod signals; +pub mod state; +pub use crate::instance::execution::{KillError, KillState, KillSuccess, KillSwitch}; pub use crate::instance::signals::{signal_handler_none, SignalBehavior, SignalHandler}; +pub use crate::instance::state::State; -use crate::alloc::Alloc; +use crate::alloc::{Alloc, HOST_PAGE_SIZE_EXPECTED}; use crate::context::Context; use crate::embed_ctx::CtxMap; use crate::error::Error; -use crate::instance::siginfo_ext::SiginfoExt; -use crate::module::{self, Global, Module}; -use crate::trapcode::{TrapCode, TrapCodeType}; +use crate::module::{self, FunctionHandle, FunctionPointer, Global, GlobalValue, Module, TrapCode}; +use crate::region::RegionInternal; use crate::val::{UntypedRetVal, Val}; use crate::WASM_PAGE_SIZE; -use libc::{c_void, siginfo_t, uintptr_t, SIGBUS, SIGSEGV}; +use libc::{c_void, pthread_self, siginfo_t, uintptr_t}; +use lucet_module::InstanceRuntimeData; +use memoffset::offset_of; use std::any::Any; -use std::cell::{RefCell, UnsafeCell}; -use std::ffi::{CStr, CString}; +use std::cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut, UnsafeCell}; +use std::marker::PhantomData; use std::mem; use std::ops::{Deref, DerefMut}; use std::ptr::{self, NonNull}; use std::sync::Arc; -pub const LUCET_INSTANCE_MAGIC: u64 = 746932922; -pub const INSTANCE_PADDING: usize = 2328; +pub const LUCET_INSTANCE_MAGIC: u64 = 746_932_922; thread_local! { /// The host context. @@ -53,8 +57,12 @@ thread_local! { /// though it were a `&mut Instance`. pub struct InstanceHandle { inst: NonNull, + needs_inst_drop: bool, } +// raw pointer lint +unsafe impl Send for InstanceHandle {} + /// Create a new `InstanceHandle`. /// /// This is not meant for public consumption, but rather is used to make implementations of @@ -71,15 +79,17 @@ pub fn new_instance_handle( embed_ctx: CtxMap, ) -> Result { let inst = NonNull::new(instance) - .ok_or(lucet_format_err!("instance pointer is null; this is a bug"))?; + .ok_or_else(|| lucet_format_err!("instance pointer is null; this is a bug"))?; - // do this check first so we don't run `InstanceHandle::drop()` for a failure lucet_ensure!( unsafe { inst.as_ref().magic } != LUCET_INSTANCE_MAGIC, "created a new instance handle in memory with existing instance magic; this is a bug" ); - let mut handle = InstanceHandle { inst }; + let mut handle = InstanceHandle { + inst, + needs_inst_drop: false, + }; let inst = Instance::new(alloc, module, embed_ctx); @@ -92,20 +102,25 @@ pub fn new_instance_handle( ptr::write(&mut *handle, inst); }; + handle.needs_inst_drop = true; + handle.reset()?; Ok(handle) } -pub fn instance_handle_to_raw(inst: InstanceHandle) -> *mut Instance { - let ptr = inst.inst.as_ptr(); - std::mem::forget(inst); - ptr +pub fn instance_handle_to_raw(mut inst: InstanceHandle) -> *mut Instance { + inst.needs_inst_drop = false; + inst.inst.as_ptr() } -pub unsafe fn instance_handle_from_raw(ptr: *mut Instance) -> InstanceHandle { +pub unsafe fn instance_handle_from_raw( + ptr: *mut Instance, + needs_inst_drop: bool, +) -> InstanceHandle { InstanceHandle { inst: NonNull::new_unchecked(ptr), + needs_inst_drop, } } @@ -128,11 +143,25 @@ impl DerefMut for InstanceHandle { impl Drop for InstanceHandle { fn drop(&mut self) { - // eprintln!("InstanceHandle::drop()"); - // zero out magic, then run the destructor by taking and dropping the inner `Instance` - self.magic = 0; - unsafe { - mem::replace(self.inst.as_mut(), mem::uninitialized()); + if self.needs_inst_drop { + unsafe { + let inst = self.inst.as_mut(); + + // Grab a handle to the region to ensure it outlives `inst`. + // + // This ensures that the region won't be dropped by `inst` being + // dropped, which could result in `inst` being unmapped by the + // Region *during* drop of the Instance's fields. + let region: Arc = inst.alloc().region.clone(); + + // drop the actual instance + std::ptr::drop_in_place(inst); + + // and now we can drop what may be the last Arc. If it is + // it can safely do what it needs with memory; we're not running + // destructors on it anymore. + mem::drop(region); + } } } } @@ -143,11 +172,43 @@ impl Drop for InstanceHandle { /// WebAssembly heap. /// /// `Instance`s are never created by runtime users directly, but rather are acquired from -/// [`Region`](trait.Region.html)s and often accessed through -/// [`InstanceHandle`](struct.InstanceHandle.html) smart pointers. This guarantees that instances +/// [`Region`](../region/trait.Region.html)s and often accessed through +/// [`InstanceHandle`](../instance/struct.InstanceHandle.html) smart pointers. This guarantees that instances /// and their fields are never moved in memory, otherwise raw pointers in the metadata could be /// unsafely invalidated. +/// +/// An instance occupies one 4096-byte page in memory, with a layout like: +/// ```text +/// 0xXXXXX000: +/// Instance { +/// .magic +/// .embed_ctx +/// ... etc ... +/// } +/// +/// // unused space +/// +/// InstanceInternals { +/// .globals +/// .instruction_counter +/// } // last address *inside* `InstanceInternals` is 0xXXXXXFFF +/// 0xXXXXY000: // start of next page, VMContext points here +/// Heap { +/// .. +/// } +/// ``` +/// +/// This layout allows modules to tightly couple to a handful of fields related to the instance, +/// rather than possibly requiring compiler-side changes (and recompiles) whenever `Instance` +/// changes. +/// +/// It also obligates `Instance` to be immediately followed by the heap, but otherwise leaves the +/// locations of the stack, globals, and any other data, to be implementation-defined by the +/// `Region` that actually creates `Slot`s onto which `Instance` are mapped. +/// For information about the layout of all instance-related memory, see the documentation of +/// [MmapRegion](../region/mmap/struct.MmapRegion.html). #[repr(C)] +#[repr(align(4096))] pub struct Instance { /// Used to catch bugs in pointer math used to find the address of the instance magic: u64, @@ -160,11 +221,14 @@ pub struct Instance { module: Arc, /// The `Context` in which the guest program runs - ctx: Context, + pub(crate) ctx: Context, /// Instance state and error information pub(crate) state: State, + /// Small mutexed state used for remote kill switch functionality + pub(crate) kill_state: Arc, + /// The memory allocated for this instance alloc: Alloc, @@ -179,7 +243,7 @@ pub struct Instance { signal_handler: Box< dyn Fn( &Instance, - &TrapCode, + &Option, libc::c_int, *const siginfo_t, *const c_void, @@ -187,16 +251,138 @@ pub struct Instance { >, /// Pointer to the function used as the entrypoint (for use in backtraces) - entrypoint: *const extern "C" fn(), + entrypoint: Option, + + /// The value passed back to the guest when resuming a yielded instance. + pub(crate) resumed_val: Option>, - /// Padding to ensure the pointer to globals at the end of the page occupied by the `Instance` - _reserved: [u8; INSTANCE_PADDING], + /// `_padding` must be the last member of the structure. + /// This marks where the padding starts to make the structure exactly 4096 bytes long. + /// It is also used to compute the size of the structure up to that point, i.e. without padding. + _padding: (), +} + +/// Users of `Instance` must be very careful about when instances are dropped! +/// +/// Typically you will not have to worry about this, as InstanceHandle will robustly handle +/// Instance drop semantics. If an instance is dropped, and the Region it's in has already dropped, +/// it may contain the last reference counted pointer to its Region. If so, when Instance's +/// destructor runs, Region will be dropped, and may free or otherwise invalidate the memory that +/// this Instance exists in, *while* the Instance destructor is executing. +impl Drop for Instance { + fn drop(&mut self) { + // Reset magic to indicate this instance + // is no longer valid + self.magic = 0; + } +} - /// Pointer to the globals +/// The result of running or resuming an [`Instance`](struct.Instance.html). +#[derive(Debug)] +pub enum RunResult { + /// An instance returned with a value. /// - /// This is accessed through the `vmctx` pointer, which points to the heap that begins - /// immediately after this struct, so it has to come at the very end. - globals_ptr: *const i64, + /// The actual type of the contained value depends on the return type of the guest function that + /// was called. For guest functions with no return value, it is undefined behavior to do + /// anything with this value. + Returned(UntypedRetVal), + /// An instance yielded, potentially with a value. + /// + /// This arises when a hostcall invokes one of the + /// [`Vmctx::yield_*()`](vmctx/struct.Vmctx.html#method.yield_) family of methods. Depending on which + /// variant is used, the `YieldedVal` may contain a value passed from the guest context to the + /// host. + /// + /// An instance that has yielded may only be resumed + /// ([with](struct.Instance.html#method.resume_with_val) or + /// [without](struct.Instance.html#method.resume) a value to returned to the guest), + /// [reset](struct.Instance.html#method.reset), or dropped. Attempting to run an instance from a + /// new entrypoint after it has yielded but without first resetting will result in an error. + Yielded(YieldedVal), +} + +impl RunResult { + /// Try to get a return value from a run result, returning `Error::InstanceNotReturned` if the + /// instance instead yielded. + pub fn returned(self) -> Result { + match self { + RunResult::Returned(rv) => Ok(rv), + RunResult::Yielded(_) => Err(Error::InstanceNotReturned), + } + } + + /// Try to get a reference to a return value from a run result, returning + /// `Error::InstanceNotReturned` if the instance instead yielded. + pub fn returned_ref(&self) -> Result<&UntypedRetVal, Error> { + match self { + RunResult::Returned(rv) => Ok(rv), + RunResult::Yielded(_) => Err(Error::InstanceNotReturned), + } + } + + /// Returns `true` if the instance returned a value. + pub fn is_returned(&self) -> bool { + self.returned_ref().is_ok() + } + + /// Unwraps a run result into a return value. + /// + /// # Panics + /// + /// Panics if the instance instead yielded, with a panic message including the passed message. + pub fn expect_returned(self, msg: &str) -> UntypedRetVal { + self.returned().expect(msg) + } + + /// Unwraps a run result into a returned value. + /// + /// # Panics + /// + /// Panics if the instance instead yielded. + pub fn unwrap_returned(self) -> UntypedRetVal { + self.returned().unwrap() + } + + /// Try to get a yielded value from a run result, returning `Error::InstanceNotYielded` if the + /// instance instead returned. + pub fn yielded(self) -> Result { + match self { + RunResult::Returned(_) => Err(Error::InstanceNotYielded), + RunResult::Yielded(yv) => Ok(yv), + } + } + + /// Try to get a reference to a yielded value from a run result, returning + /// `Error::InstanceNotYielded` if the instance instead returned. + pub fn yielded_ref(&self) -> Result<&YieldedVal, Error> { + match self { + RunResult::Returned(_) => Err(Error::InstanceNotYielded), + RunResult::Yielded(yv) => Ok(yv), + } + } + + /// Returns `true` if the instance yielded. + pub fn is_yielded(&self) -> bool { + self.yielded_ref().is_ok() + } + + /// Unwraps a run result into a yielded value. + /// + /// # Panics + /// + /// Panics if the instance instead returned, with a panic message including the passed message. + pub fn expect_yielded(self, msg: &str) -> YieldedVal { + self.yielded().expect(msg) + } + + /// Unwraps a run result into a yielded value. + /// + /// # Panics + /// + /// Panics if the instance instead returned. + pub fn unwrap_yielded(self) -> YieldedVal { + self.yielded().unwrap() + } } /// APIs that are internal, but useful to implementors of extension modules; you probably don't want @@ -247,11 +433,11 @@ impl Instance { /// # use lucet_runtime_internals::instance::InstanceHandle; /// # let instance: InstanceHandle = unimplemented!(); /// // regular execution yields `Ok(UntypedRetVal)` - /// let retval = instance.run(b"factorial", &[5u64.into()]).unwrap(); + /// let retval = instance.run("factorial", &[5u64.into()]).unwrap().unwrap_returned(); /// assert_eq!(u64::from(retval), 120u64); /// /// // runtime faults yield `Err(Error)` - /// let result = instance.run(b"faulting_function", &[]); + /// let result = instance.run("faulting_function", &[]); /// assert!(result.is_err()); /// ``` /// @@ -271,7 +457,7 @@ impl Instance { /// /// For the moment, we do not mark this as `unsafe` in the Rust type system, but that may change /// in the future. - pub fn run(&mut self, entrypoint: &[u8], args: &[Val]) -> Result { + pub fn run(&mut self, entrypoint: &str, args: &[Val]) -> Result { let func = self.module.get_export_func(entrypoint)?; self.run_func(func, &args) } @@ -279,17 +465,65 @@ impl Instance { /// Run a function with arguments in the guest context from the [WebAssembly function /// table](https://webassembly.github.io/spec/core/syntax/modules.html#tables). /// + /// # Safety + /// /// The same safety caveats of [`Instance::run()`](struct.Instance.html#method.run) apply. pub fn run_func_idx( &mut self, table_idx: u32, func_idx: u32, args: &[Val], - ) -> Result { + ) -> Result { let func = self.module.get_func_from_idx(table_idx, func_idx)?; self.run_func(func, &args) } + /// Resume execution of an instance that has yielded without providing a value to the guest. + /// + /// This should only be used when the guest yielded with + /// [`Vmctx::yield_()`](vmctx/struct.Vmctx.html#method.yield_) or + /// [`Vmctx::yield_val()`](vmctx/struct.Vmctx.html#method.yield_val). Otherwise, this call will + /// fail with `Error::InvalidArgument`. + /// + /// # Safety + /// + /// The foreign code safety caveat of [`Instance::run()`](struct.Instance.html#method.run) + /// applies. + pub fn resume(&mut self) -> Result { + self.resume_with_val(EmptyYieldVal) + } + + /// Resume execution of an instance that has yielded, providing a value to the guest. + /// + /// The type of the provided value must match the type expected by + /// [`Vmctx::yield_expecting_val()`](vmctx/struct.Vmctx.html#method.yield_expecting_val) or + /// [`Vmctx::yield_val_expecting_val()`](vmctx/struct.Vmctx.html#method.yield_val_expecting_val). + /// + /// The provided value will be dynamically typechecked against the type the guest expects to + /// receive, and if that check fails, this call will fail with `Error::InvalidArgument`. + /// + /// # Safety + /// + /// The foreign code safety caveat of [`Instance::run()`](struct.Instance.html#method.run) + /// applies. + pub fn resume_with_val(&mut self, val: A) -> Result { + match &self.state { + State::Yielded { expecting, .. } => { + // make sure the resumed value is of the right type + if !expecting.is::>() { + return Err(Error::InvalidArgument( + "type mismatch between yielded instance expected value and resumed value", + )); + } + } + _ => return Err(Error::InvalidArgument("can only resume a yielded instance")), + } + + self.resumed_val = Some(Box::new(val) as Box); + + self.swap_and_return() + } + /// Reset the instance's heap and global variables to their initial state. /// /// The WebAssembly `start` section will also be run, if one exists. @@ -312,16 +546,14 @@ impl Instance { Global::Import { .. } => { return Err(Error::Unsupported(format!( "global imports are unsupported; found: {:?}", - i + v ))); } - Global::Def { def } => def.init_val(), + Global::Def(def) => def.init_val(), }; } - self.state = State::Ready { - retval: UntypedRetVal::default(), - }; + self.state = State::Ready; self.run_start()?; @@ -332,9 +564,12 @@ impl Instance { /// /// On success, returns the number of pages that existed before the call. pub fn grow_memory(&mut self, additional_pages: u32) -> Result { + let additional_bytes = additional_pages + .checked_mul(WASM_PAGE_SIZE) + .ok_or_else(|| lucet_format_err!("additional pages larger than wasm address space",))?; let orig_len = self .alloc - .expand_heap(additional_pages * WASM_PAGE_SIZE, self.module.as_ref())?; + .expand_heap(additional_bytes, self.module.as_ref())?; Ok(orig_len / WASM_PAGE_SIZE) } @@ -359,12 +594,12 @@ impl Instance { } /// Return the WebAssembly globals as a slice of `i64`s. - pub fn globals(&self) -> &[i64] { + pub fn globals(&self) -> &[GlobalValue] { unsafe { self.alloc.globals() } } /// Return the WebAssembly globals as a mutable slice of `i64`s. - pub fn globals_mut(&mut self) -> &mut [i64] { + pub fn globals_mut(&mut self) -> &mut [GlobalValue] { unsafe { self.alloc.globals_mut() } } @@ -380,13 +615,13 @@ impl Instance { } /// Get a reference to a context value of a particular type, if it exists. - pub fn get_embed_ctx(&self) -> Option<&T> { - self.embed_ctx.get::() + pub fn get_embed_ctx(&self) -> Option, BorrowError>> { + self.embed_ctx.try_get::() } /// Get a mutable reference to a context value of a particular type, if it exists. - pub fn get_embed_ctx_mut(&mut self) -> Option<&mut T> { - self.embed_ctx.get_mut::() + pub fn get_embed_ctx_mut(&self) -> Option, BorrowMutError>> { + self.embed_ctx.try_get_mut::() } /// Insert a context value. @@ -421,7 +656,13 @@ impl Instance { pub fn set_signal_handler(&mut self, handler: H) where H: 'static - + Fn(&Instance, &TrapCode, libc::c_int, *const siginfo_t, *const c_void) -> SignalBehavior, + + Fn( + &Instance, + &Option, + libc::c_int, + *const siginfo_t, + *const c_void, + ) -> SignalBehavior, { self.signal_handler = Box::new(handler) as Box; } @@ -448,62 +689,210 @@ impl Instance { pub fn set_c_fatal_handler(&mut self, handler: unsafe extern "C" fn(*mut Instance)) { self.c_fatal_handler = Some(handler); } + + pub fn kill_switch(&self) -> KillSwitch { + KillSwitch::new(Arc::downgrade(&self.kill_state)) + } + + pub fn is_ready(&self) -> bool { + self.state.is_ready() + } + + pub fn is_yielded(&self) -> bool { + self.state.is_yielded() + } + + pub fn is_faulted(&self) -> bool { + self.state.is_faulted() + } + + pub fn is_terminated(&self) -> bool { + self.state.is_terminated() + } + + // This needs to be public as it's used in the expansion of `lucet_hostcalls`, available for + // external use. But you *really* shouldn't have to call this yourself, so we're going to keep + // it out of rustdoc. + #[doc(hidden)] + pub fn uninterruptable T>(&mut self, f: F) -> T { + self.kill_state.begin_hostcall(); + let res = f(); + let stop_reason = self.kill_state.end_hostcall(); + + if let Some(termination_details) = stop_reason { + // TODO: once we have unwinding, panic here instead so we unwind host frames + unsafe { + self.terminate(termination_details); + } + } + + res + } + + #[inline] + pub fn get_instruction_count(&self) -> u64 { + self.get_instance_implicits().instruction_count + } + + #[inline] + pub fn set_instruction_count(&mut self, instruction_count: u64) { + self.get_instance_implicits_mut().instruction_count = instruction_count; + } } // Private API impl Instance { fn new(alloc: Alloc, module: Arc, embed_ctx: CtxMap) -> Self { let globals_ptr = alloc.slot().globals as *mut i64; - Instance { + + let mut inst = Instance { magic: LUCET_INSTANCE_MAGIC, - embed_ctx: embed_ctx, + embed_ctx, module, ctx: Context::new(), - state: State::Ready { - retval: UntypedRetVal::default(), - }, + state: State::Ready, + kill_state: Arc::new(KillState::new()), alloc, fatal_handler: default_fatal_handler, c_fatal_handler: None, signal_handler: Box::new(signal_handler_none) as Box, - entrypoint: ptr::null(), - _reserved: [0; INSTANCE_PADDING], - globals_ptr, + entrypoint: None, + resumed_val: None, + _padding: (), + }; + inst.set_globals_ptr(globals_ptr); + inst.set_instruction_count(0); + + assert_eq!(mem::size_of::(), HOST_PAGE_SIZE_EXPECTED); + let unpadded_size = offset_of!(Instance, _padding); + assert!(unpadded_size <= HOST_PAGE_SIZE_EXPECTED - mem::size_of::<*mut i64>()); + inst + } + + // The globals pointer must be stored right before the end of the structure, padded to the page size, + // so that it is 8 bytes before the heap. + // For this reason, the alignment of the structure is set to 4096, and we define accessors that + // read/write the globals pointer as bytes [4096-8..4096] of that structure represented as raw bytes. + // InstanceRuntimeData is placed such that it ends at the end of the page this `Instance` starts + // on. So we can access it by *self + PAGE_SIZE - size_of:: + #[inline] + fn get_instance_implicits(&self) -> &InstanceRuntimeData { + unsafe { + let implicits_ptr = (self as *const _ as *const u8) + .offset((HOST_PAGE_SIZE_EXPECTED - mem::size_of::()) as isize) + as *const InstanceRuntimeData; + mem::transmute::<*const InstanceRuntimeData, &InstanceRuntimeData>(implicits_ptr) } } + #[inline] + fn get_instance_implicits_mut(&mut self) -> &mut InstanceRuntimeData { + unsafe { + let implicits_ptr = (self as *mut _ as *mut u8) + .offset((HOST_PAGE_SIZE_EXPECTED - mem::size_of::()) as isize) + as *mut InstanceRuntimeData; + mem::transmute::<*mut InstanceRuntimeData, &mut InstanceRuntimeData>(implicits_ptr) + } + } + + #[allow(dead_code)] + #[inline] + fn get_globals_ptr(&self) -> *mut i64 { + self.get_instance_implicits().globals_ptr + } + + #[inline] + fn set_globals_ptr(&mut self, globals_ptr: *mut i64) { + self.get_instance_implicits_mut().globals_ptr = globals_ptr + } + /// Run a function in guest context at the given entrypoint. - fn run_func( - &mut self, - func: *const extern "C" fn(), - args: &[Val], - ) -> Result { - lucet_ensure!( - self.state.is_ready(), - "instance must be ready; this is a bug" - ); - if func.is_null() { + fn run_func(&mut self, func: FunctionHandle, args: &[Val]) -> Result { + if !(self.state.is_ready() || (self.state.is_faulted() && !self.state.is_fatal())) { + return Err(Error::InvalidArgument( + "instance must be ready or non-fatally faulted", + )); + } + if func.ptr.as_usize() == 0 { return Err(Error::InvalidArgument( "entrypoint function cannot be null; this is probably a malformed module", )); } - self.entrypoint = func; + + let sig = self.module.get_signature(func.id); + + // in typechecking these values, we can only really check that arguments are correct. + // in the future we might want to make return value use more type safe as well. + + if sig.params.len() != args.len() { + return Err(Error::InvalidArgument( + "entrypoint function signature mismatch (number of arguments is incorrect)", + )); + } + + for (param_ty, arg) in sig.params.iter().zip(args.iter()) { + if param_ty != &arg.value_type() { + return Err(Error::InvalidArgument( + "entrypoint function signature mismatch", + )); + } + } + + self.entrypoint = Some(func.ptr); let mut args_with_vmctx = vec![Val::from(self.alloc.slot().heap)]; args_with_vmctx.extend_from_slice(args); - HOST_CTX.with(|host_ctx| { - Context::init( - unsafe { self.alloc.stack_u64_mut() }, - unsafe { &mut *host_ctx.get() }, - &mut self.ctx, - func, - &args_with_vmctx, - ) - })?; + let self_ptr = self as *mut _; + Context::init_with_callback( + unsafe { self.alloc.stack_u64_mut() }, + &mut self.ctx, + execution::exit_guest_region, + self_ptr, + func.ptr.as_usize(), + &args_with_vmctx, + )?; + + // Set up the guest to set itself as terminable, then continue to + // whatever guest code we want to run. + // + // `lucet_context_activate` takes two arguments: + // rsi: address of guest code to execute + // rdi: pointer to a bool that indicates the guest can be terminated + // + // The appropriate value for `rsi` is the top of the guest stack, which + // we would otherwise return to and start executing immediately. For + // `rdi`, we want to pass a pointer to the instance's `terminable` flag. + // + // once we've set up arguments, swap out the guest return address with + // `lucet_context_activate` so we start execution there. + unsafe { + let top_of_stack = self.ctx.gpr.rsp as *mut u64; + // move the guest code address to rsi + self.ctx.gpr.rsi = *top_of_stack; + // replace it with the activation thunk + *top_of_stack = crate::context::lucet_context_activate as u64; + // and store a pointer to indicate we're active + self.ctx.gpr.rdi = self.kill_state.terminable_ptr() as u64; + } + self.swap_and_return() + } + + /// The core routine for context switching into a guest, and extracting a result. + /// + /// This must only be called for an instance in a ready, non-fatally faulted, or yielded + /// state. The public wrappers around this function should make sure the state is appropriate. + fn swap_and_return(&mut self) -> Result { + debug_assert!( + self.state.is_ready() + || (self.state.is_faulted() && !self.state.is_fatal()) + || self.state.is_yielded() + ); self.state = State::Running; + self.kill_state.schedule(unsafe { pthread_self() }); + // there should never be another instance running on this thread when we enter this function CURRENT_INSTANCE.with(|current_instance| { let mut current_instance = current_instance.borrow_mut(); @@ -531,102 +920,89 @@ impl Instance { // Sandbox has jumped back to the host process, indicating it has either: // - // * trapped, or called hostcall_error: state tag changed to something other than `Running` - // * function body returned: set state back to `Ready` with return value + // * returned: state should be `Running`; transition to `Ready` and return a RunResult + // * yielded: state should be `Yielding`; transition to `Yielded` and return a RunResult + // * trapped: state should be `Faulted`; populate details and return an error or call a handler as appropriate + // * terminated: state should be `Terminating`; transition to `Terminated` and return the termination details as an Err + // + // The state should never be `Ready`, `Terminated`, `Yielded`, or `Transitioning` at this point - match &self.state { + self.kill_state.deschedule(); + + // Set transitioning state temporarily so that we can move values out of the current state + let st = mem::replace(&mut self.state, State::Transitioning); + + match st { State::Running => { let retval = self.ctx.get_untyped_retval(); - self.state = State::Ready { retval }; - Ok(retval) + self.state = State::Ready; + Ok(RunResult::Returned(retval)) + } + State::Terminating { details, .. } => { + self.state = State::Terminated; + Err(Error::RuntimeTerminated(details)) } - State::Terminated { details, .. } => Err(Error::RuntimeTerminated(details.clone())), - State::Fault { .. } => { + State::Yielding { val, expecting } => { + self.state = State::Yielded { expecting }; + Ok(RunResult::Yielded(val)) + } + State::Faulted { + mut details, + siginfo, + context, + } => { // Sandbox is no longer runnable. It's unsafe to determine all error details in the signal // handler, so we fill in extra details here. - self.populate_fault_detail()?; - if let State::Fault { ref details, .. } = self.state { - if details.fatal { - // Some errors indicate that the guest is not functioning correctly or that - // the loaded code violated some assumption, so bail out via the fatal - // handler. - - // Run the C-style fatal handler, if it exists. - self.c_fatal_handler - .map(|h| unsafe { h(self as *mut Instance) }); - - // If there is no C-style fatal handler, or if it (erroneously) returns, - // call the Rust handler that we know will not return - (self.fatal_handler)(self) - } else { - // leave the full fault details in the instance state, and return the - // higher-level info to the user - Err(Error::RuntimeFault(details.clone())) + // + // FIXME after lucet-module is complete it should be possible to fill this in without + // consulting the process symbol table + details.rip_addr_details = self + .module + .addr_details(details.rip_addr as *const c_void)?; + + // fill the state back in with the updated details in case fatal handlers need it + self.state = State::Faulted { + details: details.clone(), + siginfo, + context, + }; + + if details.fatal { + // Some errors indicate that the guest is not functioning correctly or that + // the loaded code violated some assumption, so bail out via the fatal + // handler. + + // Run the C-style fatal handler, if it exists. + if let Some(h) = self.c_fatal_handler { + unsafe { h(self as *mut Instance) } } + + // If there is no C-style fatal handler, or if it (erroneously) returns, + // call the Rust handler that we know will not return + (self.fatal_handler)(self) } else { - panic!("state remains Fault after populate_fault_detail()") + // leave the full fault details in the instance state, and return the + // higher-level info to the user + Err(Error::RuntimeFault(details)) } } - State::Ready { .. } => { - panic!("instance in Ready state after returning from guest context") - } + State::Ready | State::Terminated | State::Yielded { .. } | State::Transitioning => Err( + lucet_format_err!("\"impossible\" state found in `swap_and_return()`: {}", st), + ), } } fn run_start(&mut self) -> Result<(), Error> { if let Some(start) = self.module.get_start_func()? { - self.run_func(start, &[])?; - } - Ok(()) - } - - fn populate_fault_detail(&mut self) -> Result<(), Error> { - if let State::Fault { - details: - FaultDetails { - rip_addr, - trapcode, - ref mut fatal, - ref mut rip_addr_details, - .. - }, - siginfo, - .. - } = self.state - { - // We do this after returning from the signal handler because it requires `dladdr` - // calls, which are not signal safe - *rip_addr_details = self.module.addr_details(rip_addr as *const c_void)?.clone(); - - // If the trap table lookup returned unknown, it is a fatal error - let unknown_fault = trapcode.ty == TrapCodeType::Unknown; - - // If the trap was a segv or bus fault and the addressed memory was outside the - // guard pages, it is also a fatal error - let outside_guard = (siginfo.si_signo == SIGSEGV || siginfo.si_signo == SIGBUS) - && !self.alloc.addr_in_heap_guard(siginfo.si_addr()); - - *fatal = unknown_fault || outside_guard; + let res = self.run_func(start, &[])?; + if res.is_yielded() { + return Err(Error::StartYielded); + } } Ok(()) } } -pub enum State { - Ready { - retval: UntypedRetVal, - }, - Running, - Fault { - details: FaultDetails, - siginfo: libc::siginfo_t, - context: libc::ucontext_t, - }, - Terminated { - details: TerminationDetails, - }, -} - /// Information about a runtime fault. /// /// Runtime faults are raised implictly by signal handlers that return `SignalBehavior::Default` in @@ -636,7 +1012,7 @@ pub struct FaultDetails { /// If true, the instance's `fatal_handler` will be called. pub fatal: bool, /// Information about the type of fault that occurred. - pub trapcode: TrapCode, + pub trapcode: Option, /// The instruction pointer where the fault occurred. pub rip_addr: uintptr_t, /// Extra information about the instruction pointer's location, if available. @@ -644,14 +1020,18 @@ pub struct FaultDetails { } impl std::fmt::Display for FaultDetails { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.fatal { write!(f, "fault FATAL ")?; } else { write!(f, "fault ")?; } - self.trapcode.fmt(f)?; + if let Some(trapcode) = self.trapcode { + write!(f, "{:?} ", trapcode)?; + } else { + write!(f, "TrapCode::UNKNOWN ")?; + } write!(f, "code at address {:p}", self.rip_addr as *const c_void)?; @@ -680,18 +1060,29 @@ impl std::fmt::Display for FaultDetails { /// Guests are terminated either explicitly by `Vmctx::terminate()`, or implicitly by signal /// handlers that return `SignalBehavior::Terminate`. It usually indicates that an unrecoverable /// error has occurred in a hostcall, rather than in WebAssembly code. -#[derive(Clone)] pub enum TerminationDetails { + /// Returned when a signal handler terminates the instance. Signal, - GetEmbedCtx, - /// Calls to `Vmctx::terminate()` may attach an arbitrary pointer for extra debugging - /// information. - Provided(Arc), + /// Returned when `get_embed_ctx` or `get_embed_ctx_mut` are used with a type that is not present. + CtxNotFound, + /// Returned when the type of the value passed to `Instance::resume_with_val()` does not match + /// the type expected by `Vmctx::yield_expecting_val()` or `Vmctx::yield_val_expecting_val`, or + /// if `Instance::resume()` was called when a value was expected. + /// + /// **Note**: If you see this termination value, please report it as a Lucet bug. The types of + /// resumed values are dynamically checked by `Instance::resume()` and + /// `Instance::resume_with_val()`, so this should never arise. + YieldTypeMismatch, + /// Returned when dynamic borrowing rules of methods like `Vmctx::heap()` are violated. + BorrowError(&'static str), + /// Calls to `lucet_hostcall_terminate` provide a payload for use by the embedder. + Provided(Box), + Remote, } impl TerminationDetails { - pub fn provide(details: A) -> Self { - TerminationDetails::Provided(Arc::new(details)) + pub fn provide(details: A) -> Self { + TerminationDetails::Provided(Box::new(details)) } pub fn provided_details(&self) -> Option<&dyn Any> { match self { @@ -714,135 +1105,90 @@ fn termination_details_any_typing() { ); } +impl PartialEq for TerminationDetails { + fn eq(&self, rhs: &TerminationDetails) -> bool { + use TerminationDetails::*; + match (self, rhs) { + (Signal, Signal) => true, + (BorrowError(msg1), BorrowError(msg2)) => msg1 == msg2, + (CtxNotFound, CtxNotFound) => true, + // can't compare `Any` + _ => false, + } + } +} + impl std::fmt::Debug for TerminationDetails { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "TerminationDetails::{}", - match self { - TerminationDetails::Signal => "Signal", - TerminationDetails::GetEmbedCtx => "GetEmbedCtx", - TerminationDetails::Provided(_) => "Provided(Any)", - } - ) + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "TerminationDetails::")?; + match self { + TerminationDetails::Signal => write!(f, "Signal"), + TerminationDetails::BorrowError(msg) => write!(f, "BorrowError({})", msg), + TerminationDetails::CtxNotFound => write!(f, "CtxNotFound"), + TerminationDetails::YieldTypeMismatch => write!(f, "YieldTypeMismatch"), + TerminationDetails::Provided(_) => write!(f, "Provided(Any)"), + TerminationDetails::Remote => write!(f, "Remote"), + } } } unsafe impl Send for TerminationDetails {} unsafe impl Sync for TerminationDetails {} -impl std::fmt::Display for State { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - State::Ready { .. } => write!(f, "ready"), - State::Running => write!(f, "running"), - State::Fault { - details, siginfo, .. - } => { - write!(f, "{}", details)?; - write!( - f, - " triggered by {}: ", - strsignal_wrapper(siginfo.si_signo) - .into_string() - .expect("strsignal returns valid UTF-8") - )?; - - if siginfo.si_signo == SIGSEGV || siginfo.si_signo == SIGBUS { - // We know this is inside the heap guard, because by the time we get here, - // `lucet_error_verify_trap_safety` will have run and validated it. - write!( - f, - " accessed memory at {:p} (inside heap guard)", - siginfo.si_addr() - )?; - } - Ok(()) - } - State::Terminated { .. } => write!(f, "terminated"), - } - } +/// The value yielded by an instance through a [`Vmctx`](vmctx/struct.Vmctx.html) and returned to +/// the host. +pub struct YieldedVal { + val: Box, } -impl State { - pub fn is_ready(&self) -> bool { - if let State::Ready { .. } = self { - true +impl std::fmt::Debug for YieldedVal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.is_none() { + write!(f, "YieldedVal {{ val: None }}") } else { - false + write!(f, "YieldedVal {{ val: Some }}") } } +} - pub fn is_running(&self) -> bool { - if let State::Running = self { - true - } else { - false - } +impl YieldedVal { + pub(crate) fn new(val: A) -> Self { + YieldedVal { val: Box::new(val) } } - pub fn is_fault(&self) -> bool { - if let State::Fault { .. } = self { - true - } else { - false - } + /// Returns `true` if the guest yielded without a value. + pub fn is_none(&self) -> bool { + self.val.is::() } - pub fn is_fatal(&self) -> bool { - if let State::Fault { - details: FaultDetails { fatal, .. }, - .. - } = self - { - *fatal - } else { - false - } + /// Returns `true` if the guest yielded with a value. + pub fn is_some(&self) -> bool { + !self.is_none() } - pub fn is_terminated(&self) -> bool { - if let State::Terminated { .. } = self { - true - } else { - false + /// Attempt to downcast the yielded value to a concrete type, returning the original + /// `YieldedVal` if unsuccessful. + pub fn downcast(self) -> Result, YieldedVal> { + match self.val.downcast() { + Ok(val) => Ok(val), + Err(val) => Err(YieldedVal { val }), } } -} -fn default_fatal_handler(inst: &Instance) -> ! { - panic!("> instance {:p} had fatal error: {}", inst, inst.state); -} - -// TODO: PR into `libc` -extern "C" { - #[no_mangle] - fn strsignal(sig: libc::c_int) -> *mut libc::c_char; -} - -// TODO: PR into `nix` -fn strsignal_wrapper(sig: libc::c_int) -> CString { - unsafe { CStr::from_ptr(strsignal(sig)).to_owned() } + /// Returns a reference to the yielded value if it is present and of type `A`, or `None` if it + /// isn't. + pub fn downcast_ref(&self) -> Option<&A> { + self.val.downcast_ref() + } } -#[cfg(test)] -mod tests { - use super::*; - use memoffset::offset_of; - - #[test] - fn instance_size_correct() { - assert_eq!(mem::size_of::(), 4096); - } +/// A marker value to indicate a yield or resume with no value. +/// +/// This exists to unify the implementations of the various operators, and should only ever be +/// created by internal code. +#[derive(Debug)] +pub(crate) struct EmptyYieldVal; - #[test] - fn instance_globals_offset_correct() { - let offset = offset_of!(Instance, globals_ptr) as isize; - if offset != 4096 - 8 { - let diff = 4096 - 8 - offset; - let new_padding = INSTANCE_PADDING as isize + diff; - panic!("new padding should be: {:?}", new_padding); - } - assert_eq!(offset_of!(Instance, globals_ptr), 4096 - 8); - } +fn default_fatal_handler(inst: &Instance) -> ! { + panic!("> instance {:p} had fatal error: {}", inst, inst.state); } diff --git a/lucet-runtime/lucet-runtime-internals/src/instance/execution.rs b/lucet-runtime/lucet-runtime-internals/src/instance/execution.rs new file mode 100644 index 000000000..50891eafe --- /dev/null +++ b/lucet-runtime/lucet-runtime-internals/src/instance/execution.rs @@ -0,0 +1,321 @@ +//! The `execution` module contains state for an instance's execution, and exposes functions +//! building that state into something appropriate for safe use externally. +//! +//! So far as state tracked in this module is concerned, there are two key items: "terminability" +//! and "execution domain". +//! +//! ## Terminability +//! This specifically answers the question "is it safe to initiate termination of this instance +//! right now?". An instance becomes terminable when it begins executing, and stops being +//! terminable when it is terminated, or when it stops executing. Termination does not directly map +//! to the idea of guest code currently executing on a processor, because termination can occur +//! during host code, or while a guest has yielded execution. As a result, termination can only be +//! treated as a best-effort to deschedule a guest, and is typically quick when it occurs during +//! guest code execution, or immediately upon resuming execution of guest code (exiting host code, +//! or resuming a yielded instance). +//! +//! ## Execution Domain +//! Execution domains allow us to distinguish what an appropriate mechanism to signal termination +//! is. This means that changing of an execution domain must be atomic - it would be an error to +//! read the current execution domain, continue with that domain to determine temination, and +//! simultaneously for execution to continue possibly into a different execution domain. For +//! example, beginning termination directly at the start of a hostcall, where sending `SIGALRM` may +//! be appropriate, while the domain switches to `Hostcall` and is no longer appropriate for +//! signalling, would be an error. +//! +//! ## Instance Lifecycle and `KillState` +//! +//! And now we can enumerate interleavings of execution and timeout, to see the expected state at +//! possible points of interest in an instance's lifecycle: +//! +//! * `Instance created` +//! - terminable: `false` +//! - execution_domain: `Guest` +//! * `Instance::run called` +//! - terminable: `true` +//! - execution_domain: `Guest` +//! * `Instance::run executing` +//! - terminable: `true, or false` +//! - execution_domain: `Guest, Hostcall, or Terminated` +//! - `execution_domain` will only be `Guest` when executing guest code, only be `Hostcall` when +//! executing a hostcall, but may also be `Terminated` while in a hostcall to indicate that it +//! should exit when the hostcall completes. +//! - `terminable` will be false if and only if `execution_domain` is `Terminated`. +//! * `Instance::run returns` +//! - terminable: `false` +//! - execution_domain: `Guest, Hostcall, or Terminated` +//! - `execution_domain` will be `Guest` when the initial guest function returns, `Hostcall` when +//! terminated by `lucet_hostcall_terminate!`, and `Terminated` when exiting due to a termination +//! request. +//! * `Guest function executing` +//! - terminable: `true` +//! - execution_domain: `Guest` +//! * `Guest function returns` +//! - terminable: `true` +//! - execution_domain: `Guest` +//! * `Hostcall called` +//! - terminable: `true` +//! - execution_domain: `Hostcall` +//! * `Hostcall executing` +//! - terminable: `true` +//! - execution_domain: `Hostcall, or Terminated` +//! - `execution_domain` will typically be `Hostcall`, but may be `Terminated` if termination of +//! the instance is requested during the hostcall. +//! - `terminable` will be false if and only if `execution_domain` is `Terminated`. +//! * `Hostcall yields` +//! - This is a specific point in "Hostcall executing" and has no further semantics. +//! * `Hostcall resumes` +//! - This is a specific point in "Hostcall executing" and has no further semantics. +//! * `Hostcall returns` +//! - terminable: `true` +//! - execution_domain: `Guest` +//! - `execution_domain` may be `Terminated` before returning, in which case `terminable` will be +//! false, but the hostcall would then exit. If a hostcall successfully returns to its caller it +//! was not terminated, so the only state an instance will have after returning from a hostcall +//! will be that it's executing terminable guest code. + +use libc::{pthread_kill, pthread_t, SIGALRM}; +use std::mem; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Condvar, Mutex, Weak}; + +use crate::instance::{Instance, TerminationDetails}; + +/// All instance state a remote kill switch needs to determine if and how to signal that execution +/// should stop. +/// +/// Some definitions for reference in this struct's documentation: +/// * "stopped" means "stop executing at some point before reaching the end of the entrypoint +/// wasm function". +/// * "critical section" means what it typically means - an uninterruptable region of code. The +/// detail here is that currently "critical section" and "hostcall" are interchangeable, but in +/// the future this may change. Hostcalls may one day be able to opt out of criticalness, or +/// perhaps guest code may include critical sections. +/// +/// "Stopped" is a particularly loose word here because it encompasses the worst case: trying to +/// stop a guest that is currently in a critical section. Because the signal will only be checked +/// when exiting the critical section, the latency is bounded by whatever embedder guarantees are +/// made. In fact, it is possible for a kill signal to be successfully sent and still never +/// impactful, if a hostcall itself invokes `lucet_hostcall_terminate!`. In this circumstance, the +/// hostcall would terminate the instance if it returned, but `lucet_hostcall_terminate!` will +/// terminate the guest before the termination request would even be checked. +pub struct KillState { + /// Can the instance be terminated? This must be `true` only when the instance can be stopped. + /// This may be false while the instance can safely be stopped, such as immediately after + /// completing a host->guest context swap. Regions such as this should be minimized, but are + /// not a problem of correctness. + /// + /// Typically, this is true while in any guest code, or hostcalls made from guest code. + terminable: AtomicBool, + /// The kind of code is currently executing in the instance this `KillState` describes. + /// + /// This allows a `KillSwitch` to determine what the appropriate signalling mechanism is in + /// `terminate`. Locks on `execution_domain` prohibit modification while signalling, ensuring + /// both that: + /// * we don't enter a hostcall while someone may decide it is safe to signal, and + /// * no one may try to signal in a hostcall-safe manner after exiting a hostcall, where it + /// may never again be checked by the guest. + execution_domain: Mutex, + /// The current `thread_id` the associated instance is running on. This is the TID where + /// `SIGALRM` will be sent if the instance is killed via `KillSwitch::terminate` and a signal + /// is an appropriate mechanism. + thread_id: Mutex>, + /// `tid_change_notifier` allows functions that may cause a change in `thread_id` to wait, + /// without spinning, for the signal to be processed. + tid_change_notifier: Condvar, +} + +pub unsafe extern "C" fn exit_guest_region(instance: *mut Instance) { + let terminable = (*instance) + .kill_state + .terminable + .swap(false, Ordering::SeqCst); + if !terminable { + // Something else has taken the terminable flag, so it's not safe to actually exit a + // guest context yet. Because this is called when exiting a guest context, the + // termination mechanism will be a signal, delivered at some point (hopefully soon!). + // Further, because the termination mechanism will be a signal, we are constrained to + // only signal-safe behavior. + // + // For now, hang indefinitely, waiting for the sigalrm to arrive. + + loop {} + } +} + +impl KillState { + pub fn new() -> KillState { + KillState { + terminable: AtomicBool::new(false), + tid_change_notifier: Condvar::new(), + execution_domain: Mutex::new(Domain::Guest), + thread_id: Mutex::new(None), + } + } + + pub fn is_terminable(&self) -> bool { + self.terminable.load(Ordering::SeqCst) + } + + pub fn enable_termination(&self) { + self.terminable.store(true, Ordering::SeqCst); + } + + pub fn disable_termination(&self) { + self.terminable.store(false, Ordering::SeqCst); + } + + pub fn terminable_ptr(&self) -> *const AtomicBool { + &self.terminable as *const AtomicBool + } + + pub fn begin_hostcall(&self) { + // Lock the current execution domain, so we can update to `Hostcall`. + let mut current_domain = self.execution_domain.lock().unwrap(); + match *current_domain { + Domain::Guest => { + // Guest is the expected domain until this point. Switch to the Hostcall + // domain so we know to not interrupt this instance. + *current_domain = Domain::Hostcall; + } + Domain::Hostcall => { + panic!( + "Invalid state: Instance marked as in a hostcall while entering a hostcall." + ); + } + Domain::Terminated => { + panic!("Invalid state: Instance marked as terminated while in guest code. This should be an error."); + } + } + } + + pub fn end_hostcall(&self) -> Option { + let mut current_domain = self.execution_domain.lock().unwrap(); + match *current_domain { + Domain::Guest => { + panic!("Invalid state: Instance marked as in guest code while exiting a hostcall."); + } + Domain::Hostcall => { + *current_domain = Domain::Guest; + None + } + Domain::Terminated => { + // The instance was stopped in the hostcall we were executing. + debug_assert!(!self.terminable.load(Ordering::SeqCst)); + std::mem::drop(current_domain); + Some(TerminationDetails::Remote) + } + } + } + + pub fn schedule(&self, tid: pthread_t) { + *self.thread_id.lock().unwrap() = Some(tid); + self.tid_change_notifier.notify_all(); + } + + pub fn deschedule(&self) { + *self.thread_id.lock().unwrap() = None; + self.tid_change_notifier.notify_all(); + } +} + +pub enum Domain { + Guest, + Hostcall, + Terminated, +} + +/// An object that can be used to terminate an instance's execution from a separate thread. +pub struct KillSwitch { + state: Weak, +} + +#[derive(Debug, PartialEq)] +pub enum KillSuccess { + Signalled, + Pending, +} + +#[derive(Debug, PartialEq)] +pub enum KillError { + NotTerminable, +} + +type KillResult = Result; + +impl KillSwitch { + pub(crate) fn new(state: Weak) -> Self { + KillSwitch { state } + } + + /// Signal the instance associated with this `KillSwitch` to stop, if possible. + /// + /// The returned `Result` only describes the behavior taken by this function, not necessarily + /// what caused the associated instance to stop. + /// + /// As an example, if a `KillSwitch` fires, sending a SIGALRM to an instance at the same + /// moment it begins handling a SIGSEGV which is determined to be fatal, the instance may + /// stop with `State::Faulted` before actually _handling_ the SIGALRM we'd send here. So the + /// host code will see `State::Faulted` as an instance state, where `KillSwitch::terminate` + /// would return `Ok(KillSuccess::Signalled)`. + pub fn terminate(&self) -> KillResult { + // Get the underlying kill state. If this fails, it means the instance exited and was + // discarded, so we can't terminate. + let state = self.state.upgrade().ok_or(KillError::NotTerminable)?; + + // Attempt to take the flag indicating the instance may terminate + let terminable = state.terminable.swap(false, Ordering::SeqCst); + if !terminable { + return Err(KillError::NotTerminable); + } + + // we got it! we can signal the instance. + + // Now check what domain the instance is in. We can signal in guest code, but want + // to avoid signalling in host code lest we interrupt some function operating on + // guest/host shared memory, and invalidate invariants. For example, interrupting + // in the middle of a resize operation on a `Vec` could be extremely dangerous. + // + // Hold this lock through all signalling logic to prevent the instance from + // switching domains (and invalidating safety of whichever mechanism we choose here) + let mut execution_domain = state.execution_domain.lock().unwrap(); + + let result = match *execution_domain { + Domain::Guest => { + let mut curr_tid = state.thread_id.lock().unwrap(); + // we're in guest code, so we can just send a signal. + if let Some(thread_id) = *curr_tid { + unsafe { + pthread_kill(thread_id, SIGALRM); + } + + // wait for the SIGALRM handler to deschedule the instance + // + // this should never actually loop, which would indicate the instance + // was moved to another thread, or we got spuriously notified. + while curr_tid.is_some() { + curr_tid = state.tid_change_notifier.wait(curr_tid).unwrap(); + } + Ok(KillSuccess::Signalled) + } else { + panic!("logic error: instance is terminable but not actually running."); + } + } + Domain::Hostcall => { + // the guest is in a hostcall, so the only thing we can do is indicate it + // should terminate and wait. + *execution_domain = Domain::Terminated; + Ok(KillSuccess::Pending) + } + Domain::Terminated => { + // Something else (another KillSwitch?) has already signalled this instance + // to exit when it has completed its hostcall. Nothing to do here. + Err(KillError::NotTerminable) + } + }; + // explicitly drop the lock to be clear about how long we want to hold this lock, which is + // until all signalling is complete. + mem::drop(execution_domain); + result + } +} diff --git a/lucet-runtime/lucet-runtime-internals/src/instance/siginfo_ext.rs b/lucet-runtime/lucet-runtime-internals/src/instance/siginfo_ext.rs index 354a3711e..863d8a868 100644 --- a/lucet-runtime/lucet-runtime-internals/src/instance/siginfo_ext.rs +++ b/lucet-runtime/lucet-runtime-internals/src/instance/siginfo_ext.rs @@ -5,11 +5,11 @@ extern "C" { } pub trait SiginfoExt { - fn si_addr(&self) -> *const c_void; + fn si_addr_ext(&self) -> *const c_void; } impl SiginfoExt for siginfo_t { - fn si_addr(&self) -> *const c_void { + fn si_addr_ext(&self) -> *const c_void { unsafe { siginfo_si_addr(self as *const siginfo_t) } } } diff --git a/lucet-runtime/lucet-runtime-internals/src/instance/signals.rs b/lucet-runtime/lucet-runtime-internals/src/instance/signals.rs index fc98d1b69..1ca1af942 100644 --- a/lucet-runtime/lucet-runtime-internals/src/instance/signals.rs +++ b/lucet-runtime/lucet-runtime-internals/src/instance/signals.rs @@ -1,15 +1,19 @@ use crate::context::Context; +use crate::error::Error; use crate::instance::{ - FaultDetails, Instance, State, TerminationDetails, CURRENT_INSTANCE, HOST_CTX, + siginfo_ext::SiginfoExt, FaultDetails, Instance, State, TerminationDetails, CURRENT_INSTANCE, + HOST_CTX, }; -use crate::trapcode::{TrapCode, TrapCodeType}; -use failure::Error; +use crate::sysdeps::UContextPtr; use lazy_static::lazy_static; -use libc::{c_int, c_void, siginfo_t}; +use libc::{c_int, c_void, siginfo_t, SIGBUS, SIGSEGV}; +use lucet_module::TrapCode; use nix::sys::signal::{ pthread_sigmask, raise, sigaction, SaFlags, SigAction, SigHandler, SigSet, SigmaskHow, Signal, }; -use std::sync::Mutex; +use std::mem::MaybeUninit; +use std::panic; +use std::sync::{Arc, Mutex}; lazy_static! { // TODO: work out an alternative to this that is signal-safe for `reraise_host_signal_in_handler` @@ -28,12 +32,17 @@ pub enum SignalBehavior { Terminate, } -pub type SignalHandler = - dyn Fn(&Instance, &TrapCode, libc::c_int, *const siginfo_t, *const c_void) -> SignalBehavior; +pub type SignalHandler = dyn Fn( + &Instance, + &Option, + libc::c_int, + *const siginfo_t, + *const c_void, +) -> SignalBehavior; pub fn signal_handler_none( _inst: &Instance, - _trapcode: &TrapCode, + _trapcode: &Option, _signum: libc::c_int, _siginfo_ptr: *const siginfo_t, _ucontext_ptr: *const c_void, @@ -46,14 +55,24 @@ impl Instance { where F: FnOnce(&mut Instance) -> Result, { - // setup signal stack for this thread + // Set up the signal stack for this thread. Note that because signal stacks are per-thread, + // rather than per-process, we do this for every run, while the signal handler is installed + // only once per process. let guest_sigstack = SigStack::new( self.alloc.slot().sigstack, SigStackFlags::empty(), libc::SIGSTKSZ, ); - let saved_sigstack = unsafe { sigaltstack(&guest_sigstack).expect("sigaltstack succeeds") }; - + let previous_sigstack = unsafe { sigaltstack(Some(guest_sigstack)) } + .expect("enabling or changing the signal stack succeeds"); + if let Some(previous_sigstack) = previous_sigstack { + assert!( + !previous_sigstack + .flags() + .contains(SigStackFlags::SS_ONSTACK), + "an instance was created with a signal stack" + ); + } let mut ostate = LUCET_SIGNAL_STATE.lock().unwrap(); if let Some(ref mut state) = *ostate { state.counter += 1; @@ -72,8 +91,6 @@ impl Instance { state.counter -= 1; if state.counter == 0 { unsafe { - // restore the host signal stack - sigaltstack(&saved_sigstack).expect("sigaltstack succeeds"); restore_host_signal_state(state); } true @@ -87,6 +104,16 @@ impl Instance { *ostate = None; } + unsafe { + // restore the host signal stack for this thread + if !altstack_flags() + .expect("the current stack flags could be retrieved") + .contains(SigStackFlags::SS_ONSTACK) + { + sigaltstack(previous_sigstack).expect("sigaltstack restoration succeeds"); + } + } + res } } @@ -101,7 +128,8 @@ extern "C" fn handle_signal(signum: c_int, siginfo_ptr: *mut siginfo_t, ucontext if !(signal == Signal::SIGBUS || signal == Signal::SIGSEGV || signal == Signal::SIGILL - || signal == Signal::SIGFPE) + || signal == Signal::SIGFPE + || signal == Signal::SIGALRM) { panic!("unexpected signal in guest signal handler: {:?}", signal); } @@ -109,12 +137,9 @@ extern "C" fn handle_signal(signum: c_int, siginfo_ptr: *mut siginfo_t, ucontext // Safety: when using a SA_SIGINFO sigaction, the third argument can be cast to a `ucontext_t` // pointer per the manpage - let ctx = unsafe { - (ucontext_ptr as *mut libc::ucontext_t) - .as_ref() - .expect("ucontext must not be null") - }; - let rip = ctx.uc_mcontext.gregs[libc::REG_RIP as usize] as *const c_void; + assert!(!ucontext_ptr.is_null(), "ucontext_ptr must not be null"); + let ctx = UContextPtr::new(ucontext_ptr); + let rip = ctx.get_ip(); let switch_to_host = CURRENT_INSTANCE.with(|current_instance| { let mut current_instance = current_instance.borrow_mut(); @@ -140,49 +165,90 @@ extern "C" fn handle_signal(signum: c_int, siginfo_ptr: *mut siginfo_t, ucontext .as_mut() }; - let trapcode = inst - .module - .lookup_trapcode(rip) - // if we couldn't find a code in the manifest, return an unknown trapcode that will be - // converted to a fatal trap when we switch back to the host - .unwrap_or(TrapCode { - ty: TrapCodeType::Unknown, - tag: 0, - }); + if signal == Signal::SIGALRM { + // if have gotten a SIGALRM, the killswitch that sent this signal must have also + // disabled the `terminable` flag. If this assert fails, the SIGALRM came from some + // other source, or we have a bug that allows SIGALRM to be sent when they should not. + // + // TODO: once we have a notion of logging in `lucet-runtime`, this should be a logged + // error. + debug_assert!(!inst.kill_state.is_terminable()); + + inst.state = State::Terminating { + details: TerminationDetails::Remote, + }; + return true; + } + + let trapcode = inst.module.lookup_trapcode(rip); let behavior = (inst.signal_handler)(inst, &trapcode, signum, siginfo_ptr, ucontext_ptr); - match behavior { + let switch_to_host = match behavior { SignalBehavior::Continue => { // return to the guest context without making any modifications to the instance false } SignalBehavior::Terminate => { // set the state before jumping back to the host context - inst.state = State::Terminated { + inst.state = State::Terminating { details: TerminationDetails::Signal, }; + true } SignalBehavior::Default => { - // record the fault and jump back to the host context - inst.state = State::Fault { - details: FaultDetails { - // fatal field is set false here by default - have to wait until - // `verify_trap_safety` to have enough information to determine whether trap was - // fatal. It is not signal safe to access some of the required information. - fatal: false, - trapcode: trapcode, - rip_addr: rip as usize, - rip_addr_details: None, - }, + /* + * /!\ WARNING: LOAD-BEARING THUNK /!\ + * + * This thunk, in debug builds, introduces multiple copies of UContext in the local + * stack frame. This also includes a local `State`, which is quite large as well. + * In total, this thunk accounts for roughly 5kb of stack use, where default signal + * stack sizes are typically 8kb total. + * + * In code paths that do not pass through this (such as immediately reraising as a + * host signal), the code in this thunk would force an exhaustion of more than half + * the stack, significantly increasing the likelihood the Lucet signal handler may + * overflow some other thread with a minimal stack size. + */ + let mut thunk = || { // safety: pointer is checked for null at the top of the function, and the // manpage guarantees that a siginfo_t will be passed as the second argument - siginfo: unsafe { *siginfo_ptr }, - context: *ctx, + let siginfo = unsafe { *siginfo_ptr }; + let rip_addr = rip as usize; + // If the trap table lookup returned unknown, it is a fatal error + let unknown_fault = trapcode.is_none(); + + // If the trap was a segv or bus fault and the addressed memory was outside the + // guard pages, it is also a fatal error + let outside_guard = (siginfo.si_signo == SIGSEGV || siginfo.si_signo == SIGBUS) + && !inst.alloc.addr_in_guard_page(siginfo.si_addr_ext()); + + // record the fault and jump back to the host context + inst.state = State::Faulted { + details: FaultDetails { + fatal: unknown_fault || outside_guard, + trapcode: trapcode, + rip_addr, + // Details set to `None` here: have to wait until `verify_trap_safety` to + // fill in these details, because access may not be signal safe. + rip_addr_details: None, + }, + siginfo, + context: ctx.into(), + }; }; + + thunk(); true } + }; + + if switch_to_host { + // we must disable termination so no KillSwitch may fire in host code. + inst.kill_state.disable_termination(); } + + switch_to_host }); if switch_to_host { @@ -200,6 +266,8 @@ struct SignalState { saved_sigfpe: SigAction, saved_sigill: SigAction, saved_sigsegv: SigAction, + saved_sigalrm: SigAction, + saved_panic_hook: Option) + Sync + Send + 'static>>>, } // raw pointers in the saved types @@ -211,6 +279,7 @@ unsafe fn setup_guest_signal_state(ostate: &mut Option) { masked_signals.add(Signal::SIGFPE); masked_signals.add(Signal::SIGILL); masked_signals.add(Signal::SIGSEGV); + masked_signals.add(Signal::SIGALRM); // setup signal handlers let sa = SigAction::new( @@ -222,6 +291,9 @@ unsafe fn setup_guest_signal_state(ostate: &mut Option) { let saved_sigfpe = sigaction(Signal::SIGFPE, &sa).expect("sigaction succeeds"); let saved_sigill = sigaction(Signal::SIGILL, &sa).expect("sigaction succeeds"); let saved_sigsegv = sigaction(Signal::SIGSEGV, &sa).expect("sigaction succeeds"); + let saved_sigalrm = sigaction(Signal::SIGALRM, &sa).expect("sigaction succeeds"); + + let saved_panic_hook = Some(setup_guest_panic_hook()); *ostate = Some(SignalState { counter: 1, @@ -229,15 +301,44 @@ unsafe fn setup_guest_signal_state(ostate: &mut Option) { saved_sigfpe, saved_sigill, saved_sigsegv, + saved_sigalrm, + saved_panic_hook, }); } +fn setup_guest_panic_hook() -> Arc) + Sync + Send + 'static>> { + let saved_panic_hook = Arc::new(panic::take_hook()); + let closure_saved_panic_hook = saved_panic_hook.clone(); + std::panic::set_hook(Box::new(move |panic_info| { + if panic_info + .payload() + .downcast_ref::() + .is_none() + { + closure_saved_panic_hook(panic_info); + } else { + // this is a panic used to implement instance termination (such as + // `lucet_hostcall_terminate!`), so we don't want to print a backtrace; instead, we do + // nothing + } + })); + saved_panic_hook +} + unsafe fn restore_host_signal_state(state: &mut SignalState) { // restore signal handlers sigaction(Signal::SIGBUS, &state.saved_sigbus).expect("sigaction succeeds"); sigaction(Signal::SIGFPE, &state.saved_sigfpe).expect("sigaction succeeds"); sigaction(Signal::SIGILL, &state.saved_sigill).expect("sigaction succeeds"); sigaction(Signal::SIGSEGV, &state.saved_sigsegv).expect("sigaction succeeds"); + sigaction(Signal::SIGALRM, &state.saved_sigalrm).expect("sigaction succeeds"); + + // restore panic hook + drop(panic::take_hook()); + state + .saved_panic_hook + .take() + .map(|hook| Arc::try_unwrap(hook).map(|hook| panic::set_hook(hook))); } unsafe fn reraise_host_signal_in_handler( @@ -255,6 +356,7 @@ unsafe fn reraise_host_signal_in_handler( Signal::SIGFPE => state.saved_sigfpe.clone(), Signal::SIGILL => state.saved_sigill.clone(), Signal::SIGSEGV => state.saved_sigsegv.clone(), + Signal::SIGALRM => state.saved_sigalrm.clone(), sig => panic!( "unexpected signal in reraise_host_signal_in_handler: {:?}", sig @@ -320,10 +422,20 @@ pub struct SigStack { impl SigStack { pub fn new(sp: *mut libc::c_void, flags: SigStackFlags, size: libc::size_t) -> SigStack { - let mut stack = unsafe { std::mem::uninitialized::() }; - stack.ss_sp = sp; - stack.ss_flags = flags.bits(); - stack.ss_size = size; + let stack = libc::stack_t { + ss_sp: sp, + ss_flags: flags.bits(), + ss_size: size, + }; + SigStack { stack } + } + + pub fn disabled() -> SigStack { + let stack = libc::stack_t { + ss_sp: std::ptr::null_mut(), + ss_flags: SigStackFlags::SS_DISABLE.bits(), + ss_size: libc::SIGSTKSZ, + }; SigStack { stack } } @@ -351,13 +463,32 @@ bitflags! { } } -pub unsafe fn sigaltstack(ss: &SigStack) -> nix::Result { - let mut oldstack = std::mem::uninitialized::(); - +pub unsafe fn sigaltstack(new_sigstack: Option) -> nix::Result> { + let mut previous_stack = MaybeUninit::::uninit(); + let disabled_sigstack = SigStack::disabled(); + let new_stack = match new_sigstack { + None => &disabled_sigstack.stack, + Some(ref new_stack) => &new_stack.stack, + }; let res = libc::sigaltstack( - &ss.stack as *const libc::stack_t, - &mut oldstack as *mut libc::stack_t, + new_stack as *const libc::stack_t, + previous_stack.as_mut_ptr(), ); + nix::errno::Errno::result(res).map(|_| { + let sigstack = SigStack { + stack: previous_stack.assume_init(), + }; + if sigstack.flags().contains(SigStackFlags::SS_DISABLE) { + None + } else { + Some(sigstack) + } + }) +} - nix::errno::Errno::result(res).map(|_| SigStack { stack: oldstack }) +pub unsafe fn altstack_flags() -> nix::Result { + let mut current_stack = MaybeUninit::::uninit(); + let res = libc::sigaltstack(std::ptr::null_mut(), current_stack.as_mut_ptr()); + nix::errno::Errno::result(res) + .map(|_| SigStackFlags::from_bits_truncate(current_stack.assume_init().ss_flags)) } diff --git a/lucet-runtime/lucet-runtime-internals/src/instance/state.rs b/lucet-runtime/lucet-runtime-internals/src/instance/state.rs new file mode 100644 index 000000000..8426d520b --- /dev/null +++ b/lucet-runtime/lucet-runtime-internals/src/instance/state.rs @@ -0,0 +1,178 @@ +use crate::instance::siginfo_ext::SiginfoExt; +use crate::instance::{FaultDetails, TerminationDetails, YieldedVal}; +use crate::sysdeps::UContext; +use libc::{SIGBUS, SIGSEGV}; +use std::any::Any; +use std::ffi::{CStr, CString}; + +/// The representation of a Lucet instance's state machine. +pub enum State { + /// The instance is ready to run. + /// + /// Transitions to `Running` when the instance is run, or to `Ready` when it's reset. + Ready, + + /// The instance is running. + /// + /// Transitions to `Ready` when the guest function returns normally, or to `Faulted`, + /// `Terminating`, or `Yielding` if the instance faults, terminates, or yields. + Running, + + /// The instance has faulted, potentially fatally. + /// + /// Transitions to `Faulted` when filling in additional fault details, to `Running` if + /// re-running a non-fatally faulted instance, or to `Ready` when the instance is reset. + Faulted { + details: FaultDetails, + siginfo: libc::siginfo_t, + context: UContext, + }, + + /// The instance is in the process of terminating. + /// + /// Transitions only to `Terminated`; the `TerminationDetails` are always extracted into a + /// `RunResult` before anything else happens to the instance. + Terminating { details: TerminationDetails }, + + /// The instance has terminated, and must be reset before running again. + /// + /// Transitions to `Ready` if the instance is reset. + Terminated, + + /// The instance is in the process of yielding. + /// + /// Transitions only to `Yielded`; the `YieldedVal` is always extracted into a + /// `RunResult` before anything else happens to the instance. + Yielding { + val: YieldedVal, + /// A phantom value carrying the type of the expected resumption value. + /// + /// Concretely, this should only ever be `Box>` where `R` is the type + /// the guest expects upon resumption. + expecting: Box, + }, + + /// The instance has yielded. + /// + /// Transitions to `Running` if the instance is resumed, or to `Ready` if the instance is reset. + Yielded { + /// A phantom value carrying the type of the expected resumption value. + /// + /// Concretely, this should only ever be `Box>` where `R` is the type + /// the guest expects upon resumption. + expecting: Box, + }, + + /// A placeholder state used with `std::mem::replace()` when a new state must be constructed by + /// moving values out of an old state. + /// + /// This is used so that we do not need a `Clone` impl for this type, which would add + /// unnecessary constraints to the types of values instances could yield or terminate with. + /// + /// It is an error for this state to appear outside of a transition between other states. + Transitioning, +} + +impl std::fmt::Display for State { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + State::Ready => write!(f, "ready"), + State::Running => write!(f, "running"), + State::Faulted { + details, siginfo, .. + } => { + write!(f, "{}", details)?; + write!( + f, + " triggered by {}: ", + strsignal_wrapper(siginfo.si_signo) + .into_string() + .expect("strsignal returns valid UTF-8") + )?; + + if siginfo.si_signo == SIGSEGV || siginfo.si_signo == SIGBUS { + // We know this is inside the heap guard, because by the time we get here, + // `lucet_error_verify_trap_safety` will have run and validated it. + write!( + f, + " accessed memory at {:p} (inside heap guard)", + siginfo.si_addr_ext() + )?; + } + Ok(()) + } + State::Terminated { .. } => write!(f, "terminated"), + State::Terminating { .. } => write!(f, "terminating"), + State::Yielding { .. } => write!(f, "yielding"), + State::Yielded { .. } => write!(f, "yielded"), + State::Transitioning { .. } => { + write!(f, "transitioning (IF YOU SEE THIS, THERE'S PROBABLY A BUG)") + } + } + } +} + +impl State { + pub fn is_ready(&self) -> bool { + if let State::Ready { .. } = self { + true + } else { + false + } + } + + pub fn is_running(&self) -> bool { + if let State::Running = self { + true + } else { + false + } + } + + pub fn is_faulted(&self) -> bool { + if let State::Faulted { .. } = self { + true + } else { + false + } + } + + pub fn is_fatal(&self) -> bool { + if let State::Faulted { + details: FaultDetails { fatal, .. }, + .. + } = self + { + *fatal + } else { + false + } + } + + pub fn is_terminated(&self) -> bool { + if let State::Terminated { .. } = self { + true + } else { + false + } + } + + pub fn is_yielded(&self) -> bool { + if let State::Yielded { .. } = self { + true + } else { + false + } + } +} + +// TODO: PR into `libc` +extern "C" { + #[no_mangle] + fn strsignal(sig: libc::c_int) -> *mut libc::c_char; +} + +// TODO: PR into `nix` +fn strsignal_wrapper(sig: libc::c_int) -> CString { + unsafe { CStr::from_ptr(strsignal(sig)).to_owned() } +} diff --git a/lucet-runtime/lucet-runtime-internals/src/lib.rs b/lucet-runtime/lucet-runtime-internals/src/lib.rs index bea486467..168f91cb1 100644 --- a/lucet-runtime/lucet-runtime-internals/src/lib.rs +++ b/lucet-runtime/lucet-runtime-internals/src/lib.rs @@ -6,6 +6,9 @@ #[macro_use] pub mod error; +#[macro_use] +pub mod hostcall_macros; +pub use lucet_runtime_macros::lucet_hostcall; #[macro_use] #[cfg(test)] @@ -18,7 +21,7 @@ pub mod embed_ctx; pub mod instance; pub mod module; pub mod region; -pub mod trapcode; +pub mod sysdeps; pub mod val; pub mod vmctx; diff --git a/lucet-runtime/lucet-runtime-internals/src/module.rs b/lucet-runtime/lucet-runtime-internals/src/module.rs index 6722df0ca..6cbf679e9 100644 --- a/lucet-runtime/lucet-runtime-internals/src/module.rs +++ b/lucet-runtime/lucet-runtime-internals/src/module.rs @@ -1,73 +1,17 @@ mod dl; -mod globals; mod mock; mod sparse_page_data; pub use crate::module::dl::DlModule; -pub use crate::module::mock::MockModuleBuilder; -pub use lucet_module_data::{Global, GlobalSpec, HeapSpec}; +pub use crate::module::mock::{MockExportBuilder, MockModuleBuilder}; +pub use lucet_module::{ + FunctionHandle, FunctionIndex, FunctionPointer, FunctionSpec, Global, GlobalSpec, GlobalValue, + HeapSpec, Signature, TableElement, TrapCode, TrapManifest, ValueType, +}; use crate::alloc::Limits; use crate::error::Error; -use crate::trapcode::TrapCode; use libc::c_void; -use std::slice::from_raw_parts; - -#[repr(C)] -#[derive(Clone, Debug)] -pub struct TrapManifestRecord { - pub func_addr: u64, - pub func_len: u64, - pub table_addr: u64, - pub table_len: u64, -} - -impl TrapManifestRecord { - pub fn contains_addr(&self, addr: *const c_void) -> bool { - let addr = addr as u64; - // TODO: is this correct? off-by-one error? - addr >= self.func_addr && addr <= self.func_addr + self.func_len - } - - pub fn trapsites(&self) -> &[TrapSite] { - let table_addr = self.table_addr as *const TrapSite; - assert!(!table_addr.is_null()); - unsafe { from_raw_parts(table_addr, self.table_len as usize) } - } - - pub fn lookup_addr(&self, addr: *const c_void) -> Option { - if !self.contains_addr(addr) { - return None; - } - - // predicate to find the trapsite for the addr via binary search - let f = - |ts: &TrapSite| (self.func_addr as usize + ts.offset as usize).cmp(&(addr as usize)); - - let trapsites = self.trapsites(); - if let Ok(i) = trapsites.binary_search_by(f) { - let trapcode = - TrapCode::try_from_u32(trapsites[i].trapcode).expect("valid trapcode value"); - Some(trapcode) - } else { - None - } - } -} - -#[repr(C)] -#[derive(Copy, Clone, Debug)] -pub struct TrapSite { - pub offset: u32, - pub trapcode: u32, -} - -#[repr(C)] -#[derive(Clone, Debug)] -pub struct TableElement { - ty: u64, - rf: u64, -} /// Details about a program address. /// @@ -86,16 +30,21 @@ pub struct AddrDetails { /// /// Types that implement this trait are suitable for use with /// [`Region::new_instance()`](trait.Region.html#method.new_instance). -pub trait Module: ModuleInternal {} +pub trait Module: ModuleInternal { + /// Calculate the initial size in bytes of the module's Wasm globals. + fn initial_globals_size(&self) -> usize { + self.globals().len() * std::mem::size_of::() + } +} pub trait ModuleInternal: Send + Sync { - fn heap_spec(&self) -> &HeapSpec; + fn heap_spec(&self) -> Option<&HeapSpec>; /// Get the WebAssembly globals of the module. /// /// The indices into the returned slice correspond to the WebAssembly indices of the globals /// () - fn globals(&self) -> &[GlobalSpec]; + fn globals(&self) -> &[GlobalSpec<'_>]; fn get_sparse_page_data(&self, page: usize) -> Option<&[u8]>; @@ -105,34 +54,41 @@ pub trait ModuleInternal: Send + Sync { /// Get the table elements from the module. fn table_elements(&self) -> Result<&[TableElement], Error>; - fn get_export_func(&self, sym: &[u8]) -> Result<*const extern "C" fn(), Error>; + fn get_export_func(&self, sym: &str) -> Result; - fn get_func_from_idx( - &self, - table_id: u32, - func_id: u32, - ) -> Result<*const extern "C" fn(), Error>; + fn get_func_from_idx(&self, table_id: u32, func_id: u32) -> Result; - fn get_start_func(&self) -> Result, Error>; + fn get_start_func(&self) -> Result, Error>; - fn trap_manifest(&self) -> &[TrapManifestRecord]; + fn function_manifest(&self) -> &[FunctionSpec]; fn addr_details(&self, addr: *const c_void) -> Result, Error>; + fn get_signature(&self, fn_id: FunctionIndex) -> &Signature; + + fn function_handle_from_ptr(&self, ptr: FunctionPointer) -> FunctionHandle { + let id = self + .function_manifest() + .iter() + .enumerate() + .find(|(_, fn_spec)| fn_spec.ptr() == ptr) + .map(|(fn_id, _)| FunctionIndex::from_u32(fn_id as u32)) + .expect("valid function pointer"); + + FunctionHandle { ptr, id } + } + /// Look up an instruction pointer in the trap manifest. /// /// This function must be signal-safe. fn lookup_trapcode(&self, rip: *const c_void) -> Option { - for record in self.trap_manifest() { - if record.contains_addr(rip) { - // the trap falls within a known function - if let Some(trapcode) = record.lookup_addr(rip) { - return Some(trapcode); - } else { - // stop looking through the rest of the trap manifests; only one function should - // ever match - break; - } + for fn_spec in self.function_manifest() { + if let Some(offset) = fn_spec.relative_addr(rip as u64) { + // the address falls in this trap manifest's function. + // `rip` can only lie in one function, so either + // there's a trap site in this manifest, and that's + // the one we want, or there's none + return fn_spec.traps().and_then(|traps| traps.lookup_addr(offset)); } } None @@ -143,32 +99,36 @@ pub trait ModuleInternal: Send + Sync { /// Returns a `Result<(), Error>` rather than a boolean in order to provide a richer accounting /// of what may be invalid. fn validate_runtime_spec(&self, limits: &Limits) -> Result<(), Error> { - let heap = self.heap_spec(); - // Assure that the total reserved + guard regions fit in the address space. - // First check makes sure they fit our 32-bit model, and ensures the second - // check doesn't overflow. - if heap.reserved_size > std::u32::MAX as u64 + 1 - || heap.guard_size > std::u32::MAX as u64 + 1 - { - return Err(lucet_incorrect_module!( - "heap spec sizes would overflow: {:?}", - heap - )); - } + // Modules without heap specs will not access the heap + if let Some(heap) = self.heap_spec() { + // Assure that the total reserved + guard regions fit in the address space. + // First check makes sure they fit our 32-bit model, and ensures the second + // check doesn't overflow. + if heap.reserved_size > std::u32::MAX as u64 + 1 + || heap.guard_size > std::u32::MAX as u64 + 1 + { + return Err(lucet_incorrect_module!( + "heap spec sizes would overflow: {:?}", + heap + )); + } - if heap.reserved_size as usize + heap.guard_size as usize > limits.heap_address_space_size { - bail_limits_exceeded!("heap spec reserved and guard size: {:?}", heap); - } + if heap.reserved_size as usize + heap.guard_size as usize + > limits.heap_address_space_size + { + bail_limits_exceeded!("heap spec reserved and guard size: {:?}", heap); + } - if heap.initial_size as usize > limits.heap_memory_size { - bail_limits_exceeded!("heap spec initial size: {:?}", heap); - } + if heap.initial_size as usize > limits.heap_memory_size { + bail_limits_exceeded!("heap spec initial size: {:?}", heap); + } - if heap.initial_size > heap.reserved_size { - return Err(lucet_incorrect_module!( - "initial heap size greater than reserved size: {:?}", - heap - )); + if heap.initial_size > heap.reserved_size { + return Err(lucet_incorrect_module!( + "initial heap size greater than reserved size: {:?}", + heap + )); + } } if self.globals().len() * std::mem::size_of::() > limits.globals_size { diff --git a/lucet-runtime/lucet-runtime-internals/src/module/dl.rs b/lucet-runtime/lucet-runtime-internals/src/module/dl.rs index 3991e06bb..36b070989 100644 --- a/lucet-runtime/lucet-runtime-internals/src/module/dl.rs +++ b/lucet-runtime/lucet-runtime-internals/src/module/dl.rs @@ -1,17 +1,77 @@ use crate::error::Error; -use crate::module::{ - AddrDetails, GlobalSpec, HeapSpec, Module, ModuleInternal, TableElement, TrapManifestRecord, -}; +use crate::module::{AddrDetails, GlobalSpec, HeapSpec, Module, ModuleInternal, TableElement}; use libc::c_void; -use libloading::{Library, Symbol}; -use lucet_module_data::ModuleData; +use libloading::Library; +use lucet_module::{ + FunctionHandle, FunctionIndex, FunctionPointer, FunctionSpec, ModuleData, ModuleFeatures, + ModuleSignature, PublicKey, SerializedModule, Signature, VersionInfo, LUCET_MODULE_SYM, +}; use std::ffi::CStr; -use std::mem; +use std::mem::MaybeUninit; use std::path::Path; use std::slice; use std::slice::from_raw_parts; use std::sync::Arc; +use raw_cpuid::CpuId; + +fn check_feature_support(module_features: &ModuleFeatures) -> Result<(), Error> { + let cpuid = CpuId::new(); + + fn missing_feature(feature: &str) -> Error { + Error::Unsupported(format!( + "Module requires feature host does not support: {}", + feature + )) + } + + let info = cpuid + .get_feature_info() + .ok_or_else(|| Error::Unsupported("Unable to obtain host CPU feature info!".to_string()))?; + + if module_features.sse3 && !info.has_sse3() { + return Err(missing_feature("SSE3")); + } + if module_features.ssse3 && !info.has_ssse3() { + return Err(missing_feature("SSS3")); + } + if module_features.sse41 && !info.has_sse41() { + return Err(missing_feature("SSE4.1")); + } + if module_features.sse42 && !info.has_sse42() { + return Err(missing_feature("SSE4.2")); + } + if module_features.avx && !info.has_avx() { + return Err(missing_feature("AVX")); + } + if module_features.popcnt && !info.has_popcnt() { + return Err(missing_feature("POPCNT")); + } + + let info = cpuid.get_extended_feature_info().ok_or_else(|| { + Error::Unsupported("Unable to obtain host CPU extended feature info!".to_string()) + })?; + + if module_features.bmi1 && !info.has_bmi1() { + return Err(missing_feature("BMI1")); + } + + if module_features.bmi2 && !info.has_bmi2() { + return Err(missing_feature("BMI2")); + } + + let info = cpuid.get_extended_function_info().ok_or_else(|| { + Error::Unsupported("Unable to obtain host CPU extended function info!".to_string()) + })?; + + if module_features.lzcnt && !info.has_lzcnt() { + return Err(missing_feature("LZCNT")); + } + + // Features are fine, we're compatible! + Ok(()) +} + /// A Lucet module backed by a dynamically-loaded shared object. pub struct DlModule { lib: Library, @@ -20,9 +80,7 @@ pub struct DlModule { fbase: *const c_void, /// Metadata decoded from inside the module - module_data: ModuleData<'static>, - - trap_manifest: &'static [TrapManifestRecord], + module: lucet_module::Module<'static>, } // for the one raw pointer only @@ -32,6 +90,19 @@ unsafe impl Sync for DlModule {} impl DlModule { /// Create a module, loading code from a shared object on the filesystem. pub fn load>(so_path: P) -> Result, Error> { + Self::load_and_maybe_verify(so_path, None) + } + + /// Create a module, loading code from a shared object on the filesystem + /// and verifying it using a public key if one has been supplied. + pub fn load_and_verify>(so_path: P, pk: PublicKey) -> Result, Error> { + Self::load_and_maybe_verify(so_path, Some(pk)) + } + + fn load_and_maybe_verify>( + so_path: P, + pk: Option, + ) -> Result, Error> { // Load the dynamic library. The undefined symbols corresponding to the lucet_syscall_ // functions will be provided by the current executable. We trust our wasm->dylib compiler // to make sure these function calls are the way the dylib can touch memory outside of its @@ -39,20 +110,30 @@ impl DlModule { let abs_so_path = so_path.as_ref().canonicalize().map_err(Error::DlError)?; let lib = Library::new(abs_so_path.as_os_str()).map_err(Error::DlError)?; - let module_data_ptr = unsafe { - lib.get::<*const u8>(b"lucet_module_data").map_err(|e| { - lucet_incorrect_module!("error loading required symbol `lucet_module_data`: {}", e) - })? + let serialized_module_ptr = unsafe { + lib.get::<*const SerializedModule>(LUCET_MODULE_SYM.as_bytes()) + .map_err(|e| { + lucet_incorrect_module!("error loading required symbol `lucet_module`: {}", e) + })? }; - let module_data_len = unsafe { - lib.get::(b"lucet_module_data_len").map_err(|e| { - lucet_incorrect_module!( - "error loading required symbol `lucet_module_data_len`: {}", - e - ) - })? - }; + let serialized_module: &SerializedModule = + unsafe { serialized_module_ptr.as_ref().unwrap() }; + + let module_version = serialized_module.version.clone(); + + let runtime_version = + VersionInfo::current(include_str!(concat!(env!("OUT_DIR"), "/commit_hash")).as_bytes()); + + if !module_version.valid() { + return Err(lucet_incorrect_module!("reserved bit is not set. This module is likely too old for this lucet-runtime to load.")); + } else if !runtime_version.compatible_with(&module_version) { + return Err(lucet_incorrect_module!( + "version mismatch. module has version {}, while this runtime is version {}", + module_version, + runtime_version, + )); + } // Deserialize the slice into ModuleData, which will hold refs into the loaded // shared object file in `module_data_slice`. Both of these get a 'static lifetime because @@ -61,41 +142,60 @@ impl DlModule { // // The exposed lifetime of ModuleData will be the same as the lifetime of the // dynamically loaded library. This makes the interface safe. - let module_data_slice: &'static [u8] = - unsafe { slice::from_raw_parts(*module_data_ptr, *module_data_len) }; + let module_data_slice: &'static [u8] = unsafe { + slice::from_raw_parts( + serialized_module.module_data_ptr as *const u8, + serialized_module.module_data_len as usize, + ) + }; let module_data = ModuleData::deserialize(module_data_slice)?; - let fbase = if let Some(dli) = dladdr(*module_data_ptr as *const c_void) { + check_feature_support(module_data.features())?; + + // If a public key has been provided, verify the module signature + // The TOCTOU issue is unavoidable without reimplenting `dlopen(3)` + if let Some(pk) = pk { + ModuleSignature::verify(so_path, &pk, &module_data)?; + } + + let fbase = if let Some(dli) = + dladdr(serialized_module as *const SerializedModule as *const c_void) + { dli.dli_fbase } else { std::ptr::null() }; - let trap_manifest = unsafe { - if let Ok(len_ptr) = lib.get::<*const u32>(b"lucet_trap_manifest_len") { - let len = len_ptr.as_ref().ok_or(lucet_incorrect_module!( - "`lucet_trap_manifest_len` is defined but null" - ))?; - let records = lib - .get::<*const TrapManifestRecord>(b"lucet_trap_manifest") - .map_err(|e| { - lucet_incorrect_module!("error loading symbol `lucet_trap_manifest`: {}", e) - })? - .as_ref() - .ok_or(lucet_incorrect_module!( - "`lucet_trap_manifest` is defined but null" - ))?; - from_raw_parts(records, *len as usize) - } else { - &[] + if serialized_module.tables_len > std::u32::MAX as u64 { + lucet_incorrect_module!("table segment too long: {}", serialized_module.tables_len); + } + let tables: &'static [&'static [TableElement]] = unsafe { + from_raw_parts( + serialized_module.tables_ptr as *const &[TableElement], + serialized_module.tables_len as usize, + ) + }; + + let function_manifest = if serialized_module.function_manifest_ptr != 0 { + unsafe { + from_raw_parts( + serialized_module.function_manifest_ptr as *const FunctionSpec, + serialized_module.function_manifest_len as usize, + ) } + } else { + &[] }; Ok(Arc::new(DlModule { lib, fbase, - module_data, - trap_manifest, + module: lucet_module::Module { + version: module_version, + module_data, + tables, + function_manifest, + }, })) } } @@ -103,95 +203,79 @@ impl DlModule { impl Module for DlModule {} impl ModuleInternal for DlModule { - fn heap_spec(&self) -> &HeapSpec { - self.module_data.heap_spec() + fn heap_spec(&self) -> Option<&HeapSpec> { + self.module.module_data.heap_spec() } - fn globals(&self) -> &[GlobalSpec] { - self.module_data.globals_spec() + fn globals(&self) -> &[GlobalSpec<'_>] { + self.module.module_data.globals_spec() } fn get_sparse_page_data(&self, page: usize) -> Option<&[u8]> { - *self.module_data.sparse_data().get_page(page) + if let Some(ref sparse_data) = self.module.module_data.sparse_data() { + *sparse_data.get_page(page) + } else { + None + } } fn sparse_page_data_len(&self) -> usize { - self.module_data.sparse_data().len() + self.module + .module_data + .sparse_data() + .map(|d| d.len()) + .unwrap_or(0) } fn table_elements(&self) -> Result<&[TableElement], Error> { - let p_table_segment: Symbol<*const TableElement> = unsafe { - self.lib.get(b"guest_table_0").map_err(|e| { - lucet_incorrect_module!("error loading required symbol `guest_table_0`: {}", e) - })? - }; - let p_table_segment_len: Symbol<*const usize> = unsafe { - self.lib.get(b"guest_table_0_len").map_err(|e| { - lucet_incorrect_module!("error loading required symbol `guest_table_0_len`: {}", e) - })? - }; - let len = unsafe { **p_table_segment_len }; - let elem_size = mem::size_of::(); - if len > std::u32::MAX as usize * elem_size { - lucet_incorrect_module!("table segment too long: {}", len); - } - if len % elem_size != 0 { - lucet_incorrect_module!( - "table segment length {} not a multiple of table element size: {}", - len, - elem_size - ); - } - Ok(unsafe { from_raw_parts(*p_table_segment, **p_table_segment_len as usize / elem_size) }) - } - - fn get_export_func(&self, sym: &[u8]) -> Result<*const extern "C" fn(), Error> { - let mut guest_sym: Vec = b"guest_func_".to_vec(); - guest_sym.extend_from_slice(sym); - match unsafe { self.lib.get::<*const extern "C" fn()>(&guest_sym) } { - Err(ref e) if is_undefined_symbol(e) => Err(Error::SymbolNotFound( - String::from_utf8_lossy(sym).into_owned(), - )), - Err(e) => Err(Error::DlError(e)), - Ok(f) => Ok(*f), + match self.module.tables.get(0) { + Some(table) => Ok(table), + None => Err(lucet_incorrect_module!("table 0 is not present")), } } - fn get_func_from_idx( - &self, - table_id: u32, - func_id: u32, - ) -> Result<*const extern "C" fn(), Error> { + fn get_export_func(&self, sym: &str) -> Result { + self.module + .module_data + .get_export_func_id(sym) + .ok_or_else(|| Error::SymbolNotFound(sym.to_string())) + .map(|id| { + let ptr = self.function_manifest()[id.as_u32() as usize].ptr(); + FunctionHandle { ptr, id } + }) + } + + fn get_func_from_idx(&self, table_id: u32, func_id: u32) -> Result { if table_id != 0 { return Err(Error::FuncNotFound(table_id, func_id)); } let table = self.table_elements()?; - let func: extern "C" fn() = table + let func = table .get(func_id as usize) - .map(|element| unsafe { std::mem::transmute(element.rf) }) + .map(|element| element.function_pointer()) .ok_or(Error::FuncNotFound(table_id, func_id))?; - Ok(&func as *const extern "C" fn()) + + Ok(self.function_handle_from_ptr(func)) } - fn get_start_func(&self) -> Result, Error> { + fn get_start_func(&self) -> Result, Error> { // `guest_start` is a pointer to the function the module designates as the start function, // since we can't have multiple symbols pointing to the same function and guest code might // call it in the normal course of execution - if let Ok(start_func) = unsafe { - self.lib - .get::<*const *const extern "C" fn()>(b"guest_start") - } { + if let Ok(start_func) = unsafe { self.lib.get::<*const extern "C" fn()>(b"guest_start") } { if start_func.is_null() { lucet_incorrect_module!("`guest_start` is defined but null"); } - Ok(Some(unsafe { **start_func })) + Ok(Some(self.function_handle_from_ptr( + FunctionPointer::from_usize(unsafe { **start_func } as usize), + ))) } else { Ok(None) } } - fn trap_manifest(&self) -> &[TrapManifestRecord] { - self.trap_manifest + fn function_manifest(&self) -> &[FunctionSpec] { + self.module.function_manifest } fn addr_details(&self, addr: *const c_void) -> Result, Error> { @@ -215,21 +299,19 @@ impl ModuleInternal for DlModule { Ok(None) } } -} -fn is_undefined_symbol(e: &std::io::Error) -> bool { - // gross, but I'm not sure how else to differentiate this type of error from other - // IO errors - format!("{}", e).contains("undefined symbol") + fn get_signature(&self, fn_id: FunctionIndex) -> &Signature { + self.module.module_data.get_signature(fn_id) + } } // TODO: PR to nix or libloading? // TODO: possibly not safe to use without grabbing the mutex within libloading::Library? fn dladdr(addr: *const c_void) -> Option { - let mut info = unsafe { mem::uninitialized::() }; - let res = unsafe { libc::dladdr(addr, &mut info as *mut libc::Dl_info) }; + let mut info = MaybeUninit::::uninit(); + let res = unsafe { libc::dladdr(addr, info.as_mut_ptr()) }; if res != 0 { - Some(info) + Some(unsafe { info.assume_init() }) } else { None } diff --git a/lucet-runtime/lucet-runtime-internals/src/module/globals.rs b/lucet-runtime/lucet-runtime-internals/src/module/globals.rs deleted file mode 100644 index 8616eb1bc..000000000 --- a/lucet-runtime/lucet-runtime-internals/src/module/globals.rs +++ /dev/null @@ -1,113 +0,0 @@ -#[macro_export] -macro_rules! globals_tests { - ( $TestRegion:path ) => { - use std::sync::Arc; - use $TestRegion as TestRegion; - use $crate::alloc::Limits; - use $crate::error::Error; - use $crate::module::{MockModuleBuilder, Module}; - use $crate::region::Region; - use $crate::vmctx::{lucet_vmctx, Vmctx}; - - fn mock_import_module() -> Arc { - MockModuleBuilder::new() - .with_import(0, "something", "else") - .build() - } - - #[test] - fn reject_import() { - let module = mock_import_module(); - let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); - match region.new_instance(module) { - Ok(_) => panic!("instance creation should not succeed"), - Err(Error::Unsupported(_)) => (), - Err(e) => panic!("unexpected error: {}", e), - } - } - - fn mock_globals_module() -> Arc { - extern "C" fn get_global0(vmctx: *mut lucet_vmctx) -> i64 { - unsafe { Vmctx::from_raw(vmctx) }.globals()[0] - } - - extern "C" fn set_global0(vmctx: *mut lucet_vmctx, val: i64) { - unsafe { Vmctx::from_raw(vmctx) }.globals_mut()[0] = val; - } - - extern "C" fn get_global1(vmctx: *mut lucet_vmctx) -> i64 { - unsafe { Vmctx::from_raw(vmctx) }.globals()[1] - } - - MockModuleBuilder::new() - .with_global(0, -1) - .with_global(1, 420) - .with_export_func(b"get_global0", get_global0 as *const extern "C" fn()) - .with_export_func(b"set_global0", set_global0 as *const extern "C" fn()) - .with_export_func(b"get_global1", get_global1 as *const extern "C" fn()) - .build() - } - - /* replace with use of instance public api to make sure defined globals are initialized - * correctly - */ - - #[test] - fn globals_initialized() { - let module = mock_globals_module(); - let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); - let inst = region - .new_instance(module) - .expect("instance can be created"); - assert_eq!(inst.globals()[0], -1); - assert_eq!(inst.globals()[1], 420); - } - - #[test] - fn get_global0() { - let module = mock_globals_module(); - let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); - let mut inst = region - .new_instance(module) - .expect("instance can be created"); - - let retval = inst.run(b"get_global0", &[]).expect("instance runs"); - assert_eq!(i64::from(retval), -1); - } - - #[test] - fn get_both_globals() { - let module = mock_globals_module(); - let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); - let mut inst = region - .new_instance(module) - .expect("instance can be created"); - - let retval = inst.run(b"get_global0", &[]).expect("instance runs"); - assert_eq!(i64::from(retval), -1); - - let retval = inst.run(b"get_global1", &[]).expect("instance runs"); - assert_eq!(i64::from(retval), 420); - } - - #[test] - fn mutate_global0() { - let module = mock_globals_module(); - let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); - let mut inst = region - .new_instance(module) - .expect("instance can be created"); - - inst.run(b"set_global0", &[666i64.into()]) - .expect("instance runs"); - - let retval = inst.run(b"get_global0", &[]).expect("instance runs"); - assert_eq!(i64::from(retval), 666); - } - }; -} - -#[cfg(test)] -mod tests { - globals_tests!(crate::region::mmap::MmapRegion); -} diff --git a/lucet-runtime/lucet-runtime-internals/src/module/mock.rs b/lucet-runtime/lucet-runtime-internals/src/module/mock.rs index 857fbb88b..0602bd388 100644 --- a/lucet-runtime/lucet-runtime-internals/src/module/mock.rs +++ b/lucet-runtime/lucet-runtime-internals/src/module/mock.rs @@ -1,10 +1,14 @@ use crate::error::Error; -use crate::module::{ - AddrDetails, GlobalSpec, HeapSpec, Module, ModuleInternal, TableElement, TrapManifestRecord, -}; +use crate::module::{AddrDetails, GlobalSpec, HeapSpec, Module, ModuleInternal, TableElement}; use libc::c_void; -use lucet_module_data::owned::{OwnedGlobalSpec, OwnedModuleData, OwnedSparseData}; -use lucet_module_data::ModuleData; +use lucet_module::owned::{ + OwnedExportFunction, OwnedFunctionMetadata, OwnedGlobalSpec, OwnedImportFunction, + OwnedLinearMemorySpec, OwnedModuleData, OwnedSparseData, +}; +use lucet_module::{ + FunctionHandle, FunctionIndex, FunctionPointer, FunctionSpec, ModuleData, ModuleFeatures, + Signature, TrapSite, UniqueSignatureIndex, +}; use std::collections::{BTreeMap, HashMap}; use std::sync::Arc; @@ -14,10 +18,14 @@ pub struct MockModuleBuilder { sparse_page_data: Vec>>, globals: BTreeMap, table_elements: BTreeMap, - export_funcs: HashMap, *const extern "C" fn()>, - func_table: HashMap<(u32, u32), *const extern "C" fn()>, - start_func: Option, - trap_manifest: Vec, + export_funcs: HashMap<&'static str, FunctionPointer>, + func_table: HashMap<(u32, u32), FunctionPointer>, + start_func: Option, + function_manifest: Vec, + function_info: Vec, + imports: Vec, + exports: Vec, + signatures: Vec, } impl MockModuleBuilder { @@ -56,14 +64,14 @@ impl MockModuleBuilder { pub fn with_global(mut self, idx: u32, init_val: i64) -> Self { self.globals - .insert(idx as usize, OwnedGlobalSpec::new_def(init_val, None)); + .insert(idx as usize, OwnedGlobalSpec::new_def(init_val, vec![])); self } pub fn with_exported_global(mut self, idx: u32, init_val: i64, export_name: &str) -> Self { self.globals.insert( idx as usize, - OwnedGlobalSpec::new_def(init_val, Some(export_name.to_string())), + OwnedGlobalSpec::new_def(init_val, vec![export_name.to_string()]), ); self } @@ -71,7 +79,11 @@ impl MockModuleBuilder { pub fn with_import(mut self, idx: u32, import_module: &str, import_field: &str) -> Self { self.globals.insert( idx as usize, - OwnedGlobalSpec::new_import(import_module.to_string(), import_field.to_string(), None), + OwnedGlobalSpec::new_import( + import_module.to_string(), + import_field.to_string(), + vec![], + ), ); self } @@ -88,7 +100,7 @@ impl MockModuleBuilder { OwnedGlobalSpec::new_import( import_module.to_string(), import_field.to_string(), - Some(export_name.to_string()), + vec![export_name.to_string()], ), ); self @@ -99,32 +111,81 @@ impl MockModuleBuilder { self } - pub fn with_export_func(mut self, sym: &[u8], func: *const extern "C" fn()) -> Self { - self.export_funcs.insert(sym.to_vec(), func); + fn record_sig(&mut self, sig: Signature) -> UniqueSignatureIndex { + let idx = self + .signatures + .iter() + .enumerate() + .find(|(_, v)| *v == &sig) + .map(|(key, _)| key) + .unwrap_or_else(|| { + self.signatures.push(sig); + self.signatures.len() - 1 + }); + UniqueSignatureIndex::from_u32(idx as u32) + } + + pub fn with_export_func(mut self, export: MockExportBuilder) -> Self { + self.export_funcs.insert(export.sym(), export.func()); + let sig_idx = self.record_sig(export.sig()); + self.function_info.push(OwnedFunctionMetadata { + signature: sig_idx, + name: Some(export.sym().to_string()), + }); + self.exports.push(OwnedExportFunction { + fn_idx: FunctionIndex::from_u32(self.function_manifest.len() as u32), + names: vec![export.sym().to_string()], + }); + self.function_manifest.push(FunctionSpec::new( + export.func().as_usize() as u64, + export.func_len() as u32, + export.traps().as_ptr() as u64, + export.traps().len() as u64, + )); self } - pub fn with_table_func( + pub fn with_exported_import_func( mut self, - table_idx: u32, - func_idx: u32, - func: *const extern "C" fn(), + export_name: &'static str, + import_fn_ptr: FunctionPointer, + sig: Signature, ) -> Self { - self.func_table.insert((table_idx, func_idx), func); + self.export_funcs.insert(export_name, import_fn_ptr); + let sig_idx = self.record_sig(sig); + self.function_info.push(OwnedFunctionMetadata { + signature: sig_idx, + name: Some(export_name.to_string()), + }); + self.exports.push(OwnedExportFunction { + fn_idx: FunctionIndex::from_u32(self.function_manifest.len() as u32), + names: vec![export_name.to_string()], + }); + self.function_manifest.push(FunctionSpec::new( + import_fn_ptr.as_usize() as u64, + 0u32, + 0u64, + 0u64, + )); self } - pub fn with_start_func(mut self, func: extern "C" fn()) -> Self { - self.start_func = Some(func); + pub fn with_table_func(mut self, table_idx: u32, func_idx: u32, func: FunctionPointer) -> Self { + self.func_table.insert((table_idx, func_idx), func); self } - pub fn with_trap_manifest(mut self, trap_manifest: &[TrapManifestRecord]) -> Self { - self.trap_manifest = trap_manifest.to_vec(); + pub fn with_start_func(mut self, func: FunctionPointer) -> Self { + self.start_func = Some(func); self } pub fn build(self) -> Arc { + assert!( + self.sparse_page_data.len() * 4096 <= self.heap_spec.initial_size as usize, + "heap must fit in heap spec initial size" + ); + let table_elements = self .table_elements .into_iter() @@ -150,9 +211,17 @@ impl MockModuleBuilder { }) .collect(); let owned_module_data = OwnedModuleData::new( - self.heap_spec, - OwnedSparseData::new(self.sparse_page_data).expect("sparse data pages are valid"), + Some(OwnedLinearMemorySpec { + heap: self.heap_spec, + initializer: OwnedSparseData::new(self.sparse_page_data) + .expect("sparse data pages are valid"), + }), globals_spec, + self.function_info.clone(), + self.imports, + self.exports, + self.signatures, + ModuleFeatures::none(), ); let serialized_module_data = owned_module_data .to_ref() @@ -168,7 +237,7 @@ impl MockModuleBuilder { export_funcs: self.export_funcs, func_table: self.func_table, start_func: self.start_func, - trap_manifest: self.trap_manifest, + function_manifest: self.function_manifest, }; Arc::new(mock) } @@ -179,10 +248,10 @@ pub struct MockModule { serialized_module_data: Vec, module_data: ModuleData<'static>, pub table_elements: Vec, - pub export_funcs: HashMap, *const extern "C" fn()>, - pub func_table: HashMap<(u32, u32), *const extern "C" fn()>, - pub start_func: Option, - pub trap_manifest: Vec, + pub export_funcs: HashMap<&'static str, FunctionPointer>, + pub func_table: HashMap<(u32, u32), FunctionPointer>, + pub start_func: Option, + pub function_manifest: Vec, } unsafe impl Send for MockModule {} @@ -191,52 +260,57 @@ unsafe impl Sync for MockModule {} impl Module for MockModule {} impl ModuleInternal for MockModule { - fn heap_spec(&self) -> &HeapSpec { + fn heap_spec(&self) -> Option<&HeapSpec> { self.module_data.heap_spec() } - fn globals(&self) -> &[GlobalSpec] { + fn globals(&self) -> &[GlobalSpec<'_>] { self.module_data.globals_spec() } fn get_sparse_page_data(&self, page: usize) -> Option<&[u8]> { - *self.module_data.sparse_data().get_page(page) + if let Some(ref sparse_data) = self.module_data.sparse_data() { + *sparse_data.get_page(page) + } else { + None + } } fn sparse_page_data_len(&self) -> usize { - self.module_data.sparse_data().len() + self.module_data.sparse_data().map(|d| d.len()).unwrap_or(0) } fn table_elements(&self) -> Result<&[TableElement], Error> { Ok(&self.table_elements) } - fn get_export_func(&self, sym: &[u8]) -> Result<*const extern "C" fn(), Error> { - self.export_funcs + fn get_export_func(&self, sym: &str) -> Result { + let ptr = *self + .export_funcs .get(sym) - .cloned() - .ok_or(Error::SymbolNotFound( - String::from_utf8_lossy(sym).into_owned(), - )) + .ok_or(Error::SymbolNotFound(sym.to_string()))?; + + Ok(self.function_handle_from_ptr(ptr)) } - fn get_func_from_idx( - &self, - table_id: u32, - func_id: u32, - ) -> Result<*const extern "C" fn(), Error> { - self.func_table + fn get_func_from_idx(&self, table_id: u32, func_id: u32) -> Result { + let ptr = self + .func_table .get(&(table_id, func_id)) .cloned() - .ok_or(Error::FuncNotFound(table_id, func_id)) + .ok_or(Error::FuncNotFound(table_id, func_id))?; + + Ok(self.function_handle_from_ptr(ptr)) } - fn get_start_func(&self) -> Result, Error> { - Ok(self.start_func.map(|start| start as *const extern "C" fn())) + fn get_start_func(&self) -> Result, Error> { + Ok(self + .start_func + .map(|start| self.function_handle_from_ptr(start))) } - fn trap_manifest(&self) -> &[TrapManifestRecord] { - &self.trap_manifest + fn function_manifest(&self) -> &[FunctionSpec] { + &self.function_manifest } fn addr_details(&self, _addr: *const c_void) -> Result, Error> { @@ -244,4 +318,62 @@ impl ModuleInternal for MockModule { // a way to determine whether or not we're in "module" code; punt for now Ok(None) } + + fn get_signature(&self, fn_id: FunctionIndex) -> &Signature { + self.module_data.get_signature(fn_id) + } +} + +pub struct MockExportBuilder { + sym: &'static str, + func: FunctionPointer, + func_len: Option, + traps: Option<&'static [TrapSite]>, + sig: Signature, +} + +impl MockExportBuilder { + pub fn new(name: &'static str, func: FunctionPointer) -> MockExportBuilder { + MockExportBuilder { + sym: name, + func: func, + func_len: None, + traps: None, + sig: Signature { + params: vec![], + ret_ty: None, + }, + } + } + + pub fn with_func_len(mut self, len: usize) -> MockExportBuilder { + self.func_len = Some(len); + self + } + + pub fn with_traps(mut self, traps: &'static [TrapSite]) -> MockExportBuilder { + self.traps = Some(traps); + self + } + + pub fn with_sig(mut self, sig: Signature) -> MockExportBuilder { + self.sig = sig; + self + } + + pub fn sym(&self) -> &'static str { + self.sym + } + pub fn func(&self) -> FunctionPointer { + self.func + } + pub fn func_len(&self) -> usize { + self.func_len.unwrap_or(1) + } + pub fn traps(&self) -> &'static [TrapSite] { + self.traps.unwrap_or(&[]) + } + pub fn sig(&self) -> Signature { + self.sig.clone() + } } diff --git a/lucet-runtime/lucet-runtime-internals/src/region/mmap.rs b/lucet-runtime/lucet-runtime-internals/src/region/mmap.rs index 2d96d570f..76d9d6ce1 100644 --- a/lucet-runtime/lucet-runtime-internals/src/region/mmap.rs +++ b/lucet-runtime/lucet-runtime-internals/src/region/mmap.rs @@ -3,17 +3,60 @@ use crate::embed_ctx::CtxMap; use crate::error::Error; use crate::instance::{new_instance_handle, Instance, InstanceHandle}; use crate::module::Module; -use crate::region::{Region, RegionInternal}; +use crate::region::{Region, RegionCreate, RegionInternal}; +#[cfg(not(target_os = "linux"))] +use libc::memset; use libc::{c_void, SIGSTKSZ}; use nix::sys::mman::{madvise, mmap, munmap, MapFlags, MmapAdvise, ProtFlags}; use std::ptr; use std::sync::{Arc, Mutex, Weak}; -/// A [`Region`](trait.Region.html) backed by `mmap`. +/// A [`Region`](../trait.Region.html) backed by `mmap`. +/// +/// `MmapRegion` lays out memory for instances in a contiguous block, +/// with an Instance's space reserved, followed by heap, stack, globals, and sigstack. +/// +/// This results in an actual layout of an instance on an `MmapRegion`-produced `Slot` being: +/// ```text +/// 0x0000: +-----------------------+ <-- Instance +/// 0x0000: | .magic | +/// 0x0008: | ... | +/// 0x000X: | ... | +/// 0x0XXX: | .alloc -> Alloc { | +/// 0x0XXX: | .start = 0x0000 | +/// 0x0XXX: | .heap = 0x1000 | +/// 0x0XXX: | .stack = 0xN000 | +/// 0x0XXX: | .globals = 0xM000 | +/// 0x0XXX: | .sigstack = 0xS000 | +/// 0x0XXX: | } | +/// 0x0XXX: | ... | +/// 0x0XXX: ~ ~padding~ ~ +/// 0x0XXX: | ... | +/// 0x0XXX: | .globals = 0xM000 | <-- InstanceRuntimeData +/// 0x0XXX: | .inst_count = 0x0000 | +/// 0x1000: +-----------------------+ <-- Heap, and `lucet_vmctx`. One page into the allocation. +/// 0x1XXX: | | +/// 0xXXXX: ~ .......heap....... ~ // heap size is governed by limits.heap_address_space_size +/// 0xXXXX: | | +/// 0xN000: +-----------------------| <-- Stack (at heap_start + limits.heap_address_space_size) +/// 0xNXXX: --- stack guard page ---- +/// 0xNXXX: | | +/// 0xXXXX: ~ .......stack...... ~ // stack size is governed by limits.stack_size +/// 0xXXXX: | | +/// 0xM000: +-----------------------| <-- Globals (at stack_start + limits.stack_size + PAGE_SIZE) +/// 0xMXXX: | | +/// 0xXXXX: ~ ......globals..... ~ +/// 0xXXXX: | | +/// 0xXXXX --- global guard page --- +/// 0xS000: +-----------------------| <-- Sigstack (at globals_start + globals_size + PAGE_SIZE) +/// 0xSXXX: | ......sigstack.... | // sigstack is SIGSTKSZ bytes +/// 0xSXXX: +-----------------------| +/// ``` pub struct MmapRegion { capacity: usize, freelist: Mutex>, limits: Limits, + min_heap_alignment: usize, } impl Region for MmapRegion {} @@ -46,7 +89,7 @@ impl RegionInternal for MmapRegion { // make the sigstack read/writable (slot.sigstack, SIGSTKSZ), ] - .into_iter() + .iter() { // eprintln!("setting r/w {:p}[{:x}]", *ptr, len); unsafe { mprotect(*ptr, *len, ProtFlags::PROT_READ | ProtFlags::PROT_WRITE)? }; @@ -89,15 +132,23 @@ impl RegionInternal for MmapRegion { // clear and disable access to the heap, stack, globals, and sigstack for (ptr, len) in [ - (slot.heap, slot.limits.heap_address_space_size), + // We don't ever shrink the heap, so we only need to zero up until the accessible size + (slot.heap, alloc.heap_accessible_size), (slot.stack, slot.limits.stack_size), (slot.globals, slot.limits.globals_size), (slot.sigstack, SIGSTKSZ), ] - .into_iter() + .iter() { // eprintln!("setting none {:p}[{:x}]", *ptr, len); unsafe { + // MADV_DONTNEED is not guaranteed to clear pages on non-Linux systems + #[cfg(not(target_os = "linux"))] + { + mprotect(*ptr, *len, ProtFlags::PROT_READ | ProtFlags::PROT_WRITE) + .expect("mprotect succeeds during drop"); + memset(*ptr, 0, *len); + } mprotect(*ptr, *len, ProtFlags::PROT_NONE).expect("mprotect succeeds during drop"); madvise(*ptr, *len, MmapAdvise::MADV_DONTNEED) .expect("madvise succeeds during drop"); @@ -126,15 +177,29 @@ impl RegionInternal for MmapRegion { let heap_size = alloc.slot().limits.heap_address_space_size; unsafe { + // `mprotect()` and `madvise()` are sufficient to zero a page on Linux, + // but not necessarily on all POSIX operating systems, and on macOS in particular. + #[cfg(not(target_os = "linux"))] + { + mprotect( + heap, + alloc.heap_accessible_size, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + )?; + memset(heap, 0, alloc.heap_accessible_size); + } mprotect(heap, heap_size, ProtFlags::PROT_NONE)?; madvise(heap, heap_size, MmapAdvise::MADV_DONTNEED)?; } } - let initial_size = module.heap_spec().initial_size as usize; + let initial_size = module + .heap_spec() + .map(|h| h.initial_size as usize) + .unwrap_or(0); // reset the heap to the initial size, and mprotect those pages appropriately - if alloc.heap_accessible_size != initial_size { + if initial_size > 0 { unsafe { mprotect( heap, @@ -142,10 +207,9 @@ impl RegionInternal for MmapRegion { ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, )? }; - alloc.heap_accessible_size = initial_size; - alloc.heap_inaccessible_size = - alloc.slot().limits.heap_address_space_size - initial_size; } + alloc.heap_accessible_size = initial_size; + alloc.heap_inaccessible_size = alloc.slot().limits.heap_address_space_size - initial_size; // Initialize the heap using the module sparse page data. There cannot be more pages in the // sparse page data than will fit in the initial heap size. @@ -193,6 +257,14 @@ impl Drop for MmapRegion { } } +impl RegionCreate for MmapRegion { + const TYPE_NAME: &'static str = "MmapRegion"; + + fn create(instance_capacity: usize, limits: &Limits) -> Result, Error> { + MmapRegion::create(instance_capacity, limits) + } +} + impl MmapRegion { /// Create a new `MmapRegion` that can support a given number instances, each subject to the /// same runtime limits. @@ -210,6 +282,48 @@ impl MmapRegion { capacity: instance_capacity, freelist: Mutex::new(Vec::with_capacity(instance_capacity)), limits: limits.clone(), + min_heap_alignment: 0, // No constaints on heap alignment by default + }); + { + let mut freelist = region.freelist.lock().unwrap(); + for _ in 0..instance_capacity { + freelist.push(MmapRegion::create_slot(®ion)?); + } + } + + Ok(region) + } + + /// Create a new `MmapRegion` that can support a given number instances, each subject to the + /// same runtime limits. Additionally, ensure that the heap is aligned at least to the + /// specified amount. heap_alignment must be a power of 2. + /// + /// The region is returned in an `Arc`, because any instances created from it carry a reference + /// back to the region. + pub fn create_aligned( + instance_capacity: usize, + limits: &Limits, + heap_alignment: usize, + ) -> Result, Error> { + assert!( + SIGSTKSZ % host_page_size() == 0, + "signal stack size is a multiple of host page size" + ); + limits.validate()?; + + let is_power_of_2 = (heap_alignment & (heap_alignment - 1)) == 0; + + if !is_power_of_2 { + return Err(Error::InvalidArgument( + "heap_alignment must be a power of 2", + )); + } + + let region = Arc::new(MmapRegion { + capacity: instance_capacity, + freelist: Mutex::new(Vec::with_capacity(instance_capacity)), + limits: limits.clone(), + min_heap_alignment: heap_alignment, }); { let mut freelist = region.freelist.lock().unwrap(); @@ -223,15 +337,27 @@ impl MmapRegion { fn create_slot(region: &Arc) -> Result { // get the chunk of virtual memory that the `Slot` will manage - let mem = unsafe { - mmap( - ptr::null_mut(), - region.limits.total_memory_size(), - ProtFlags::PROT_NONE, - MapFlags::MAP_ANONYMOUS | MapFlags::MAP_PRIVATE, - 0, - 0, - )? + let mem = if region.min_heap_alignment == 0 { + unsafe { + mmap( + ptr::null_mut(), + region.limits.total_memory_size(), + ProtFlags::PROT_NONE, + MapFlags::MAP_ANON | MapFlags::MAP_PRIVATE, + 0, + 0, + )? + } + } else { + unsafe { + mmap_aligned( + region.limits.total_memory_size(), + ProtFlags::PROT_NONE, + MapFlags::MAP_ANON | MapFlags::MAP_PRIVATE, + region.min_heap_alignment, // requested alignment + instance_heap_offset(), // offset that must be aligned + )? + } }; // set the first part of the memory to read/write so that the `Instance` can be stored there @@ -246,8 +372,8 @@ impl MmapRegion { // lay out the other sections in memory let heap = mem as usize + instance_heap_offset(); - let stack = heap + region.limits.heap_address_space_size; - let globals = stack + region.limits.stack_size + host_page_size(); + let stack = heap + region.limits.heap_address_space_size + host_page_size(); + let globals = stack + region.limits.stack_size; let sigstack = globals + host_page_size(); Ok(Slot { @@ -272,7 +398,138 @@ impl MmapRegion { } } +// Note alignment must be a power of 2 +// Offset must be a multiple of 4Kb (page size) +unsafe fn mmap_aligned( + requested_length: usize, + prot: ProtFlags, + flags: MapFlags, + alignment: usize, + alignment_offset: usize, +) -> Result<*mut c_void, Error> { + let addr = ptr::null_mut(); + let fd = 0; + let offset = 0; + + let padded_length = requested_length + alignment + alignment_offset; + let unaligned = mmap(addr, padded_length, prot, flags, fd, offset)? as usize; + + // Round up the next address that has addr % alignment = 0 + let aligned_nonoffset = (unaligned + (alignment - 1)) & !(alignment - 1); + + // Currently offset 0 is aligned according to alignment + // Alignment needs to be enforced at the given offset + let aligned = if aligned_nonoffset - alignment_offset >= unaligned { + aligned_nonoffset - alignment_offset + } else { + aligned_nonoffset - alignment_offset + alignment + }; + + //Sanity check + if aligned < unaligned + || (aligned + (requested_length - 1)) > (unaligned + (padded_length - 1)) + || (aligned + alignment_offset) % alignment != 0 + { + // explicitly ignore failures now, as this is just a best-effort clean up after the last fail + let _ = munmap(unaligned as *mut c_void, padded_length); + return Err(Error::Unsupported("Could not align memory".to_string())); + } + + { + let unused_front = aligned - unaligned; + if unused_front != 0 { + if munmap(unaligned as *mut c_void, unused_front).is_err() { + // explicitly ignore failures now, as this is just a best-effort clean up after the last fail + let _ = munmap(unaligned as *mut c_void, padded_length); + return Err(Error::Unsupported("Could not align memory".to_string())); + } + } + } + + { + let unused_back = (unaligned + (padded_length - 1)) - (aligned + (requested_length - 1)); + if unused_back != 0 { + if munmap((aligned + requested_length) as *mut c_void, unused_back).is_err() { + // explicitly ignore failures now, as this is just a best-effort clean up after the last fail + let _ = munmap(unaligned as *mut c_void, padded_length); + return Err(Error::Unsupported("Could not align memory".to_string())); + } + } + } + + return Ok(aligned as *mut c_void); +} + // TODO: remove this once `nix` PR https://github.com/nix-rust/nix/pull/991 is merged unsafe fn mprotect(addr: *mut c_void, length: libc::size_t, prot: ProtFlags) -> nix::Result<()> { nix::errno::Errno::result(libc::mprotect(addr, length, prot.bits())).map(drop) } + +#[cfg(test)] +mod tests2 { + use super::*; + use nix::sys::mman::{munmap, MapFlags, ProtFlags}; + + #[test] + fn test_aligned_mem() { + let kb: usize = 1024; + let mb: usize = 1024 * kb; + + struct TestProps { + pub mem_size: usize, + pub mem_align: usize, + pub offset: usize, + }; + + let tests = vec![ + TestProps { + mem_size: 1 * mb, + mem_align: 1 * mb, + offset: 0, + }, + TestProps { + mem_size: 1 * mb, + mem_align: 2 * mb, + offset: 0, + }, + TestProps { + mem_size: 32 * mb, + mem_align: 32 * mb, + offset: 0, + }, + TestProps { + mem_size: 32 * mb, + mem_align: 32 * mb, + offset: 4 * kb, + }, + ]; + + for test in tests { + let mem = unsafe { + mmap_aligned( + test.mem_size, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + MapFlags::MAP_ANON | MapFlags::MAP_PRIVATE, + test.mem_align, + test.offset, + ) + .unwrap() + }; + + // Check alignment + let actual_align = ((mem as usize) + test.offset) % test.mem_align; + assert_eq!(actual_align, 0); + + // Make sure the memory is accessible + let mem_slice = + unsafe { std::slice::from_raw_parts_mut(mem as *mut u8, test.mem_size) }; + for loc in mem_slice { + *loc = 1; + } + + unsafe { + munmap(mem, test.mem_size).unwrap(); + } + } + } +} diff --git a/lucet-runtime/lucet-runtime-internals/src/region/mod.rs b/lucet-runtime/lucet-runtime-internals/src/region/mod.rs index 46734e9d7..3b0bc82a8 100644 --- a/lucet-runtime/lucet-runtime-internals/src/region/mod.rs +++ b/lucet-runtime/lucet-runtime-internals/src/region/mod.rs @@ -1,6 +1,6 @@ pub mod mmap; -use crate::alloc::{Alloc, Slot}; +use crate::alloc::{Alloc, Limits, Slot}; use crate::embed_ctx::CtxMap; use crate::error::Error; use crate::instance::InstanceHandle; @@ -54,6 +54,19 @@ pub trait RegionInternal: Send + Sync { fn as_dyn_internal(&self) -> &dyn RegionInternal; } +/// A trait for regions that are created with a fixed capacity and limits. +/// +/// This is not part of [`Region`](trait.Region.html) so that `Region` types can be made into trait +/// objects. +pub trait RegionCreate: Region { + /// The type name of the region; useful for testing. + const TYPE_NAME: &'static str; + + /// Create a new `Region` that can support a given number instances, each subject to the same + /// runtime limits. + fn create(instance_capacity: usize, limits: &Limits) -> Result, Error>; +} + /// A builder for instances; created by /// [`Region::new_instance_builder()`](trait.Region.html#method.new_instance_builder). pub struct InstanceBuilder<'a> { @@ -67,7 +80,7 @@ impl<'a> InstanceBuilder<'a> { InstanceBuilder { region, module, - embed_ctx: CtxMap::new(), + embed_ctx: CtxMap::default(), } } diff --git a/lucet-runtime/lucet-runtime-internals/src/sysdeps/linux.rs b/lucet-runtime/lucet-runtime-internals/src/sysdeps/linux.rs new file mode 100644 index 000000000..fb576d8a1 --- /dev/null +++ b/lucet-runtime/lucet-runtime-internals/src/sysdeps/linux.rs @@ -0,0 +1,50 @@ +use libc::{c_void, ucontext_t, REG_RIP}; + +#[derive(Clone, Copy, Debug)] +pub struct UContextPtr(*const ucontext_t); + +impl UContextPtr { + #[inline] + pub fn new(ptr: *const c_void) -> Self { + assert!(!ptr.is_null(), "non-null context"); + UContextPtr(ptr as *const ucontext_t) + } + + #[inline] + pub fn get_ip(self) -> *const c_void { + let mcontext = &unsafe { *(self.0) }.uc_mcontext; + mcontext.gregs[REG_RIP as usize] as *const _ + } +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct UContext { + context: ucontext_t, +} + +impl UContext { + #[inline] + pub fn new(ptr: *const c_void) -> Self { + UContext { + context: *unsafe { + (ptr as *const ucontext_t) + .as_ref() + .expect("non-null context") + }, + } + } + + pub fn as_ptr(&mut self) -> UContextPtr { + UContextPtr::new(&self.context as *const _ as *const _) + } +} + +impl Into for UContextPtr { + #[inline] + fn into(self) -> UContext { + UContext { + context: unsafe { *(self.0) }, + } + } +} diff --git a/lucet-runtime/lucet-runtime-internals/src/sysdeps/macos.rs b/lucet-runtime/lucet-runtime-internals/src/sysdeps/macos.rs new file mode 100644 index 000000000..3ea2e1c86 --- /dev/null +++ b/lucet-runtime/lucet-runtime-internals/src/sysdeps/macos.rs @@ -0,0 +1,171 @@ +use libc::{c_int, c_short, c_void, sigset_t, size_t}; +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct sigaltstack { + pub ss_sp: *const c_void, + pub ss_size: size_t, + pub ss_flags: c_int, +} + +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct x86_exception_state64 { + pub trapno: u16, + pub cpu: u16, + pub err: u32, + pub faultvaddr: u64, +} + +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct x86_thread_state64 { + pub rax: u64, + pub rbx: u64, + pub rcx: u64, + pub rdx: u64, + pub rdi: u64, + pub rsi: u64, + pub rbp: u64, + pub rsp: u64, + pub r8: u64, + pub r9: u64, + pub r10: u64, + pub r11: u64, + pub r12: u64, + pub r13: u64, + pub r14: u64, + pub r15: u64, + pub rip: u64, + pub rflags: u64, + pub cs: u64, + pub fs: u64, + pub gs: u64, +} + +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct mmst_reg { + pub mmst_reg: [u8; 10], + pub rsrv: [u8; 6], +} + +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct xmm_reg([u8; 16]); + +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct x86_float_state64 { + pub fpu_reserved: [c_int; 2], + pub fpu_fcw: c_short, + pub fpu_fsw: c_short, + pub fpu_ftw: u8, + pub fpu_rsrv1: u8, + pub fpu_fop: u16, + pub fpu_ip: u32, + pub fpu_cs: u16, + pub fpu_rsrv2: u16, + pub fpu_dp: u32, + pub fpu_ds: u16, + pub fpu_rsrv3: u16, + pub fpu_mxcsr: u32, + pub fpu_mxcsrmask: u32, + pub fpu_stmm0: mmst_reg, + pub fpu_stmm1: mmst_reg, + pub fpu_stmm2: mmst_reg, + pub fpu_stmm3: mmst_reg, + pub fpu_stmm4: mmst_reg, + pub fpu_stmm5: mmst_reg, + pub fpu_stmm6: mmst_reg, + pub fpu_stmm7: mmst_reg, + pub fpu_xmm0: xmm_reg, + pub fpu_xmm1: xmm_reg, + pub fpu_xmm2: xmm_reg, + pub fpu_xmm3: xmm_reg, + pub fpu_xmm4: xmm_reg, + pub fpu_xmm5: xmm_reg, + pub fpu_xmm6: xmm_reg, + pub fpu_xmm7: xmm_reg, + pub fpu_xmm8: xmm_reg, + pub fpu_xmm9: xmm_reg, + pub fpu_xmm10: xmm_reg, + pub fpu_xmm11: xmm_reg, + pub fpu_xmm12: xmm_reg, + pub fpu_xmm13: xmm_reg, + pub fpu_xmm14: xmm_reg, + pub fpu_xmm15: xmm_reg, + pub fpu_rsrv4_0: [u8; 16], + pub fpu_rsrv4_1: [u8; 16], + pub fpu_rsrv4_2: [u8; 16], + pub fpu_rsrv4_3: [u8; 16], + pub fpu_rsrv4_4: [u8; 16], + pub fpu_rsrv4_5: [u8; 16], + pub fpu_reserved1: c_int, +} + +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct mcontext64 { + pub es: x86_exception_state64, + pub ss: x86_thread_state64, + pub fs: x86_float_state64, +} +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct ucontext_t { + pub uc_onstack: c_int, + pub uc_sigmask: sigset_t, + pub uc_stack: sigaltstack, + pub uc_link: *const ucontext_t, + pub uc_mcsize: size_t, + pub uc_mcontext: *const mcontext64, +} + +#[derive(Clone, Copy, Debug)] +pub struct UContextPtr(*const ucontext_t); + +impl UContextPtr { + #[inline] + pub fn new(ptr: *const c_void) -> Self { + assert!(!ptr.is_null(), "non-null context"); + UContextPtr(ptr as *const ucontext_t) + } + + #[inline] + pub fn get_ip(self) -> *const c_void { + let mcontext = &unsafe { *(*self.0).uc_mcontext }; + mcontext.ss.rip as *const _ + } +} + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct UContext { + context: ucontext_t, + mcontext: mcontext64, +} + +impl UContext { + #[inline] + pub fn new(ptr: *const c_void) -> Self { + let context = *unsafe { + (ptr as *const ucontext_t) + .as_ref() + .expect("non-null context") + }; + let mcontext = unsafe { *context.uc_mcontext }; + UContext { context, mcontext } + } + + pub fn as_ptr(&mut self) -> UContextPtr { + self.context.uc_mcontext = &self.mcontext; + UContextPtr::new(&self.context as *const _ as *const _) + } +} + +impl Into for UContextPtr { + #[inline] + fn into(self) -> UContext { + UContext::new(self.0 as *const _) + } +} diff --git a/lucet-runtime/lucet-runtime-internals/src/sysdeps/mod.rs b/lucet-runtime/lucet-runtime-internals/src/sysdeps/mod.rs new file mode 100644 index 000000000..8f9247c4f --- /dev/null +++ b/lucet-runtime/lucet-runtime-internals/src/sysdeps/mod.rs @@ -0,0 +1,11 @@ +#[cfg(target_os = "macos")] +mod macos; + +#[cfg(target_os = "linux")] +mod linux; + +#[cfg(target_os = "macos")] +pub use macos::*; + +#[cfg(target_os = "linux")] +pub use linux::*; diff --git a/lucet-runtime/lucet-runtime-internals/src/trapcode.rs b/lucet-runtime/lucet-runtime-internals/src/trapcode.rs deleted file mode 100644 index 50c91061b..000000000 --- a/lucet-runtime/lucet-runtime-internals/src/trapcode.rs +++ /dev/null @@ -1,50 +0,0 @@ -use num_derive::FromPrimitive; -use num_traits::FromPrimitive; - -/// The details associated with a WebAssembly -/// [trap](http://webassembly.github.io/spec/core/intro/overview.html#trap). -#[derive(Copy, Clone, Debug)] -pub struct TrapCode { - pub ty: TrapCodeType, - /// Mainly for internal testing, this field will likely be deprecated soon. - pub tag: u16, -} - -impl std::fmt::Display for TrapCode { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - if self.ty == TrapCodeType::User { - write!(f, "{:?}({})", self.ty, self.tag) - } else { - write!(f, "{:?}", self.ty) - } - } -} - -impl TrapCode { - pub fn try_from_u32(trapcode_bin: u32) -> Option { - let trapcode_type = (trapcode_bin & 0x0000FFFF) as u16; - TrapCodeType::from_u16(trapcode_type).map(|ty| { - let tag = (trapcode_bin >> 16) as u16; - TrapCode { ty, tag } - }) - } -} - -/// The type of a WebAssembly -/// [trap](http://webassembly.github.io/spec/core/intro/overview.html#trap). -#[repr(u16)] -#[derive(Copy, Clone, Debug, FromPrimitive, PartialEq)] -pub enum TrapCodeType { - StackOverflow = 0, - HeapOutOfBounds = 1, - OutOfBounds = 2, - IndirectCallToNull = 3, - BadSignature = 4, - IntegerOverflow = 5, - IntegerDivByZero = 6, - BadConversionToInteger = 7, - Interrupt = 8, - TableOutOfBounds = 9, - User = 0xFFFF, - Unknown = 0xFFFE, -} diff --git a/lucet-runtime/lucet-runtime-internals/src/val.rs b/lucet-runtime/lucet-runtime-internals/src/val.rs index 8b836ce39..c2f2fe51c 100644 --- a/lucet-runtime/lucet-runtime-internals/src/val.rs +++ b/lucet-runtime/lucet-runtime-internals/src/val.rs @@ -7,6 +7,26 @@ use std::arch::x86_64::{ _mm_storeu_pd, _mm_storeu_ps, }; +use lucet_module::ValueType; + +impl Val { + pub fn value_type(&self) -> ValueType { + match self { + // USize, ISize, and CPtr are all as fits for definitions on the target architecture + // (wasm) which is all 32-bit. + Val::USize(_) | Val::ISize(_) | Val::CPtr(_) => ValueType::I32, + Val::GuestPtr(_) => ValueType::I32, + Val::I8(_) | Val::U8(_) | Val::I16(_) | Val::U16(_) | Val::I32(_) | Val::U32(_) => { + ValueType::I32 + } + Val::I64(_) | Val::U64(_) => ValueType::I64, + Val::Bool(_) => ValueType::I32, + Val::F32(_) => ValueType::F32, + Val::F64(_) => ValueType::F64, + } + } +} + /// Typed values used for passing arguments into guest functions. #[derive(Clone, Copy, Debug)] pub enum Val { @@ -145,7 +165,7 @@ pub struct UntypedRetVal { } impl std::fmt::Display for UntypedRetVal { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "") } } @@ -156,6 +176,21 @@ impl UntypedRetVal { } } +impl From for UntypedRetVal { + fn from(reg: RegVal) -> UntypedRetVal { + match reg { + RegVal::GpReg(r) => UntypedRetVal::new(r, unsafe { _mm_setzero_ps() }), + RegVal::FpReg(r) => UntypedRetVal::new(0, r), + } + } +} + +impl> From for UntypedRetVal { + fn from(v: T) -> UntypedRetVal { + val_to_reg(&v.into()).into() + } +} + macro_rules! impl_from_fp { ( $ty:ty, $f:ident, $as:ident ) => { impl From for $ty { diff --git a/lucet-runtime/lucet-runtime-internals/src/vmctx.rs b/lucet-runtime/lucet-runtime-internals/src/vmctx.rs index 1c25e8a91..b666f3574 100644 --- a/lucet-runtime/lucet-runtime-internals/src/vmctx.rs +++ b/lucet-runtime/lucet-runtime-internals/src/vmctx.rs @@ -9,15 +9,42 @@ use crate::alloc::instance_heap_offset; use crate::context::Context; use crate::error::Error; use crate::instance::{ - Instance, InstanceHandle, InstanceInternal, State, TerminationDetails, CURRENT_INSTANCE, - HOST_CTX, + EmptyYieldVal, Instance, InstanceInternal, State, TerminationDetails, YieldedVal, + CURRENT_INSTANCE, HOST_CTX, }; +use lucet_module::{FunctionHandle, GlobalValue}; use std::any::Any; +use std::borrow::{Borrow, BorrowMut}; +use std::cell::{Ref, RefCell, RefMut}; +use std::marker::PhantomData; /// An opaque handle to a running instance's context. #[derive(Debug)] pub struct Vmctx { vmctx: *mut lucet_vmctx, + /// A view of the underlying instance's heap. + /// + /// This must never be dropped automatically, as the view does not own the heap. Rather, this is + /// a value used to implement dynamic borrowing of the heap contents that are owned and managed + /// by the instance and its `Alloc`. + heap_view: RefCell>, + /// A view of the underlying instance's globals. + /// + /// This must never be dropped automatically, as the view does not own the globals. Rather, this + /// is a value used to implement dynamic borrowing of the globals that are owned and managed by + /// the instance and its `Alloc`. + globals_view: RefCell>, +} + +impl Drop for Vmctx { + fn drop(&mut self) { + let heap_view = self.heap_view.replace(Box::new([])); + let globals_view = self.globals_view.replace(Box::new([])); + // as described in the definition of `Vmctx`, we cannot allow the boxed views of the heap + // and globals to be dropped + Box::leak(heap_view); + Box::leak(globals_view); + } } pub trait VmctxInternal { @@ -33,6 +60,25 @@ pub trait VmctxInternal { /// you could not use orthogonal `&mut` refs that come from `Vmctx`, like the heap or /// terminating the instance. unsafe fn instance_mut(&self) -> &mut Instance; + + /// Try to take and return the value passed to `Instance::resume_with_val()`. + /// + /// If there is no resumed value, or if the dynamic type check of the value fails, this returns + /// `None`. + fn try_take_resumed_val(&self) -> Option; + + /// Suspend the instance, returning a value in + /// [`RunResult::Yielded`](../enum.RunResult.html#variant.Yielded) to where the instance was run + /// or resumed. + /// + /// After suspending, the instance may be resumed by calling + /// [`Instance::resume_with_val()`](../struct.Instance.html#method.resume_with_val) from the + /// host with a value of type `R`. If resumed with a value of some other type, this returns + /// `None`. + /// + /// The dynamic type checks used by the other yield methods should make this explicit option + /// type redundant, however this interface is used to avoid exposing a panic to the C API. + fn yield_val_try_val(&mut self, val: A) -> Option; } impl VmctxInternal for Vmctx { @@ -43,16 +89,43 @@ impl VmctxInternal for Vmctx { unsafe fn instance_mut(&self) -> &mut Instance { instance_from_vmctx(self.vmctx) } + + fn try_take_resumed_val(&self) -> Option { + let inst = unsafe { self.instance_mut() }; + if let Some(val) = inst.resumed_val.take() { + match val.downcast() { + Ok(val) => Some(*val), + Err(val) => { + inst.resumed_val = Some(val); + None + } + } + } else { + None + } + } + + fn yield_val_try_val(&mut self, val: A) -> Option { + self.yield_impl::(val); + self.try_take_resumed_val() + } } impl Vmctx { - /// Create a `Vmctx` from the compiler-inserted `vmctx` argument in a guest - /// function. + /// Create a `Vmctx` from the compiler-inserted `vmctx` argument in a guest function. + /// + /// This is almost certainly not what you want to use to get a `Vmctx`; instead use the first + /// argument of a function with the `#[lucet_hostcall]` attribute, which must have the type + /// `&mut Vmctx`. pub unsafe fn from_raw(vmctx: *mut lucet_vmctx) -> Vmctx { - let res = Vmctx { vmctx }; - // we don't actually need the instance for this call, but asking for it here causes an - // earlier failure if the pointer isn't valid - assert!(res.instance().valid_magic()); + let inst = instance_from_vmctx(vmctx); + assert!(inst.valid_magic()); + + let res = Vmctx { + vmctx, + heap_view: RefCell::new(Box::<[u8]>::from_raw(inst.heap_mut())), + globals_view: RefCell::new(Box::<[GlobalValue]>::from_raw(inst.globals_mut())), + }; res } @@ -62,13 +135,55 @@ impl Vmctx { } /// Return the WebAssembly heap as a slice of bytes. - pub fn heap(&self) -> &[u8] { - self.instance().heap() + /// + /// If the heap is already mutably borrowed by `heap_mut()`, the instance will + /// terminate with `TerminationDetails::BorrowError`. + pub fn heap(&self) -> Ref<'_, [u8]> { + unsafe { + self.reconstitute_heap_view_if_needed(); + } + let r = self + .heap_view + .try_borrow() + .unwrap_or_else(|_| panic!(TerminationDetails::BorrowError("heap"))); + Ref::map(r, |b| b.borrow()) } /// Return the WebAssembly heap as a mutable slice of bytes. - pub fn heap_mut(&mut self) -> &mut [u8] { - unsafe { self.instance_mut().heap_mut() } + /// + /// If the heap is already borrowed by `heap()` or `heap_mut()`, the instance will terminate + /// with `TerminationDetails::BorrowError`. + pub fn heap_mut(&self) -> RefMut<'_, [u8]> { + unsafe { + self.reconstitute_heap_view_if_needed(); + } + let r = self + .heap_view + .try_borrow_mut() + .unwrap_or_else(|_| panic!(TerminationDetails::BorrowError("heap_mut"))); + RefMut::map(r, |b| b.borrow_mut()) + } + + /// Check whether the heap has grown, and replace the heap view if it has. + /// + /// This handles the case where `Vmctx::grow_memory()` and `Vmctx::heap()` are called in + /// sequence. Since `Vmctx::grow_memory()` takes `&mut self`, heap references cannot live across + /// it. + /// + /// TODO: There is still an unsound case, though, when a heap reference is held across a call + /// back into the guest via `Vmctx::get_func_from_idx()`. That guest code may grow the heap as + /// well, causing any outstanding heap references to become invalid. We will address this when + /// we rework the interface for calling back into the guest. + unsafe fn reconstitute_heap_view_if_needed(&self) { + let inst = self.instance_mut(); + if inst.heap_mut().len() != self.heap_view.borrow().len() { + let old_heap_view = self + .heap_view + .replace(Box::<[u8]>::from_raw(inst.heap_mut())); + // as described in the definition of `Vmctx`, we cannot allow the boxed view of the heap + // to be dropped + Box::leak(old_heap_view); + } } /// Check whether a given range in the host address space overlaps with the memory that backs @@ -82,24 +197,43 @@ impl Vmctx { self.instance().contains_embed_ctx::() } - /// Get a reference to a context value of a particular type. If it does not exist, - /// the context will terminate. - pub fn get_embed_ctx(&self) -> &T { - unsafe { self.instance_mut().get_embed_ctx_or_term() } + /// Get a reference to a context value of a particular type. + /// + /// If a context of that type does not exist, the instance will terminate with + /// `TerminationDetails::CtxNotFound`. + /// + /// If the context is already mutably borrowed by `get_embed_ctx_mut`, the instance will + /// terminate with `TerminationDetails::BorrowError`. + pub fn get_embed_ctx(&self) -> Ref<'_, T> { + match self.instance().embed_ctx.try_get::() { + Some(Ok(t)) => t, + Some(Err(_)) => panic!(TerminationDetails::BorrowError("get_embed_ctx")), + None => panic!(TerminationDetails::CtxNotFound), + } } - /// Get a mutable reference to a context value of a particular type> If it does not exist, - /// the context will terminate. - pub fn get_embed_ctx_mut(&mut self) -> &mut T { - unsafe { self.instance_mut().get_embed_ctx_mut_or_term() } + /// Get a mutable reference to a context value of a particular type. + /// + /// If a context of that type does not exist, the instance will terminate with + /// `TerminationDetails::CtxNotFound`. + /// + /// If the context is already borrowed by some other use of `get_embed_ctx` or + /// `get_embed_ctx_mut`, the instance will terminate with `TerminationDetails::BorrowError`. + pub fn get_embed_ctx_mut(&self) -> RefMut<'_, T> { + match unsafe { self.instance_mut().embed_ctx.try_get_mut::() } { + Some(Ok(t)) => t, + Some(Err(_)) => panic!(TerminationDetails::BorrowError("get_embed_ctx_mut")), + None => panic!(TerminationDetails::CtxNotFound), + } } - /// Terminate this guest and return to the host context. + /// Terminate this guest and return to the host context without unwinding. /// - /// This will return an `Error::RuntimeTerminated` value to the caller of `Instance::run()`. - pub fn terminate(&mut self, info: I) -> ! { - let details = TerminationDetails::provide(info); - unsafe { self.instance_mut().terminate(details) } + /// This is almost certainly not what you want to use to terminate an instance from a hostcall, + /// as any resources currently in scope will not be dropped. Instead, use + /// `lucet_hostcall_terminate!` which unwinds to the enclosing hostcall body. + pub unsafe fn terminate_no_unwind(&mut self, details: TerminationDetails) -> ! { + self.instance_mut().terminate(details) } /// Grow the guest memory by the given number of WebAssembly pages. @@ -110,13 +244,27 @@ impl Vmctx { } /// Return the WebAssembly globals as a slice of `i64`s. - pub fn globals(&self) -> &[i64] { - self.instance().globals() + /// + /// If the globals are already mutably borrowed by `globals_mut()`, the instance will terminate + /// with `TerminationDetails::BorrowError`. + pub fn globals(&self) -> Ref<'_, [GlobalValue]> { + let r = self + .globals_view + .try_borrow() + .unwrap_or_else(|_| panic!(TerminationDetails::BorrowError("globals"))); + Ref::map(r, |b| b.borrow()) } /// Return the WebAssembly globals as a mutable slice of `i64`s. - pub fn globals_mut(&mut self) -> &mut [i64] { - unsafe { self.instance_mut().globals_mut() } + /// + /// If the globals are already borrowed by `globals()` or `globals_mut()`, the instance will + /// terminate with `TerminationDetails::BorrowError`. + pub fn globals_mut(&self) -> RefMut<'_, [GlobalValue]> { + let r = self + .globals_view + .try_borrow_mut() + .unwrap_or_else(|_| panic!(TerminationDetails::BorrowError("globals_mut"))); + RefMut::map(r, |b| b.borrow_mut()) } /// Get a function pointer by WebAssembly table and function index. @@ -124,50 +272,118 @@ impl Vmctx { /// This is useful when a hostcall takes a function pointer as its argument, as WebAssembly uses /// table indices as its runtime representation of function pointers. /// + /// # Safety + /// /// We do not currently reflect function type information into the Rust type system, so callers /// of the returned function must take care to cast it to the correct type before calling. The /// correct type will include the `vmctx` argument, which the caller is responsible for passing /// from its own context. /// + /// There is currently no guarantee that guest functions will return before faulting, or + /// terminating the instance in a subsequent hostcall. This means that any Rust resources that + /// are held open when the guest function is called might be leaked if the guest function, for + /// example, divides by zero. Work to make this safer is + /// [ongoing](https://github.com/bytecodealliance/lucet/pull/254). + /// /// ```no_run + /// use lucet_runtime_macros::lucet_hostcall; + /// use lucet_runtime_internals::lucet_hostcall_terminate; /// use lucet_runtime_internals::vmctx::{lucet_vmctx, Vmctx}; + /// + /// #[lucet_hostcall] /// #[no_mangle] - /// extern "C" fn hostcall_call_binop( - /// vmctx: *mut lucet_vmctx, + /// pub unsafe extern "C" fn hostcall_call_binop( + /// vmctx: &mut Vmctx, /// binop_table_idx: u32, /// binop_func_idx: u32, /// operand1: u32, /// operand2: u32, /// ) -> u32 { - /// let mut ctx = unsafe { Vmctx::from_raw(vmctx) }; - /// if let Ok(binop) = ctx.get_func_from_idx(binop_table_idx, binop_func_idx) { - /// let typed_binop = binop as *const extern "C" fn(*mut lucet_vmctx, u32, u32) -> u32; - /// unsafe { (*typed_binop)(vmctx, operand1, operand2) } + /// if let Ok(binop) = vmctx.get_func_from_idx(binop_table_idx, binop_func_idx) { + /// let typed_binop = std::mem::transmute::< + /// usize, + /// extern "C" fn(*mut lucet_vmctx, u32, u32) -> u32, + /// >(binop.ptr.as_usize()); + /// unsafe { (typed_binop)(vmctx.as_raw(), operand1, operand2) } /// } else { - /// ctx.terminate("invalid function index") + /// lucet_hostcall_terminate!("invalid function index") /// } /// } + /// ``` pub fn get_func_from_idx( &self, table_idx: u32, func_idx: u32, - ) -> Result<*const extern "C" fn(), Error> { + ) -> Result { self.instance() .module() .get_func_from_idx(table_idx, func_idx) } -} -/// Terminating an instance requires mutating the state field, and then jumping back to the -/// host context. The mutable borrow may conflict with a mutable borrow of the embed_ctx if -/// this is performed via a method call. We use a macro so we can convince the borrow checker that -/// this is safe at each use site. -macro_rules! inst_terminate { - ($self:ident, $details:expr) => {{ - $self.state = State::Terminated { details: $details }; - #[allow(unused_unsafe)] // The following unsafe will be incorrectly warned as unused - HOST_CTX.with(|host_ctx| unsafe { Context::set(&*host_ctx.get()) }) - }}; + /// Suspend the instance, returning an empty + /// [`RunResult::Yielded`](../enum.RunResult.html#variant.Yielded) to where the instance was run + /// or resumed. + /// + /// After suspending, the instance may be resumed by the host using + /// [`Instance::resume()`](../struct.Instance.html#method.resume). + /// + /// (The reason for the trailing underscore in the name is that Rust reserves `yield` as a + /// keyword for future use.) + pub fn yield_(&mut self) { + self.yield_val_expecting_val::(EmptyYieldVal); + } + + /// Suspend the instance, returning an empty + /// [`RunResult::Yielded`](../enum.RunResult.html#variant.Yielded) to where the instance was run + /// or resumed. + /// + /// After suspending, the instance may be resumed by calling + /// [`Instance::resume_with_val()`](../struct.Instance.html#method.resume_with_val) from the + /// host with a value of type `R`. + pub fn yield_expecting_val(&mut self) -> R { + self.yield_val_expecting_val::(EmptyYieldVal) + } + + /// Suspend the instance, returning a value in + /// [`RunResult::Yielded`](../enum.RunResult.html#variant.Yielded) to where the instance was run + /// or resumed. + /// + /// After suspending, the instance may be resumed by the host using + /// [`Instance::resume()`](../struct.Instance.html#method.resume). + pub fn yield_val(&mut self, val: A) { + self.yield_val_expecting_val::(val); + } + + /// Suspend the instance, returning a value in + /// [`RunResult::Yielded`](../enum.RunResult.html#variant.Yielded) to where the instance was run + /// or resumed. + /// + /// After suspending, the instance may be resumed by calling + /// [`Instance::resume_with_val()`](../struct.Instance.html#method.resume_with_val) from the + /// host with a value of type `R`. + pub fn yield_val_expecting_val(&mut self, val: A) -> R { + self.yield_impl::(val); + self.take_resumed_val() + } + + fn yield_impl(&mut self, val: A) { + let inst = unsafe { self.instance_mut() }; + let expecting: Box> = Box::new(PhantomData); + inst.state = State::Yielding { + val: YieldedVal::new(val), + expecting: expecting as Box, + }; + + HOST_CTX.with(|host_ctx| unsafe { Context::swap(&mut inst.ctx, &mut *host_ctx.get()) }); + } + + /// Take and return the value passed to + /// [`Instance::resume_with_val()`](../struct.Instance.html#method.resume_with_val), terminating + /// the instance if there is no value present, or the dynamic type check of the value fails. + fn take_resumed_val(&self) -> R { + self.try_take_resumed_val() + .unwrap_or_else(|| panic!(TerminationDetails::YieldTypeMismatch)) + } } /// Get an `Instance` from the `vmctx` pointer. @@ -202,43 +418,13 @@ pub unsafe fn instance_from_vmctx<'a>(vmctx: *mut lucet_vmctx) -> &'a mut Instan } impl Instance { - /// Helper function specific to Vmctx::get_embed_ctx. From the vmctx interface, - /// there is no way to recover if the expected embedder ctx is not set, so we terminate - /// the instance. - fn get_embed_ctx_or_term(&mut self) -> &T { - match self.embed_ctx.get::() { - Some(t) => t, - None => inst_terminate!(self, TerminationDetails::GetEmbedCtx), - } - } - - /// Helper function specific to Vmctx::get_embed_ctx_mut. See above. - fn get_embed_ctx_mut_or_term(&mut self) -> &mut T { - match self.embed_ctx.get_mut::() { - Some(t) => t, - None => inst_terminate!(self, TerminationDetails::GetEmbedCtx), - } - } - - /// Terminate the guest and swap back to the host context. + /// Terminate the guest and swap back to the host context without unwinding. /// - /// Only safe to call from within the guest context. - unsafe fn terminate(&mut self, details: TerminationDetails) -> ! { - inst_terminate!(self, details) + /// This is almost certainly not what you want to use to terminate from a hostcall; use panics + /// with `TerminationDetails` instead. + pub(crate) unsafe fn terminate(&mut self, details: TerminationDetails) -> ! { + self.state = State::Terminating { details }; + #[allow(unused_unsafe)] // The following unsafe will be incorrectly warned as unused + HOST_CTX.with(|host_ctx| unsafe { Context::set(&*host_ctx.get()) }) } } - -/// Unsafely get a `Vmctx` from an `InstanceHandle`, and fake a current instance TLS variable. -/// -/// This is provided for compatibility with the Terrarium memory management test suite, but should -/// absolutely not be used in newer code. -#[deprecated] -pub unsafe fn vmctx_from_mock_instance(inst: &InstanceHandle) -> Vmctx { - CURRENT_INSTANCE.with(|current_instance| { - let mut current_instance = current_instance.borrow_mut(); - *current_instance = Some(std::ptr::NonNull::new_unchecked( - inst.alloc().slot().start as *mut Instance, - )); - }); - Vmctx::from_raw(inst.alloc().slot().heap as *mut lucet_vmctx) -} diff --git a/lucet-runtime/lucet-runtime-macros/Cargo.toml b/lucet-runtime/lucet-runtime-macros/Cargo.toml new file mode 100644 index 000000000..3d2d73fdc --- /dev/null +++ b/lucet-runtime/lucet-runtime-macros/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "lucet-runtime-macros" +version = "0.5.0" +description = "Macros for the Lucet WebAssembly runtime" +homepage = "https://github.com/bytecodealliance/lucet" +repository = "https://github.com/bytecodealliance/lucet" +license = "Apache-2.0 WITH LLVM-exception" +categories = ["wasm"] +authors = ["Lucet team "] +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "1.0", features = ["full"] } +quote = "1.0" diff --git a/lucet-runtime/lucet-runtime-macros/src/lib.rs b/lucet-runtime/lucet-runtime-macros/src/lib.rs new file mode 100644 index 000000000..58134710d --- /dev/null +++ b/lucet-runtime/lucet-runtime-macros/src/lib.rs @@ -0,0 +1,120 @@ +extern crate proc_macro; +use proc_macro::TokenStream; +use quote::quote; +use syn::spanned::Spanned; + +/// This attribute generates a Lucet hostcall from a standalone Rust function that takes a `&mut +/// Vmctx` as its first argument. +/// +/// It is important to use this attribute for hostcalls, rather than exporting them +/// directly. Otherwise the behavior of instance termination and timeouts are +/// undefined. Additionally, the attribute makes the resulting function `unsafe extern "C"` +/// regardless of how the function is defined, as this ABI is required for all hostcalls. +/// +/// In most cases, you will want to also provide the `#[no_mangle]` attribute and `pub` visibility +/// in order for the hostcall to be exported from the final executable. +/// +/// ```ignore +/// #[lucet_hostcall] +/// #[no_mangle] +/// pub fn yield_5(vmctx: &mut Vmctx) { +/// vmctx.yield_val(5); +/// } +/// ``` +/// +/// Note that `lucet-runtime` must be a dependency of any crate where this attribute is used, and it +/// may not be renamed (this restriction may be lifted once [this +/// issue](https://github.com/rust-lang/rust/issues/54363) is resolved). +#[proc_macro_attribute] +pub fn lucet_hostcall(_attr: TokenStream, item: TokenStream) -> TokenStream { + // determine whether we need to import from `lucet_runtime_internals`; this is useful if we want + // to define a hostcall for a target (or tests, more concretely) that doesn't depend on + // `lucet-runtime` + let in_internals = std::env::var("CARGO_PKG_NAME").unwrap() == "lucet-runtime-internals"; + + let mut hostcall = syn::parse_macro_input!(item as syn::ItemFn); + let hostcall_ident = hostcall.sig.ident.clone(); + + // use the same attributes and visibility as the impl hostcall + let attrs = hostcall.attrs.clone(); + let vis = hostcall.vis.clone(); + + // remove #[no_mangle] from the attributes of the impl hostcall if it's there + hostcall + .attrs + .retain(|attr| !attr.path.is_ident("no_mangle")); + // make the impl hostcall private + hostcall.vis = syn::Visibility::Inherited; + + // modify the type signature of the exported raw hostcall based on the original signature + let mut raw_sig = hostcall.sig.clone(); + + // hostcalls are always unsafe + raw_sig.unsafety = Some(syn::Token![unsafe](raw_sig.span())); + + // hostcalls are always extern "C" + raw_sig.abi = Some(syn::parse_quote!(extern "C")); + + let vmctx_mod = if in_internals { + quote! { lucet_runtime_internals::vmctx } + } else { + quote! { lucet_runtime::vmctx } + }; + + // replace the first argument to the raw hostcall with the vmctx pointer + if let Some(arg0) = raw_sig.inputs.iter_mut().nth(0) { + let lucet_vmctx: syn::FnArg = syn::parse_quote!(vmctx_raw: *mut #vmctx_mod::lucet_vmctx); + *arg0 = lucet_vmctx; + } + + // the args after the first to provide to the hostcall impl + let impl_args = hostcall + .sig + .inputs + .iter() + .skip(1) + .map(|arg| match arg { + syn::FnArg::Receiver(_) => { + // this case is an error, but we produce some valid rust code anyway so that the + // compiler can produce a more meaningful error message at a later point + let s = syn::Token![self](arg.span()); + quote!(#s) + } + syn::FnArg::Typed(syn::PatType { pat, .. }) => quote!(#pat), + }) + .collect::>(); + + let termination_details = if in_internals { + quote! { lucet_runtime_internals::instance::TerminationDetails } + } else { + quote! { lucet_runtime::TerminationDetails } + }; + + let raw_hostcall = quote! { + #(#attrs)* + #vis + #raw_sig { + #[inline(always)] + #hostcall + + let mut vmctx = #vmctx_mod::Vmctx::from_raw(vmctx_raw); + #vmctx_mod::VmctxInternal::instance_mut(&mut vmctx).uninterruptable(|| { + let res = std::panic::catch_unwind(move || { + #hostcall_ident(&mut #vmctx_mod::Vmctx::from_raw(vmctx_raw), #(#impl_args),*) + }); + match res { + Ok(res) => res, + Err(e) => { + match e.downcast::<#termination_details>() { + Ok(details) => { + #vmctx_mod::Vmctx::from_raw(vmctx_raw).terminate_no_unwind(*details) + }, + Err(e) => std::panic::resume_unwind(e), + } + } + } + }) + } + }; + raw_hostcall.into() +} diff --git a/lucet-runtime/lucet-runtime-tests/Cargo.toml b/lucet-runtime/lucet-runtime-tests/Cargo.toml index 237489586..af197e24f 100644 --- a/lucet-runtime/lucet-runtime-tests/Cargo.toml +++ b/lucet-runtime/lucet-runtime-tests/Cargo.toml @@ -1,16 +1,27 @@ [package] name = "lucet-runtime-tests" -version = "0.1.0" -authors = ["Adam C. Foltzer "] +version = "0.5.0" +description = "Pure Rust runtime for Lucet WebAssembly toolchain (tests)" +homepage = "https://github.com/fastly/lucet" +repository = "https://github.com/fastly/lucet" +license = "Apache-2.0 WITH LLVM-exception" +categories = ["wasm"] +authors = ["Lucet team "] edition = "2018" +[lib] +# This crate only defines tests in macros, it does not contain any tests itself. This flag prevents +# `cargo test -p lucet-runtime-tests` from trying to link an executable with undefined symbols. +test = false + [dependencies] -failure = "0.1" +anyhow = "1" lazy_static = "1.1" tempfile = "3.0" -lucet-runtime-internals = { path = "../lucet-runtime-internals" } -lucet-wasi-sdk = { path = "../../lucet-wasi-sdk" } -lucetc = { path = "../../lucetc" } +lucet-module = { path = "../../lucet-module", version = "=0.5.0" } +lucet-runtime-internals = { path = "../lucet-runtime-internals", version = "=0.5.0" } +lucet-wasi-sdk = { path = "../../lucet-wasi-sdk", version = "=0.5.0" } +lucetc = { path = "../../lucetc", version = "=0.5.0" } [build-dependencies] cc = "1.0" diff --git a/lucet-runtime/lucet-runtime-tests/guests/entrypoint/bindings.json b/lucet-runtime/lucet-runtime-tests/guests/entrypoint/bindings.json index 39f1e1074..bab61d8f5 100644 --- a/lucet-runtime/lucet-runtime-tests/guests/entrypoint/bindings.json +++ b/lucet-runtime/lucet-runtime-tests/guests/entrypoint/bindings.json @@ -1,6 +1,7 @@ { "env": { "black_box": "black_box", - "callback_hostcall": "callback_hostcall" + "callback_hostcall": "callback_hostcall", + "add_4_hostcall": "add_4_hostcall" } } diff --git a/lucet-runtime/lucet-runtime-tests/guests/entrypoint/calculator.wat b/lucet-runtime/lucet-runtime-tests/guests/entrypoint/calculator.wat index 12f50724c..34db1679f 100644 --- a/lucet-runtime/lucet-runtime-tests/guests/entrypoint/calculator.wat +++ b/lucet-runtime/lucet-runtime-tests/guests/entrypoint/calculator.wat @@ -1,4 +1,5 @@ (module + (func $add_4 (export "add_4_reexport") (import "env" "add_4_hostcall") (param i64 i64 i64 i64) (result i64)) (memory 1) (func $add_2 (export "add_2") (param i64 i64) (result i64) (i64.add (get_local 0) (get_local 1)) diff --git a/lucet-runtime/lucet-runtime-tests/guests/host/bindings.json b/lucet-runtime/lucet-runtime-tests/guests/host/bindings.json index 26b5c8b44..383bcf316 100644 --- a/lucet-runtime/lucet-runtime-tests/guests/host/bindings.json +++ b/lucet-runtime/lucet-runtime-tests/guests/host/bindings.json @@ -1,6 +1,7 @@ { "env": { "hostcall_test_func_hello": "hostcall_test_func_hello", - "hostcall_test_func_hostcall_error": "hostcall_test_func_hostcall_error" + "hostcall_test_func_hostcall_error": "hostcall_test_func_hostcall_error", + "hostcall_test_func_hostcall_error_unwind": "hostcall_test_func_hostcall_error_unwind" } } diff --git a/lucet-runtime/lucet-runtime-tests/guests/host/hostcall_error_unwind.c b/lucet-runtime/lucet-runtime-tests/guests/host/hostcall_error_unwind.c new file mode 100644 index 000000000..6b48ce840 --- /dev/null +++ b/lucet-runtime/lucet-runtime-tests/guests/host/hostcall_error_unwind.c @@ -0,0 +1,9 @@ +#include + +extern void hostcall_test_func_hostcall_error_unwind(void); + +int main(void) +{ + hostcall_test_func_hostcall_error_unwind(); + return 0; +} diff --git a/lucet-runtime/lucet-runtime-tests/guests/timeout/bindings.json b/lucet-runtime/lucet-runtime-tests/guests/timeout/bindings.json new file mode 100644 index 000000000..5f45274a8 --- /dev/null +++ b/lucet-runtime/lucet-runtime-tests/guests/timeout/bindings.json @@ -0,0 +1,4 @@ +{ + "env": { + } +} diff --git a/lucet-runtime/lucet-runtime-tests/guests/timeout/fault.c b/lucet-runtime/lucet-runtime-tests/guests/timeout/fault.c new file mode 100644 index 000000000..948072dec --- /dev/null +++ b/lucet-runtime/lucet-runtime-tests/guests/timeout/fault.c @@ -0,0 +1,8 @@ +int main() { + char* invalid = (char*)0x12345678; + char c = *invalid; +} + +int onetwothree() { + return 123; +} diff --git a/lucet-runtime/lucet-runtime-tests/guests/timeout/inf_loop.c b/lucet-runtime/lucet-runtime-tests/guests/timeout/inf_loop.c new file mode 100644 index 000000000..3fca49c6d --- /dev/null +++ b/lucet-runtime/lucet-runtime-tests/guests/timeout/inf_loop.c @@ -0,0 +1,4 @@ +int main() { + while(1) {} + return 0; +} diff --git a/lucet-runtime/lucet-runtime-tests/src/build.rs b/lucet-runtime/lucet-runtime-tests/src/build.rs index 5de367884..6a22fc21d 100644 --- a/lucet-runtime/lucet-runtime-tests/src/build.rs +++ b/lucet-runtime/lucet-runtime-tests/src/build.rs @@ -1,7 +1,8 @@ -use failure::Error; +use anyhow::Error; +use lucet_module::bindings::Bindings; use lucet_runtime_internals::module::DlModule; -use lucet_wasi_sdk::Link; -use lucetc::{Bindings, Lucetc}; +use lucet_wasi_sdk::{CompileOpts, Link, LinkOpt, LinkOpts}; +use lucetc::{Lucetc, LucetcOpts}; use std::path::{Path, PathBuf}; use std::sync::Arc; use tempfile::TempDir; @@ -30,10 +31,10 @@ where let workdir = TempDir::new().expect("create working directory"); let wasm_build = Link::new(&[c_file]) - .cflag("-nostartfiles") - .ldflag("--no-entry") - .ldflag("--allow-undefined") - .ldflag("--export-all"); + .with_cflag("-nostartfiles") + .with_link_opt(LinkOpt::NoDefaultEntryPoint) + .with_link_opt(LinkOpt::AllowUndefinedAll) + .with_link_opt(LinkOpt::ExportAll); let wasm_file = workdir.path().join("out.wasm"); @@ -41,7 +42,7 @@ where let bindings = Bindings::from_file(bindings_file.as_ref())?; - let native_build = Lucetc::new(wasm_file)?.bindings(bindings)?; + let native_build = Lucetc::new(wasm_file).with_bindings(bindings); let so_file = workdir.path().join("out.so"); @@ -67,7 +68,7 @@ where let bindings = Bindings::from_file(&bindings_file)?; - let native_build = Lucetc::new(wasm_file)?.bindings(bindings)?; + let native_build = Lucetc::new(wasm_file).with_bindings(bindings); let so_file = workdir.path().join("out.so"); diff --git a/lucet-runtime/lucet-runtime-tests/src/entrypoint.rs b/lucet-runtime/lucet-runtime-tests/src/entrypoint.rs index 3a1820513..6334500d7 100644 --- a/lucet-runtime/lucet-runtime-tests/src/entrypoint.rs +++ b/lucet-runtime/lucet-runtime-tests/src/entrypoint.rs @@ -1,5 +1,6 @@ use crate::build::test_module_wasm; -use crate::helpers::MockModuleBuilder; +use crate::helpers::{MockExportBuilder, MockModuleBuilder}; +use lucet_module::{lucet_signature, FunctionPointer}; use lucet_runtime_internals::module::Module; use lucet_runtime_internals::vmctx::lucet_vmctx; use std::sync::Arc; @@ -13,6 +14,16 @@ pub fn mock_calculator_module() -> Arc { arg0 + arg1 } + extern "C" fn add_4_hostcall( + _vmctx: *mut lucet_vmctx, + arg0: u64, + arg1: u64, + arg2: u64, + arg3: u64, + ) -> u64 { + arg0 + arg1 + arg2 + arg3 + } + extern "C" fn add_10( _vmctx: *mut lucet_vmctx, arg0: u64, @@ -119,14 +130,61 @@ pub fn mock_calculator_module() -> Arc { } MockModuleBuilder::new() - .with_export_func(b"add_2", add_2 as *const extern "C" fn()) - .with_export_func(b"add_10", add_10 as *const extern "C" fn()) - .with_export_func(b"mul_2", mul_2 as *const extern "C" fn()) - .with_export_func(b"add_f32_2", add_f32_2 as *const extern "C" fn()) - .with_export_func(b"add_f64_2", add_f64_2 as *const extern "C" fn()) - .with_export_func(b"add_f32_10", add_f32_10 as *const extern "C" fn()) - .with_export_func(b"add_f64_10", add_f64_10 as *const extern "C" fn()) - .with_export_func(b"add_mixed_20", add_mixed_20 as *const extern "C" fn()) + .with_export_func( + MockExportBuilder::new("add_2", FunctionPointer::from_usize(add_2 as usize)) + .with_sig(lucet_signature!((I64, I64) -> I64)), + ) + .with_exported_import_func( + "add_4_reexport", + FunctionPointer::from_usize(add_4_hostcall as usize), + lucet_signature!((I64, I64, I64, I64) -> I64), + ) + .with_export_func( + MockExportBuilder::new("add_10", FunctionPointer::from_usize(add_10 as usize)) + .with_sig(lucet_signature!( + (I64, I64, I64, I64, I64, I64, I64, I64, I64, I64) -> I64)), + ) + .with_export_func( + MockExportBuilder::new("mul_2", FunctionPointer::from_usize(mul_2 as usize)) + .with_sig(lucet_signature!((I64, I64) -> I64)), + ) + .with_export_func( + MockExportBuilder::new("add_f32_2", FunctionPointer::from_usize(add_f32_2 as usize)) + .with_sig(lucet_signature!((F32, F32) -> F32)), + ) + .with_export_func( + MockExportBuilder::new("add_f64_2", FunctionPointer::from_usize(add_f64_2 as usize)) + .with_sig(lucet_signature!((F64, F64) -> F64)), + ) + .with_export_func( + MockExportBuilder::new( + "add_f32_10", + FunctionPointer::from_usize(add_f32_10 as usize), + ) + .with_sig(lucet_signature!( + (F32, F32, F32, F32, F32, F32, F32, F32, F32, F32) -> F32)), + ) + .with_export_func( + MockExportBuilder::new( + "add_f64_10", + FunctionPointer::from_usize(add_f64_10 as usize), + ) + .with_sig(lucet_signature!( + (F64, F64, F64, F64, F64, F64, F64, F64, F64, F64) -> F64)), + ) + .with_export_func( + MockExportBuilder::new( + "add_mixed_20", + FunctionPointer::from_usize(add_mixed_20 as usize), + ) + .with_sig(lucet_signature!( + ( + F64, I32, F32, F64, I32, F32, + F64, I32, F32, F64, I32, F32, + F64, I32, F32, F64, I64, F32, + F64, I64 + ) -> F64)), + ) .build() } @@ -135,13 +193,16 @@ macro_rules! entrypoint_tests { ( $TestRegion:path ) => { use libc::c_void; use lucet_runtime::vmctx::{lucet_vmctx, Vmctx}; - use lucet_runtime::{DlModule, Error, Limits, Module, Region, Val, WASM_PAGE_SIZE}; + use lucet_runtime::{ + lucet_hostcall, DlModule, Error, Limits, Module, Region, Val, WASM_PAGE_SIZE, + }; use std::sync::Arc; use $TestRegion as TestRegion; use $crate::entrypoint::{mock_calculator_module, wat_calculator_module}; + #[lucet_hostcall] #[no_mangle] - extern "C" fn black_box(_vmctx: *mut lucet_vmctx, _val: *mut c_void) {} + pub fn black_box(_vmctx: &mut Vmctx, _val: *mut c_void) {} #[test] fn mock_calc_add_2() { @@ -160,8 +221,9 @@ macro_rules! entrypoint_tests { .expect("instance can be created"); let retval = inst - .run(b"add_2", &[123u64.into(), 456u64.into()]) - .expect("instance runs"); + .run("add_2", &[123u64.into(), 456u64.into()]) + .expect("instance runs") + .unwrap_returned(); assert_eq!(u64::from(retval), 123u64 + 456); } @@ -190,7 +252,7 @@ macro_rules! entrypoint_tests { // order is correct. let retval = inst .run( - b"add_10", + "add_10", &[ 1u64.into(), 2u64.into(), @@ -204,7 +266,8 @@ macro_rules! entrypoint_tests { 10u64.into(), ], ) - .expect("instance runs"); + .expect("instance runs") + .unwrap_returned(); assert_eq!(u64::from(retval), 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10); } @@ -226,8 +289,9 @@ macro_rules! entrypoint_tests { .expect("instance can be created"); let retval = inst - .run(b"mul_2", &[123u64.into(), 456u64.into()]) - .expect("instance runs"); + .run("mul_2", &[123u64.into(), 456u64.into()]) + .expect("instance runs") + .unwrap_returned(); assert_eq!(u64::from(retval), 123 * 456); } @@ -244,14 +308,16 @@ macro_rules! entrypoint_tests { .expect("instance can be created"); let retval = inst - .run(b"add_2", &[111u64.into(), 222u64.into()]) - .expect("instance runs"); + .run("add_2", &[111u64.into(), 222u64.into()]) + .expect("instance runs") + .unwrap_returned(); assert_eq!(u64::from(retval), 111 + 222); let retval = inst - .run(b"mul_2", &[333u64.into(), 444u64.into()]) - .expect("instance runs"); + .run("mul_2", &[333u64.into(), 444u64.into()]) + .expect("instance runs") + .unwrap_returned(); assert_eq!(u64::from(retval), 333 * 444); } @@ -272,7 +338,7 @@ macro_rules! entrypoint_tests { .new_instance(module) .expect("instance can be created"); - match inst.run(b"invalid", &[123u64.into(), 456u64.into()]) { + match inst.run("invalid", &[123u64.into(), 456u64.into()]) { Err(Error::SymbolNotFound(sym)) => assert_eq!(sym, "invalid"), res => panic!("unexpected result: {:?}", res), } @@ -294,8 +360,9 @@ macro_rules! entrypoint_tests { .expect("instance can be created"); let retval = inst - .run(b"add_f32_2", &[(-6.9f32).into(), 4.2f32.into()]) - .expect("instance runs"); + .run("add_f32_2", &[(-6.9f32).into(), 4.2f32.into()]) + .expect("instance runs") + .unwrap_returned(); assert_eq!(f32::from(retval), -6.9 + 4.2); } @@ -315,8 +382,9 @@ macro_rules! entrypoint_tests { .expect("instance can be created"); let retval = inst - .run(b"add_f64_2", &[(-6.9f64).into(), 4.2f64.into()]) - .expect("instance runs"); + .run("add_f64_2", &[(-6.9f64).into(), 4.2f64.into()]) + .expect("instance runs") + .unwrap_returned(); assert_eq!(f64::from(retval), -6.9 + 4.2); } @@ -337,7 +405,7 @@ macro_rules! entrypoint_tests { let retval = inst .run( - b"add_f32_10", + "add_f32_10", &[ 0.1f32.into(), 0.2f32.into(), @@ -351,7 +419,8 @@ macro_rules! entrypoint_tests { 1.0f32.into(), ], ) - .expect("instance runs"); + .expect("instance runs") + .unwrap_returned(); assert_eq!( f32::from(retval), @@ -375,7 +444,7 @@ macro_rules! entrypoint_tests { let retval = inst .run( - b"add_f64_10", + "add_f64_10", &[ 0.1f64.into(), 0.2f64.into(), @@ -389,7 +458,8 @@ macro_rules! entrypoint_tests { 1.0f64.into(), ], ) - .expect("instance runs"); + .expect("instance runs") + .unwrap_returned(); assert_eq!( f64::from(retval), @@ -410,7 +480,7 @@ macro_rules! entrypoint_tests { let retval = inst .run( - b"add_mixed_20", + "add_mixed_20", &[ (-1.1f64).into(), 1u8.into(), @@ -434,7 +504,8 @@ macro_rules! entrypoint_tests { 19u64.into(), ], ) - .expect("instance runs"); + .expect("instance runs") + .unwrap_returned(); assert_eq!( f64::from(retval), @@ -461,6 +532,106 @@ macro_rules! entrypoint_tests { ); } + #[test] + fn mock_typecheck_entrypoint_wrong_args() { + typecheck_entrypoint_wrong_args(mock_calculator_module()) + } + + #[test] + fn wat_typecheck_entrypoint_wrong_args() { + typecheck_entrypoint_wrong_args(wat_calculator_module()) + } + + fn typecheck_entrypoint_wrong_args(module: Arc) { + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + match inst.run("add_2", &[123.0f64.into(), 456.0f64.into()]) { + Err(Error::InvalidArgument(err)) => { + assert_eq!(err, "entrypoint function signature mismatch") + } + res => panic!("unexpected result: {:?}", res), + } + } + + #[test] + fn mock_typecheck_entrypoint_too_few_args() { + typecheck_entrypoint_too_few_args(mock_calculator_module()) + } + + #[test] + fn wat_typecheck_entrypoint_too_few_args() { + typecheck_entrypoint_too_few_args(wat_calculator_module()) + } + + fn typecheck_entrypoint_too_few_args(module: Arc) { + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + match inst.run("add_2", &[123u64.into()]) { + Err(Error::InvalidArgument(err)) => assert_eq!( + err, + "entrypoint function signature mismatch (number of arguments is incorrect)" + ), + res => panic!("unexpected result: {:?}", res), + } + } + + #[test] + fn mock_typecheck_entrypoint_too_many_args() { + typecheck_entrypoint_too_many_args(mock_calculator_module()) + } + + #[test] + fn wat_typecheck_entrypoint_too_many_args() { + typecheck_entrypoint_too_many_args(wat_calculator_module()) + } + + fn typecheck_entrypoint_too_many_args(module: Arc) { + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + match inst.run("add_2", &[123u64.into(), 456u64.into(), 789u64.into()]) { + Err(Error::InvalidArgument(err)) => assert_eq!( + err, + "entrypoint function signature mismatch (number of arguments is incorrect)" + ), + res => panic!("unexpected result: {:?}", res), + } + } + + #[test] + fn mock_imported_entrypoint() { + imported_entrypoint(mock_calculator_module()) + } + + #[test] + fn wat_imported_entrypoint() { + imported_entrypoint(wat_calculator_module()) + } + + fn imported_entrypoint(module: Arc) { + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + let retval = inst + .run( + "add_4_reexport", + &[123u64.into(), 456u64.into(), 789u64.into(), 432u64.into()], + ) + .unwrap() + .unwrap_returned(); + assert_eq!(u64::from(retval), 1800); + } + use $crate::build::test_module_c; const TEST_REGION_INIT_VAL: libc::c_int = 123; const TEST_REGION_SIZE: libc::size_t = 4; @@ -487,7 +658,7 @@ macro_rules! entrypoint_tests { // This function will call `malloc` for the given size, then `memset` the entire region to the // init_as argument. The pointer to the allocated region gets stored in loc_outval. inst.run( - b"create_and_memset", + "create_and_memset", &[ // int init_as TEST_REGION_INIT_VAL.into(), @@ -538,7 +709,7 @@ macro_rules! entrypoint_tests { // Create a region and initialize it, just like above inst.run( - b"create_and_memset", + "create_and_memset", &[ // int init_as TEST_REGION_INIT_VAL.into(), @@ -568,7 +739,7 @@ macro_rules! entrypoint_tests { } // Then increment the first location in the region - inst.run(b"increment_ptr", &[Val::GuestPtr(loc_region_1)]) + inst.run("increment_ptr", &[Val::GuestPtr(loc_region_1)]) .expect("instance runs"); let heap = inst.heap(); @@ -613,7 +784,7 @@ macro_rules! entrypoint_tests { // same as above inst.run( - b"create_and_memset", + "create_and_memset", &[ // int init_as TEST_REGION_INIT_VAL.into(), @@ -633,7 +804,7 @@ macro_rules! entrypoint_tests { // Create a second region inst.run( - b"create_and_memset", + "create_and_memset", &[ // int init_as TEST_REGION2_INIT_VAL.into(), @@ -691,7 +862,7 @@ macro_rules! entrypoint_tests { // Run the setup routine inst.run( - b"ctype_setup", + "ctype_setup", &[ std::ptr::null::().into(), Val::GuestPtr(loc_ctxstar), @@ -707,18 +878,32 @@ macro_rules! entrypoint_tests { assert!(ctxstar > 0); // Run the body routine - inst.run(b"ctype_body", &[Val::GuestPtr(ctxstar)]) + inst.run("ctype_body", &[Val::GuestPtr(ctxstar)]) .expect("instance runs"); } + #[lucet_hostcall] #[no_mangle] - extern "C" fn callback_hostcall(vmctx: *mut lucet_vmctx, cb_idx: u32, x: u64) -> u64 { - let vmctx = unsafe { Vmctx::from_raw(vmctx) }; + pub unsafe extern "C" fn callback_hostcall(vmctx: &mut Vmctx, cb_idx: u32, x: u64) -> u64 { let func = vmctx .get_func_from_idx(0, cb_idx) .expect("can get function by index"); - let func = func as *const extern "C" fn(*mut lucet_vmctx, u64) -> u64; - unsafe { (*func)(vmctx.as_raw(), x) + 1 } + let func = std::mem::transmute:: u64>( + func.ptr.as_usize(), + ); + (func)(vmctx.as_raw(), x) + 1 + } + + #[lucet_hostcall] + #[no_mangle] + pub unsafe extern "C" fn add_4_hostcall( + vmctx: &mut Vmctx, + x: u64, + y: u64, + z: u64, + w: u64, + ) -> u64 { + x + y + z + w } #[test] @@ -732,8 +917,9 @@ macro_rules! entrypoint_tests { .expect("instance can be created"); let retval = inst - .run(b"callback_entrypoint", &[0u64.into()]) - .expect("instance runs"); + .run("callback_entrypoint", &[0u64.into()]) + .expect("instance runs") + .unwrap_returned(); assert_eq!(u64::from(retval), 3); } }; diff --git a/lucet-runtime/lucet-runtime-tests/src/globals.rs b/lucet-runtime/lucet-runtime-tests/src/globals.rs index c6da6d35b..4add0d788 100644 --- a/lucet-runtime/lucet-runtime-tests/src/globals.rs +++ b/lucet-runtime/lucet-runtime-tests/src/globals.rs @@ -1,12 +1,13 @@ #[macro_export] macro_rules! globals_tests { ( $TestRegion:path ) => { + use $crate::build::test_module_wasm; + use $crate::helpers::{MockExportBuilder, MockModuleBuilder}; + use lucet_module::{lucet_signature, FunctionPointer, GlobalValue}; use lucet_runtime::vmctx::{lucet_vmctx, Vmctx}; - use lucet_runtime::{Limits, Region}; - use lucet_runtime_internals::instance::InstanceInternal; + use lucet_runtime::{Error, Limits, Module, Region}; use std::sync::Arc; use $TestRegion as TestRegion; - use $crate::build::test_module_wasm; #[test] fn defined_globals() { @@ -17,7 +18,7 @@ macro_rules! globals_tests { .new_instance(module) .expect("instance can be created"); - inst.run(b"main", &[]).expect("instance runs"); + inst.run("main", &[]).expect("instance runs"); // Now the globals should be: // $x = 3 @@ -28,18 +29,151 @@ macro_rules! globals_tests { // [4] = 5 // [8] = 6 - let heap_u32 = unsafe { inst.alloc().heap_u32() }; + let heap_u32 = unsafe { inst.heap_u32() }; assert_eq!(heap_u32[0..=2], [4, 5, 6]); - inst.run(b"main", &[]).expect("instance runs"); + inst.run("main", &[]).expect("instance runs"); // now heap should be: // [0] = 3 // [4] = 2 // [8] = 6 - let heap_u32 = unsafe { inst.alloc().heap_u32() }; + let heap_u32 = unsafe { inst.heap_u32() }; assert_eq!(heap_u32[0..=2], [3, 2, 6]); } + + fn mock_import_module() -> Arc { + MockModuleBuilder::new() + .with_import(0, "something", "else") + .build() + } + + #[test] + fn reject_import() { + let module = mock_import_module(); + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + match region.new_instance(module) { + Ok(_) => panic!("instance creation should not succeed"), + Err(Error::Unsupported(_)) => (), + Err(e) => panic!("unexpected error: {}", e), + } + } + + fn mock_globals_module() -> Arc { + extern "C" { + fn lucet_vmctx_get_globals(vmctx: *mut lucet_vmctx) -> *mut GlobalValue; + } + + unsafe extern "C" fn get_global0(vmctx: *mut lucet_vmctx) -> i64 { + let globals = std::slice::from_raw_parts(lucet_vmctx_get_globals(vmctx), 2); + globals[0].i_64 + } + + unsafe extern "C" fn set_global0(vmctx: *mut lucet_vmctx, val: i64) { + let globals = std::slice::from_raw_parts_mut(lucet_vmctx_get_globals(vmctx), 2); + globals[0].i_64 = val; + } + + unsafe extern "C" fn get_global1(vmctx: *mut lucet_vmctx) -> i64 { + let globals = std::slice::from_raw_parts(lucet_vmctx_get_globals(vmctx), 2); + globals[1].i_64 + } + + MockModuleBuilder::new() + .with_global(0, -1) + .with_global(1, 420) + .with_export_func( + MockExportBuilder::new( + "get_global0", + FunctionPointer::from_usize(get_global0 as usize), + ) + .with_sig(lucet_signature!(() -> I64)), + ) + .with_export_func( + MockExportBuilder::new( + "set_global0", + FunctionPointer::from_usize(set_global0 as usize), + ) + .with_sig(lucet_signature!((I64) -> ())), + ) + .with_export_func( + MockExportBuilder::new( + "get_global1", + FunctionPointer::from_usize(get_global1 as usize), + ) + .with_sig(lucet_signature!(() -> I64)), + ) + .build() + } + + /* replace with use of instance public api to make sure defined globals are initialized + * correctly + */ + + #[test] + fn globals_initialized() { + let module = mock_globals_module(); + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let inst = region + .new_instance(module) + .expect("instance can be created"); + assert_eq!(unsafe { inst.globals()[0].i_64 }, -1); + assert_eq!(unsafe { inst.globals()[1].i_64 }, 420); + } + + #[test] + fn get_global0() { + let module = mock_globals_module(); + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + let retval = inst + .run("get_global0", &[]) + .expect("instance runs") + .unwrap_returned(); + assert_eq!(i64::from(retval), -1); + } + + #[test] + fn get_both_globals() { + let module = mock_globals_module(); + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + let retval = inst + .run("get_global0", &[]) + .expect("instance runs") + .unwrap_returned(); + assert_eq!(i64::from(retval), -1); + + let retval = inst + .run("get_global1", &[]) + .expect("instance runs") + .unwrap_returned(); + assert_eq!(i64::from(retval), 420); + } + + #[test] + fn mutate_global0() { + let module = mock_globals_module(); + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + inst.run("set_global0", &[666i64.into()]) + .expect("instance runs"); + + let retval = inst + .run("get_global0", &[]) + .expect("instance runs") + .unwrap_returned(); + assert_eq!(i64::from(retval), 666); + } }; } diff --git a/lucet-runtime/lucet-runtime-tests/src/guest_fault.rs b/lucet-runtime/lucet-runtime-tests/src/guest_fault.rs index fc8347fb9..6d443fdb9 100644 --- a/lucet-runtime/lucet-runtime-tests/src/guest_fault.rs +++ b/lucet-runtime/lucet-runtime-tests/src/guest_fault.rs @@ -1,6 +1,7 @@ -use crate::helpers::MockModuleBuilder; -use lucet_runtime_internals::module::{Module, TrapManifestRecord, TrapSite}; -use lucet_runtime_internals::vmctx::{lucet_vmctx, Vmctx}; +use crate::helpers::{MockExportBuilder, MockModuleBuilder}; +use lucet_module::{FunctionPointer, TrapCode, TrapSite}; +use lucet_runtime_internals::module::Module; +use lucet_runtime_internals::vmctx::lucet_vmctx; use std::sync::Arc; pub fn mock_traps_module() -> Arc { @@ -8,7 +9,7 @@ pub fn mock_traps_module() -> Arc { 123 } - extern "C" fn hostcall_main(vmctx: *mut lucet_vmctx) { + extern "C" fn hostcall_main(vmctx: *mut lucet_vmctx) -> () { extern "C" { // actually is defined in this file fn hostcall_test(vmctx: *mut lucet_vmctx); @@ -19,29 +20,33 @@ pub fn mock_traps_module() -> Arc { } } - extern "C" fn infinite_loop(_vmctx: *mut lucet_vmctx) { + extern "C" fn infinite_loop(_vmctx: *mut lucet_vmctx) -> () { loop {} } - extern "C" fn fatal(vmctx: *mut lucet_vmctx) { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - let heap_base = vmctx.heap_mut().as_mut_ptr(); + extern "C" fn fatal(vmctx: *mut lucet_vmctx) -> () { + extern "C" { + fn lucet_vmctx_get_heap(vmctx: *mut lucet_vmctx) -> *mut u8; + } - // Using the default limits, each instance as of this writing takes up 0x200026000 bytes - // worth of virtual address space. We want to access a point beyond all the instances, so - // that memory is unmapped. We assume no more than 16 instances are mapped - // concurrently. This may change as the library, test configuration, linker, phase of moon, - // etc change, but for now it works. unsafe { + let heap_base = lucet_vmctx_get_heap(vmctx); + + // Using the default limits, each instance as of this writing takes up 0x200026000 bytes + // worth of virtual address space. We want to access a point beyond all the instances, + // so that memory is unmapped. We assume no more than 16 instances are mapped + // concurrently. This may change as the library, test configuration, linker, phase of + // moon, etc change, but for now it works. *heap_base.offset(0x200026000 * 16) = 0; } } - extern "C" fn recoverable_fatal(_vmctx: *mut lucet_vmctx) { + extern "C" fn recoverable_fatal(_vmctx: *mut lucet_vmctx) -> () { use std::os::raw::c_char; extern "C" { fn guest_recoverable_get_ptr() -> *mut c_char; } + unsafe { *guest_recoverable_get_ptr() = '\0' as c_char; } @@ -70,44 +75,48 @@ pub fn mock_traps_module() -> Arc { static ILLEGAL_INSTR_TRAPS: &'static [TrapSite] = &[TrapSite { offset: 8, - trapcode: 4, /* BadSignature */ + code: TrapCode::BadSignature, }]; static OOB_TRAPS: &'static [TrapSite] = &[TrapSite { offset: 29, - trapcode: 1, /* HeapOutOfBounds */ + code: TrapCode::HeapOutOfBounds, }]; - let trap_manifest = &[ - TrapManifestRecord { - func_addr: guest_func_illegal_instr as *const extern "C" fn() as u64, - func_len: 11, - table_addr: ILLEGAL_INSTR_TRAPS.as_ptr() as u64, - table_len: 1, - }, - TrapManifestRecord { - func_addr: guest_func_oob as *const extern "C" fn() as u64, - func_len: 41, - table_addr: OOB_TRAPS.as_ptr() as u64, - table_len: 1, - }, - ]; - MockModuleBuilder::new() - .with_export_func(b"onetwothree", onetwothree as *const extern "C" fn()) + .with_export_func(MockExportBuilder::new( + "onetwothree", + FunctionPointer::from_usize(onetwothree as usize), + )) .with_export_func( - b"illegal_instr", - guest_func_illegal_instr as *const extern "C" fn(), + MockExportBuilder::new( + "illegal_instr", + FunctionPointer::from_usize(guest_func_illegal_instr as usize), + ) + .with_func_len(11) + .with_traps(ILLEGAL_INSTR_TRAPS), ) - .with_export_func(b"oob", guest_func_oob as *const extern "C" fn()) - .with_export_func(b"hostcall_main", hostcall_main as *const extern "C" fn()) - .with_export_func(b"infinite_loop", infinite_loop as *const extern "C" fn()) - .with_export_func(b"fatal", fatal as *const extern "C" fn()) .with_export_func( - b"recoverable_fatal", - recoverable_fatal as *const extern "C" fn(), + MockExportBuilder::new("oob", FunctionPointer::from_usize(guest_func_oob as usize)) + .with_func_len(41) + .with_traps(OOB_TRAPS), ) - .with_trap_manifest(trap_manifest) + .with_export_func(MockExportBuilder::new( + "hostcall_main", + FunctionPointer::from_usize(hostcall_main as usize), + )) + .with_export_func(MockExportBuilder::new( + "infinite_loop", + FunctionPointer::from_usize(infinite_loop as usize), + )) + .with_export_func(MockExportBuilder::new( + "fatal", + FunctionPointer::from_usize(fatal as usize), + )) + .with_export_func(MockExportBuilder::new( + "recoverable_fatal", + FunctionPointer::from_usize(recoverable_fatal as usize), + )) .build() } @@ -115,11 +124,11 @@ pub fn mock_traps_module() -> Arc { macro_rules! guest_fault_tests { ( $TestRegion:path ) => { use lazy_static::lazy_static; - use libc::{c_void, siginfo_t, SIGSEGV}; + use libc::{c_void, pthread_kill, pthread_self, siginfo_t, SIGALRM, SIGSEGV}; use lucet_runtime::vmctx::{lucet_vmctx, Vmctx}; use lucet_runtime::{ - DlModule, Error, FaultDetails, Instance, Limits, Region, SignalBehavior, TrapCode, - TrapCodeType, + lucet_hostcall, lucet_hostcall_terminate, DlModule, Error, FaultDetails, Instance, + Limits, Region, SignalBehavior, TerminationDetails, TrapCode, }; use nix::sys::mman::{mmap, MapFlags, ProtFlags}; use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal}; @@ -129,7 +138,9 @@ macro_rules! guest_fault_tests { use std::sync::{Arc, Mutex}; use $TestRegion as TestRegion; use $crate::guest_fault::mock_traps_module; - use $crate::helpers::{test_ex, test_nonex, MockModuleBuilder}; + use $crate::helpers::{ + test_ex, test_nonex, FunctionPointer, MockExportBuilder, MockModuleBuilder, + }; lazy_static! { static ref RECOVERABLE_PTR_LOCK: Mutex<()> = Mutex::new(()); @@ -143,7 +154,7 @@ macro_rules! guest_fault_tests { ptr::null_mut(), 4096, ProtFlags::PROT_NONE, - MapFlags::MAP_ANONYMOUS | MapFlags::MAP_PRIVATE, + MapFlags::MAP_ANON | MapFlags::MAP_PRIVATE, 0, 0, ) @@ -174,13 +185,17 @@ macro_rules! guest_fault_tests { static HOSTCALL_TEST_ERROR: &'static str = "hostcall_test threw an error!"; + #[lucet_hostcall] #[no_mangle] - unsafe extern "C" fn hostcall_test(vmctx: *mut lucet_vmctx) { - Vmctx::from_raw(vmctx).terminate(HOSTCALL_TEST_ERROR); + pub fn hostcall_test(_vmctx: &mut Vmctx) { + lucet_hostcall_terminate!(HOSTCALL_TEST_ERROR); } fn run_onetwothree(inst: &mut Instance) { - let retval = inst.run(b"onetwothree", &[]).expect("instance runs"); + let retval = inst + .run("onetwothree", &[]) + .expect("instance runs") + .unwrap_returned(); assert_eq!(libc::c_int::from(retval), 123); } @@ -194,9 +209,9 @@ macro_rules! guest_fault_tests { .new_instance(module) .expect("instance can be created"); - match inst.run(b"illegal_instr", &[]) { + match inst.run("illegal_instr", &[]) { Err(Error::RuntimeFault(details)) => { - assert_eq!(details.trapcode.ty, TrapCodeType::BadSignature); + assert_eq!(details.trapcode, Some(TrapCode::BadSignature)); } res => panic!("unexpected result: {:?}", res), } @@ -218,9 +233,9 @@ macro_rules! guest_fault_tests { .new_instance(module) .expect("instance can be created"); - match inst.run(b"oob", &[]) { + match inst.run("oob", &[]) { Err(Error::RuntimeFault(details)) => { - assert_eq!(details.trapcode.ty, TrapCodeType::HeapOutOfBounds); + assert_eq!(details.trapcode, Some(TrapCode::HeapOutOfBounds)); } res => panic!("unexpected result: {:?}", res), } @@ -242,7 +257,7 @@ macro_rules! guest_fault_tests { .new_instance(module) .expect("instance can be created"); - match inst.run(b"hostcall_main", &[]) { + match inst.run("hostcall_main", &[]) { Err(Error::RuntimeTerminated(term)) => { assert_eq!( *term @@ -267,7 +282,7 @@ macro_rules! guest_fault_tests { fn fatal_continue_signal_handler() { fn signal_handler_continue( _inst: &Instance, - _trapcode: &TrapCode, + _trapcode: &Option, signum: libc::c_int, _siginfo_ptr: *const siginfo_t, _ucontext_ptr: *const c_void, @@ -303,7 +318,7 @@ macro_rules! guest_fault_tests { // returns. This will initially cause a segfault. The signal handler will recover // from the segfault, map the page to read/write, and then return to the child // code. The child code will then succeed, and the instance will exit successfully. - inst.run(b"recoverable_fatal", &[]).expect("instance runs"); + inst.run("recoverable_fatal", &[]).expect("instance runs"); unsafe { recoverable_ptr_teardown() }; drop(lock); @@ -314,7 +329,7 @@ macro_rules! guest_fault_tests { fn fatal_terminate_signal_handler() { fn signal_handler_terminate( _inst: &Instance, - _trapcode: &TrapCode, + _trapcode: &Option, signum: libc::c_int, _siginfo_ptr: *const siginfo_t, _ucontext_ptr: *const c_void, @@ -348,7 +363,7 @@ macro_rules! guest_fault_tests { // returns. This will initially cause a segfault. The signal handler will recover // from the segfault, map the page to read/write, and then return to the child // code. The child code will then succeed, and the instance will exit successfully. - match inst.run(b"recoverable_fatal", &[]) { + match inst.run("recoverable_fatal", &[]) { Err(Error::RuntimeTerminated(_)) => (), res => panic!("unexpected result: {:?}", res), } @@ -403,9 +418,9 @@ macro_rules! guest_fault_tests { ); unsafe { sigaction(Signal::SIGSEGV, &sa).expect("sigaction succeeds") }; - match inst.run(b"illegal_instr", &[]) { + match inst.run("illegal_instr", &[]) { Err(Error::RuntimeFault(details)) => { - assert_eq!(details.trapcode.ty, TrapCodeType::BadSignature); + assert_eq!(details.trapcode, Some(TrapCode::BadSignature)); } res => panic!("unexpected result: {:?}", res), } @@ -437,57 +452,6 @@ macro_rules! guest_fault_tests { }) } - #[test] - fn alarm() { - extern "C" fn timeout_handler(signum: libc::c_int) { - assert!(signum == libc::SIGALRM); - std::process::exit(3); - } - test_ex(|| { - let module = mock_traps_module(); - let region = - TestRegion::create(1, &Limits::default()).expect("region can be created"); - let mut inst = region - .new_instance(module) - .expect("instance can be created"); - - inst.set_fatal_handler(fatal_handler_exit); - - match fork().expect("can fork") { - ForkResult::Child => { - // set up alarm handler and pend an alarm in 1 second - unsafe { - // child process doesn't have any contention for installed signal handlers, so - // we don't need to grab the lock exclusively here - sigaction( - Signal::SIGALRM, - &SigAction::new( - SigHandler::Handler(timeout_handler), - SaFlags::empty(), - SigSet::empty(), - ), - ) - .expect("sigaction succeeds"); - } - nix::unistd::alarm::set(1); - - // run guest code that loops forever - inst.run(b"infinite_loop", &[]).expect("instance runs"); - // show that we never get here - std::process::exit(1); - } - ForkResult::Parent { child } => { - match waitpid(Some(child), None).expect("can wait on child") { - WaitStatus::Exited(_, code) => { - assert_eq!(code, 3); - } - ws => panic!("unexpected wait status: {:?}", ws), - } - } - } - }) - } - #[test] fn sigsegv_handler_during_guest() { lazy_static! { @@ -505,7 +469,8 @@ macro_rules! guest_fault_tests { *HOST_SIGSEGV_TRIGGERED.lock().unwrap() = true; } - extern "C" fn sleepy_guest(_vmctx: *const lucet_vmctx) { + #[lucet_hostcall] + pub fn sleepy_guest(_vmctx: &mut Vmctx) { std::thread::sleep(std::time::Duration::from_millis(20)); } @@ -527,7 +492,10 @@ macro_rules! guest_fault_tests { // therefore testing that the host signal gets re-raised. let child = std::thread::spawn(|| { let module = MockModuleBuilder::new() - .with_export_func(b"sleepy_guest", sleepy_guest as *const extern "C" fn()) + .with_export_func(MockExportBuilder::new( + "sleepy_guest", + FunctionPointer::from_usize(sleepy_guest as usize), + )) .build(); let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); @@ -535,7 +503,7 @@ macro_rules! guest_fault_tests { .new_instance(module) .expect("instance can be created"); - inst.run(b"sleepy_guest", &[]).expect("instance runs"); + inst.run("sleepy_guest", &[]).expect("instance runs"); }); // now trigger a segfault in the middle of running the guest @@ -585,7 +553,7 @@ macro_rules! guest_fault_tests { .new_instance(module) .expect("instance can be created"); - inst.run(b"infinite_loop", &[]).expect("instance runs"); + inst.run("infinite_loop", &[]).expect("instance runs"); unreachable!() }); @@ -625,7 +593,7 @@ macro_rules! guest_fault_tests { // Child code should run code that will make an OOB beyond the guard page. This will // cause the entire process to abort before returning from `run` inst.set_fatal_handler(handler); - inst.run(b"fatal", &[]).expect("instance runs"); + inst.run("fatal", &[]).expect("instance runs"); // Show that we never get here: std::process::exit(1); } @@ -660,7 +628,7 @@ macro_rules! guest_fault_tests { // Child code should run code that will make an OOB beyond the guard page. This will // cause the entire process to abort before returning from `run` inst.set_fatal_handler(fatal_handler_exit); - inst.run(b"fatal", &[]).expect("instance runs"); + inst.run("fatal", &[]).expect("instance runs"); // Show that we never get here: std::process::exit(1); } @@ -676,6 +644,38 @@ macro_rules! guest_fault_tests { }) } + #[test] + fn sigaltstack_restores() { + use libc::*; + use std::mem::MaybeUninit; + + test_nonex(|| { + // any alternate stack present before a thread runs an instance should be restored + // after the instance returns + let mut beforestack = MaybeUninit::::uninit(); + let beforestack = unsafe { + sigaltstack(std::ptr::null(), beforestack.as_mut_ptr()); + beforestack.assume_init() + }; + + let module = mock_traps_module(); + let region = + TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + run_onetwothree(&mut inst); + + let mut afterstack = MaybeUninit::::uninit(); + let afterstack = unsafe { + sigaltstack(std::ptr::null(), afterstack.as_mut_ptr()); + afterstack.assume_init() + }; + + assert_eq!(beforestack.ss_sp, afterstack.ss_sp); + }) + } + // TODO: remove this once `nix` PR https://github.com/nix-rust/nix/pull/991 is merged pub unsafe fn mprotect( addr: *mut c_void, diff --git a/lucet-runtime/lucet-runtime-tests/src/guest_fault/traps.S b/lucet-runtime/lucet-runtime-tests/src/guest_fault/traps.S index 1cd602d78..f7df486ec 100644 --- a/lucet-runtime/lucet-runtime-tests/src/guest_fault/traps.S +++ b/lucet-runtime/lucet-runtime-tests/src/guest_fault/traps.S @@ -1,8 +1,13 @@ .text .globl guest_func_illegal_instr # -- Begin function guest_func_illegal_instr +#ifdef __ELF__ + .type guest_func_illegal_instr,@function +#else + .globl _guest_func_illegal_instr +#endif .p2align 4, 0x90 - .type guest_func_illegal_instr,@function guest_func_illegal_instr: # @guest_func_illegal_instr +_guest_func_illegal_instr: .cfi_startproc # %bb.0: pushq %rbp @@ -18,13 +23,20 @@ guest_func_illegal_instr: # @guest_func_illegal_instr .cfi_def_cfa %rsp, 8 retq .Lfunc_end0: - .size guest_func_illegal_instr, .Lfunc_end0-guest_func_illegal_instr +#ifdef ___ELF__ + .size guest_func_illegal_instr, .Lfunc_end0-guest_func_illegal_instr +#endif .cfi_endproc # -- End function .globl guest_func_oob # -- Begin function guest_func_oob - .p2align 4, 0x90 +#ifdef __ELF__ .type guest_func_oob,@function +#else + .globl _guest_func_oob +#endif + .p2align 4, 0x90 guest_func_oob: # @guest_func_oob +_guest_func_oob: .cfi_startproc # %bb.0: pushq %rbp @@ -35,7 +47,11 @@ guest_func_oob: # @guest_func_oob subq $16, %rsp movq %rdi, -8(%rbp) movq -8(%rbp), %rdi +#ifdef __ELF__ callq lucet_vmctx_get_heap@PLT +#else + callq _lucet_vmctx_get_heap +#endif movq %rax, -16(%rbp) movq -16(%rbp), %rax movb $0, 65537(%rax) @@ -44,8 +60,11 @@ guest_func_oob: # @guest_func_oob .cfi_def_cfa %rsp, 8 retq .Lfunc_end1: - .size guest_func_oob, .Lfunc_end1-guest_func_oob +#ifdef __ELF__ + .size guest_func_oob, .Lfunc_end1-guest_func_oob +#endif .cfi_endproc - # -- End function - .ident "clang version 7.0.1-svn348686-1~exp1~20181221231927.53 (branches/release_70)" + +#if defined(__linux__) && defined(__ELF__) .section ".note.GNU-stack","",@progbits +#endif diff --git a/lucet-runtime/lucet-runtime-tests/src/helpers.rs b/lucet-runtime/lucet-runtime-tests/src/helpers.rs index 967810c9c..a73ee55a6 100644 --- a/lucet-runtime/lucet-runtime-tests/src/helpers.rs +++ b/lucet-runtime/lucet-runtime-tests/src/helpers.rs @@ -1,7 +1,7 @@ // re-export types that should only be used for testing -pub use lucet_runtime_internals::module::{HeapSpec, MockModuleBuilder}; -#[allow(deprecated)] -pub use lucet_runtime_internals::vmctx::vmctx_from_mock_instance; +pub use lucet_runtime_internals::module::{ + FunctionPointer, HeapSpec, MockExportBuilder, MockModuleBuilder, +}; use lazy_static::lazy_static; use std::sync::RwLock; diff --git a/lucet-runtime/lucet-runtime-tests/src/host.rs b/lucet-runtime/lucet-runtime-tests/src/host.rs index 635c50d6f..dfb109307 100644 --- a/lucet-runtime/lucet-runtime-tests/src/host.rs +++ b/lucet-runtime/lucet-runtime-tests/src/host.rs @@ -1,12 +1,17 @@ #[macro_export] macro_rules! host_tests { ( $TestRegion:path ) => { + use lazy_static::lazy_static; use libc::c_void; use lucet_runtime::vmctx::{lucet_vmctx, Vmctx}; - use lucet_runtime::{DlModule, Error, Limits, Region, TrapCodeType}; - use std::sync::Arc; + use lucet_runtime::{ + lucet_hostcall, lucet_hostcall_terminate, DlModule, Error, Limits, Region, + TerminationDetails, TrapCode, + }; + use std::sync::{Arc, Mutex}; use $TestRegion as TestRegion; use $crate::build::test_module_c; + use $crate::helpers::{FunctionPointer, MockExportBuilder, MockModuleBuilder}; #[test] fn load_module() { let _module = test_module_c("host", "trivial.c").expect("build and load module"); @@ -18,30 +23,124 @@ macro_rules! host_tests { assert!(module.is_err()); } + const ERROR_MESSAGE: &'static str = "hostcall_test_func_hostcall_error"; + + lazy_static! { + static ref HOSTCALL_MUTEX: Mutex<()> = Mutex::new(()); + } + + #[lucet_hostcall] #[no_mangle] - extern "C" fn hostcall_test_func_hello( - vmctx: *mut lucet_vmctx, - hello_ptr: u32, - hello_len: u32, - ) { + pub fn hostcall_test_func_hostcall_error(_vmctx: &mut Vmctx) { + lucet_hostcall_terminate!(ERROR_MESSAGE); + } + + #[lucet_hostcall] + #[no_mangle] + pub fn hostcall_test_func_hello(vmctx: &mut Vmctx, hello_ptr: u32, hello_len: u32) { + let heap = vmctx.heap(); + let hello = heap.as_ptr() as usize + hello_ptr as usize; + if !vmctx.check_heap(hello as *const c_void, hello_len as usize) { + lucet_hostcall_terminate!("heap access"); + } + let hello = + unsafe { std::slice::from_raw_parts(hello as *const u8, hello_len as usize) }; + if hello.starts_with(b"hello") { + *vmctx.get_embed_ctx_mut::() = true; + } + } + + #[lucet_hostcall] + #[allow(unreachable_code)] + #[no_mangle] + pub fn hostcall_test_func_hostcall_error_unwind(_vmctx: &mut Vmctx) { + let _lock = HOSTCALL_MUTEX.lock().unwrap(); unsafe { - let mut vmctx = Vmctx::from_raw(vmctx); - let heap = vmctx.heap(); - let hello = heap.as_ptr() as usize + hello_ptr as usize; - if !vmctx.check_heap(hello as *const c_void, hello_len as usize) { - vmctx.terminate("heap access"); - } - let hello = std::slice::from_raw_parts(hello as *const u8, hello_len as usize); - if hello.starts_with(b"hello") { - *vmctx.get_embed_ctx_mut::() = true; - } + lucet_hostcall_terminate!(ERROR_MESSAGE); } + drop(_lock); } - const ERROR_MESSAGE: &'static str = "hostcall_test_func_hostcall_error"; + #[lucet_hostcall] + #[no_mangle] + pub fn hostcall_bad_borrow(vmctx: &mut Vmctx) -> bool { + let heap = vmctx.heap(); + let mut other_heap = vmctx.heap_mut(); + heap[0] == other_heap[0] + } + + #[lucet_hostcall] + #[no_mangle] + pub fn hostcall_missing_embed_ctx(vmctx: &mut Vmctx) -> bool { + struct S { + x: bool, + } + let ctx = vmctx.get_embed_ctx::(); + ctx.x + } + + #[lucet_hostcall] + #[no_mangle] + pub fn hostcall_multiple_vmctx(vmctx: &mut Vmctx) -> bool { + let mut vmctx1 = unsafe { Vmctx::from_raw(vmctx.as_raw()) }; + vmctx1.heap_mut()[0] = 0xAF; + drop(vmctx1); + + let mut vmctx2 = unsafe { Vmctx::from_raw(vmctx.as_raw()) }; + let res = vmctx2.heap()[0] == 0xAF; + drop(vmctx2); + + res + } + + #[lucet_hostcall] + #[no_mangle] + pub fn hostcall_yields(vmctx: &mut Vmctx) { + vmctx.yield_(); + } + + #[lucet_hostcall] #[no_mangle] - extern "C" fn hostcall_test_func_hostcall_error(vmctx: *mut lucet_vmctx) { - unsafe { Vmctx::from_raw(vmctx).terminate(ERROR_MESSAGE) } + pub fn hostcall_yield_expects_5(vmctx: &mut Vmctx) -> u64 { + vmctx.yield_expecting_val() + } + + #[lucet_hostcall] + #[no_mangle] + pub fn hostcall_yields_5(vmctx: &mut Vmctx) { + vmctx.yield_val(5u64); + } + + #[lucet_hostcall] + #[no_mangle] + pub fn hostcall_yield_facts(vmctx: &mut Vmctx, n: u64) -> u64 { + fn fact(vmctx: &mut Vmctx, n: u64) -> u64 { + let result = if n <= 1 { 1 } else { n * fact(vmctx, n - 1) }; + vmctx.yield_val(result); + result + } + fact(vmctx, n) + } + + pub enum CoopFactsK { + Mult(u64, u64), + Result(u64), + } + + #[lucet_hostcall] + #[no_mangle] + pub fn hostcall_coop_facts(vmctx: &mut Vmctx, n: u64) -> u64 { + fn fact(vmctx: &mut Vmctx, n: u64) -> u64 { + let result = if n <= 1 { + 1 + } else { + let n_rec = fact(vmctx, n - 1); + vmctx.yield_val_expecting_val(CoopFactsK::Mult(n, n_rec)) + }; + vmctx.yield_val(CoopFactsK::Result(result)); + result + } + fact(vmctx, n) } #[test] @@ -60,7 +159,8 @@ macro_rules! host_tests { let mut inst = region .new_instance(module) .expect("instance can be created"); - inst.run(b"main", &[]).expect("instance runs"); + inst.run("main", &[0u32.into(), 0i32.into()]) + .expect("instance runs"); } #[test] @@ -74,9 +174,10 @@ macro_rules! host_tests { .build() .expect("instance can be created"); - inst.run(b"main", &[]).expect("instance runs"); + inst.run("main", &[0u32.into(), 0i32.into()]) + .expect("instance runs"); - assert!(inst.get_embed_ctx::().unwrap()); + assert!(*inst.get_embed_ctx::().unwrap().unwrap()); } #[test] @@ -87,7 +188,7 @@ macro_rules! host_tests { .new_instance(module) .expect("instance can be created"); - match inst.run(b"main", &[]) { + match inst.run("main", &[0u32.into(), 0i32.into()]) { Err(Error::RuntimeTerminated(term)) => { assert_eq!( *term @@ -102,6 +203,32 @@ macro_rules! host_tests { } } + #[test] + fn run_hostcall_error_unwind() { + let module = + test_module_c("host", "hostcall_error_unwind.c").expect("build and load module"); + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + match inst.run("main", &[0u32.into(), 0u32.into()]) { + Err(Error::RuntimeTerminated(term)) => { + assert_eq!( + *term + .provided_details() + .expect("user provided termination reason") + .downcast_ref::<&'static str>() + .expect("error was static str"), + ERROR_MESSAGE + ); + } + res => panic!("unexpected result: {:?}", res), + } + + assert!(HOSTCALL_MUTEX.is_poisoned()); + } + #[test] fn run_fpe() { let module = test_module_c("host", "fpe.c").expect("build and load module"); @@ -110,14 +237,403 @@ macro_rules! host_tests { .new_instance(module) .expect("instance can be created"); - match inst.run(b"trigger_div_error", &[0u64.into()]) { + match inst.run("trigger_div_error", &[0u32.into()]) { Err(Error::RuntimeFault(details)) => { - assert_eq!(details.trapcode.ty, TrapCodeType::IntegerDivByZero); + assert_eq!(details.trapcode, Some(TrapCode::IntegerDivByZero)); + } + res => { + panic!("unexpected result: {:?}", res); + } + } + } + + #[test] + fn run_hostcall_bad_borrow() { + extern "C" { + fn hostcall_bad_borrow(vmctx: *mut lucet_vmctx) -> bool; + } + + unsafe extern "C" fn f(vmctx: *mut lucet_vmctx) { + hostcall_bad_borrow(vmctx); + } + + let module = MockModuleBuilder::new() + .with_export_func(MockExportBuilder::new( + "f", + FunctionPointer::from_usize(f as usize), + )) + .build(); + + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + match inst.run("f", &[]) { + Err(Error::RuntimeTerminated(details)) => { + assert_eq!(details, TerminationDetails::BorrowError("heap_mut")); + } + res => { + panic!("unexpected result: {:?}", res); + } + } + } + + #[test] + fn run_hostcall_missing_embed_ctx() { + extern "C" { + fn hostcall_missing_embed_ctx(vmctx: *mut lucet_vmctx) -> bool; + } + + unsafe extern "C" fn f(vmctx: *mut lucet_vmctx) { + hostcall_missing_embed_ctx(vmctx); + } + + let module = MockModuleBuilder::new() + .with_export_func(MockExportBuilder::new( + "f", + FunctionPointer::from_usize(f as usize), + )) + .build(); + + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + match inst.run("f", &[]) { + Err(Error::RuntimeTerminated(details)) => { + assert_eq!(details, TerminationDetails::CtxNotFound); } res => { panic!("unexpected result: {:?}", res); } } } + + #[test] + fn run_hostcall_multiple_vmctx() { + extern "C" { + fn hostcall_multiple_vmctx(vmctx: *mut lucet_vmctx) -> bool; + } + + unsafe extern "C" fn f(vmctx: *mut lucet_vmctx) { + hostcall_multiple_vmctx(vmctx); + } + + let module = MockModuleBuilder::new() + .with_export_func(MockExportBuilder::new( + "f", + FunctionPointer::from_usize(f as usize), + )) + .build(); + + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + let retval = inst + .run("f", &[]) + .expect("instance runs") + .expect_returned("instance returned"); + assert_eq!(bool::from(retval), true); + } + + #[test] + fn run_hostcall_yields_5() { + extern "C" { + fn hostcall_yields_5(vmctx: *mut lucet_vmctx); + } + + unsafe extern "C" fn f(vmctx: *mut lucet_vmctx) { + hostcall_yields_5(vmctx); + } + + let module = MockModuleBuilder::new() + .with_export_func(MockExportBuilder::new( + "f", + FunctionPointer::from_usize(f as usize), + )) + .build(); + + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + assert_eq!( + *inst + .run("f", &[]) + .unwrap() + .unwrap_yielded() + .downcast::() + .unwrap(), + 5u64 + ); + } + + #[test] + fn run_hostcall_yield_expects_5() { + extern "C" { + fn hostcall_yield_expects_5(vmctx: *mut lucet_vmctx) -> u64; + } + + unsafe extern "C" fn f(vmctx: *mut lucet_vmctx) -> u64 { + hostcall_yield_expects_5(vmctx) + } + + let module = MockModuleBuilder::new() + .with_export_func(MockExportBuilder::new( + "f", + FunctionPointer::from_usize(f as usize), + )) + .build(); + + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + assert!(inst.run("f", &[]).unwrap().unwrap_yielded().is_none()); + + let retval = inst + .resume_with_val(5u64) + .expect("instance resumes") + .unwrap_returned(); + assert_eq!(u64::from(retval), 5u64); + } + + #[test] + fn yield_factorials() { + extern "C" { + fn hostcall_yield_facts(vmctx: *mut lucet_vmctx, n: u64) -> u64; + } + + unsafe extern "C" fn f(vmctx: *mut lucet_vmctx) -> u64 { + hostcall_yield_facts(vmctx, 5) + } + + let module = MockModuleBuilder::new() + .with_export_func(MockExportBuilder::new( + "f", + FunctionPointer::from_usize(f as usize), + )) + .build(); + + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + let mut facts = vec![]; + + let mut res = inst.run("f", &[]).unwrap(); + + while res.is_yielded() { + facts.push(*res.unwrap_yielded().downcast::().unwrap()); + res = inst.resume().unwrap(); + } + + assert_eq!(facts.as_slice(), &[1, 2, 6, 24, 120]); + assert_eq!(u64::from(res.unwrap_returned()), 120u64); + } + + #[test] + fn coop_factorials() { + extern "C" { + fn hostcall_coop_facts(vmctx: *mut lucet_vmctx, n: u64) -> u64; + } + + unsafe extern "C" fn f(vmctx: *mut lucet_vmctx) -> u64 { + hostcall_coop_facts(vmctx, 5) + } + + let module = MockModuleBuilder::new() + .with_export_func(MockExportBuilder::new( + "f", + FunctionPointer::from_usize(f as usize), + )) + .build(); + + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + let mut facts = vec![]; + + let mut res = inst.run("f", &[]).unwrap(); + + while let Ok(val) = res.yielded_ref() { + if let Some(k) = val.downcast_ref::() { + match k { + CoopFactsK::Mult(n, n_rec) => { + // guest wants us to multiply for it + res = inst.resume_with_val(n * n_rec).unwrap(); + } + CoopFactsK::Result(n) => { + // guest is returning an answer + facts.push(*n); + res = inst.resume().unwrap(); + } + } + } else { + panic!("didn't yield with expected type"); + } + } + + assert_eq!(facts.as_slice(), &[1, 2, 6, 24, 120]); + assert_eq!(u64::from(res.unwrap_returned()), 120u64); + } + + #[test] + fn resume_unexpected() { + extern "C" { + fn hostcall_yields_5(vmctx: *mut lucet_vmctx); + } + + unsafe extern "C" fn f(vmctx: *mut lucet_vmctx) { + hostcall_yields_5(vmctx); + } + + let module = MockModuleBuilder::new() + .with_export_func(MockExportBuilder::new( + "f", + FunctionPointer::from_usize(f as usize), + )) + .build(); + + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + assert_eq!( + *inst + .run("f", &[]) + .unwrap() + .unwrap_yielded() + .downcast::() + .unwrap(), + 5u64 + ); + + match inst.resume_with_val(5u64) { + Err(Error::InvalidArgument(_)) => (), + Err(e) => panic!("unexpected error: {}", e), + Ok(_) => panic!("unexpected success"), + } + } + + #[test] + fn missing_resume_val() { + extern "C" { + fn hostcall_yield_expects_5(vmctx: *mut lucet_vmctx) -> u64; + } + + unsafe extern "C" fn f(vmctx: *mut lucet_vmctx) -> u64 { + hostcall_yield_expects_5(vmctx) + } + + let module = MockModuleBuilder::new() + .with_export_func(MockExportBuilder::new( + "f", + FunctionPointer::from_usize(f as usize), + )) + .build(); + + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + assert!(inst.run("f", &[]).unwrap().unwrap_yielded().is_none()); + + match inst.resume() { + Err(Error::InvalidArgument(_)) => (), + Err(e) => panic!("unexpected error: {}", e), + Ok(_) => panic!("unexpected success"), + } + } + + #[test] + fn resume_wrong_type() { + extern "C" { + fn hostcall_yield_expects_5(vmctx: *mut lucet_vmctx) -> u64; + } + + unsafe extern "C" fn f(vmctx: *mut lucet_vmctx) -> u64 { + hostcall_yield_expects_5(vmctx) + } + + let module = MockModuleBuilder::new() + .with_export_func(MockExportBuilder::new( + "f", + FunctionPointer::from_usize(f as usize), + )) + .build(); + + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + assert!(inst.run("f", &[]).unwrap().unwrap_yielded().is_none()); + + match inst.resume_with_val(true) { + Err(Error::InvalidArgument(_)) => (), + Err(e) => panic!("unexpected error: {}", e), + Ok(_) => panic!("unexpected success"), + } + } + + /// This test shows that we can send an `InstanceHandle` to another thread while a guest is + /// yielded, and it resumes successfully. + #[test] + fn switch_threads_resume() { + extern "C" { + fn hostcall_yields_5(vmctx: *mut lucet_vmctx); + } + + unsafe extern "C" fn f(vmctx: *mut lucet_vmctx) -> u64 { + hostcall_yields_5(vmctx); + 42 + } + + let module = MockModuleBuilder::new() + .with_export_func(MockExportBuilder::new( + "f", + FunctionPointer::from_usize(f as usize), + )) + .build(); + + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + // make sure we yield with 5 on the original thread + assert_eq!( + *inst + .run("f", &[]) + .unwrap() + .unwrap_yielded() + .downcast::() + .unwrap(), + 5u64 + ); + + let res = std::thread::spawn(move || { + // but then move the instance to another thread and resume it from there + inst.resume() + .expect("instance resumes") + .returned() + .expect("returns 42") + }) + .join() + .unwrap(); + assert_eq!(u64::from(res), 42u64); + } }; } diff --git a/lucet-runtime/lucet-runtime-tests/src/lib.rs b/lucet-runtime/lucet-runtime-tests/src/lib.rs index 9d2d11feb..a9c3da653 100644 --- a/lucet-runtime/lucet-runtime-tests/src/lib.rs +++ b/lucet-runtime/lucet-runtime-tests/src/lib.rs @@ -8,3 +8,4 @@ pub mod memory; pub mod stack; pub mod start; pub mod strcmp; +pub mod timeout; diff --git a/lucet-runtime/lucet-runtime-tests/src/memory.rs b/lucet-runtime/lucet-runtime-tests/src/memory.rs index ad3cc458a..bf2a964f6 100644 --- a/lucet-runtime/lucet-runtime-tests/src/memory.rs +++ b/lucet-runtime/lucet-runtime-tests/src/memory.rs @@ -16,7 +16,10 @@ macro_rules! memory_tests { .new_instance(module) .expect("instance can be created"); - let retval = inst.run(b"main", &[]).expect("instance runs"); + let retval = inst + .run("main", &[]) + .expect("instance runs") + .unwrap_returned(); assert_eq!(u32::from(retval), 4); } @@ -29,7 +32,7 @@ macro_rules! memory_tests { .new_instance(module) .expect("instance can be created"); - inst.run(b"main", &[]).expect("instance runs"); + inst.run("main", &[]).expect("instance runs"); let heap = inst.heap_u32(); // guest puts the result of the grow_memory(1) call in heap[0]; based on the current settings, diff --git a/lucet-runtime/lucet-runtime-tests/src/stack.rs b/lucet-runtime/lucet-runtime-tests/src/stack.rs index 69beee8e2..b9062476d 100644 --- a/lucet-runtime/lucet-runtime-tests/src/stack.rs +++ b/lucet-runtime/lucet-runtime-tests/src/stack.rs @@ -1,20 +1,13 @@ -use failure::Error; +use anyhow::Error; use lucet_runtime_internals::module::DlModule; use lucetc::Lucetc; -use std::fs::File; -use std::io::prelude::*; use std::sync::Arc; use tempfile::TempDir; pub fn stack_testcase(num_locals: usize) -> Result, Error> { - let workdir = TempDir::new().expect("create working directory"); - - let wasm_path = workdir.path().join("out.wasm"); + let native_build = Lucetc::try_from_bytes(generate_test_wat(num_locals))?; - let mut wasm_file = File::create(&wasm_path)?; - wasm_file.write_all(generate_test_wat(num_locals).as_bytes())?; - - let native_build = Lucetc::new(wasm_path)?; + let workdir = TempDir::new().expect("create working directory"); let so_file = workdir.path().join("out.so"); @@ -42,9 +35,10 @@ fn generate_test_wat(num_locals: usize) -> String { // Use each local for the first time: for i in 1..num_locals { module.push_str(&format!( - "(set_local {} (i32.add (get_local {}) (get_local {})))\n", + "(set_local {} (i32.add (get_local {}) (i32.xor (get_local {}) (i32.const {}))))\n", i, i - 1, + i, i )); } @@ -52,9 +46,10 @@ fn generate_test_wat(num_locals: usize) -> String { // Use each local for a second time, so they get pushed to the stack between uses: for i in 2..(num_locals - 1) { module.push_str(&format!( - "(set_local {} (i32.add (get_local {}) (get_local {})))\n", + "(set_local {} (i32.add (get_local {}) (i32.and (get_local {}) (i32.const {}))))\n", i, i - 1, + i, i )); } @@ -84,7 +79,7 @@ fn generate_test_wat(num_locals: usize) -> String { macro_rules! stack_tests { ( $TestRegion:path ) => { use lucet_runtime::{ - DlModule, Error, InstanceHandle, Limits, Region, TrapCodeType, UntypedRetVal, Val, + DlModule, Error, InstanceHandle, Limits, Region, TrapCode, UntypedRetVal, Val, }; use std::sync::Arc; use $TestRegion as TestRegion; @@ -96,7 +91,8 @@ macro_rules! stack_tests { .new_instance(module) .expect("instance can be created"); - inst.run(b"localpalooza", &[recursion_depth.into()]) + inst.run("localpalooza", &[recursion_depth.into()]) + .and_then(|rr| rr.returned()) } fn expect_ok(module: Arc, recursion_depth: i32) { @@ -108,7 +104,7 @@ macro_rules! stack_tests { Err(Error::RuntimeFault(details)) => { // We should get a nonfatal trap due to the stack overflow. assert_eq!(details.fatal, false); - assert_eq!(details.trapcode.ty, TrapCodeType::StackOverflow); + assert_eq!(details.trapcode, Some(TrapCode::StackOverflow)); if probestack { // Make sure we overflowed in the stack probe as expected // @@ -145,14 +141,22 @@ macro_rules! stack_tests { } #[test] - fn expect_ok_locals64_480() { - expect_ok(stack_testcase(64).expect("generate stack_testcase 64"), 480); + fn expect_ok_locals64_481() { + // We use 64 local variables, but cranelift optimizes many of them into registers + // rather than stack space. Four of those are callee-saved, for n > 18 local + // variables, we actually use n + 4 stack spaces. So we use 64 spaces at 64 - 4 = 60 + // local variables. + expect_ok( + stack_testcase(64 - 4).expect("generate stack_testcase 64"), + 480, + ); } #[test] fn expect_stack_overflow_locals64_481() { expect_stack_overflow( - stack_testcase(64).expect("generate stack_testcase 64"), + // Same note as `expect_ok_locals64_481` + stack_testcase(64 - 4).expect("generate stack_testcase 64"), 481, true, ); diff --git a/lucet-runtime/lucet-runtime-tests/src/start.rs b/lucet-runtime/lucet-runtime-tests/src/start.rs index 91418163b..2daab564b 100644 --- a/lucet-runtime/lucet-runtime-tests/src/start.rs +++ b/lucet-runtime/lucet-runtime-tests/src/start.rs @@ -15,7 +15,7 @@ macro_rules! start_tests { .new_instance(module) .expect("instance can be created"); - inst.run(b"main", &[]).expect("instance runs"); + inst.run("main", &[]).expect("instance runs"); // Now the globals should be: // $flossie = 17 @@ -35,7 +35,7 @@ macro_rules! start_tests { .new_instance(module) .expect("instance can be created"); - inst.run(b"main", &[]).expect("instance runs"); + inst.run("main", &[]).expect("instance runs"); // Now the globals should be: // $flossie = 17 @@ -55,7 +55,7 @@ macro_rules! start_tests { .new_instance(module) .expect("instance can be created"); - inst.run(b"main", &[]).expect("instance runs"); + inst.run("main", &[]).expect("instance runs"); // Now the globals should be: // $flossie = 17 diff --git a/lucet-runtime/lucet-runtime-tests/src/strcmp.rs b/lucet-runtime/lucet-runtime-tests/src/strcmp.rs index 2c6b9e260..d4d27a713 100644 --- a/lucet-runtime/lucet-runtime-tests/src/strcmp.rs +++ b/lucet-runtime/lucet-runtime-tests/src/strcmp.rs @@ -1,18 +1,21 @@ #[macro_export] macro_rules! strcmp_tests { ( $TestRegion:path ) => { - use libc::{c_char, c_int, c_void, strcmp, uint64_t}; - use lucet_runtime::vmctx::lucet_vmctx; - use lucet_runtime::{Error, Limits, Region, Val, WASM_PAGE_SIZE}; + use libc::{c_char, c_int, c_void, strcmp}; + use lucet_runtime::vmctx::{lucet_vmctx, Vmctx}; + use lucet_runtime::{lucet_hostcall, Error, Limits, Region, Val, WASM_PAGE_SIZE}; use std::ffi::CString; use std::sync::Arc; use $TestRegion as TestRegion; use $crate::build::test_module_c; + #[lucet_hostcall] #[no_mangle] - unsafe extern "C" fn hostcall_host_fault(_vmctx: *const lucet_vmctx) { + pub fn hostcall_host_fault(_vmctx: &mut Vmctx) { let oob = (-1isize) as *mut c_char; - *oob = 'x' as c_char; + unsafe { + *oob = 'x' as c_char; + } } fn strcmp_compare(s1: &str, s2: &str) { @@ -23,8 +26,7 @@ macro_rules! strcmp_tests { .expect("s2 is a valid CString") .into_bytes_with_nul(); - let res_size = std::mem::size_of::(); - assert!(res_size + s1.len() + s2.len() < WASM_PAGE_SIZE as usize); + assert!(s1.len() + s2.len() < WASM_PAGE_SIZE as usize); let module = test_module_c("strcmp", "guest.c").expect("compile module"); let region = TestRegion::create(10, &Limits::default()).expect("region can be created"); @@ -35,22 +37,18 @@ macro_rules! strcmp_tests { let newpage_start = inst.grow_memory(1).expect("grow_memory succeeds"); let heap = inst.heap_mut(); - let res_ptr = (newpage_start * WASM_PAGE_SIZE) as usize; - let s1_ptr = res_ptr + res_size; + let s1_ptr = (newpage_start * WASM_PAGE_SIZE) as usize; let s2_ptr = s1_ptr + s1.len(); heap[s1_ptr..s2_ptr].copy_from_slice(&s1); heap[s2_ptr..s2_ptr + s2.len()].copy_from_slice(&s2); let res = c_int::from( inst.run( - b"run_strcmp", - &[ - Val::GuestPtr(s1_ptr as u32), - Val::GuestPtr(s2_ptr as u32), - Val::GuestPtr(res_ptr as u32), - ], + "run_strcmp", + &[Val::GuestPtr(s1_ptr as u32), Val::GuestPtr(s2_ptr as u32)], ) - .expect("instance runs"), + .expect("instance runs") + .unwrap_returned(), ); let host_strcmp_res = @@ -86,7 +84,7 @@ macro_rules! strcmp_tests { .new_instance(module) .expect("instance can be created"); - match inst.run(b"wasm_fault", &[]) { + match inst.run("wasm_fault", &[]) { Err(Error::RuntimeFault { .. }) => (), res => panic!("unexpected result: {:?}", res), } diff --git a/lucet-runtime/lucet-runtime-tests/src/timeout.rs b/lucet-runtime/lucet-runtime-tests/src/timeout.rs new file mode 100644 index 000000000..8fb3e67b7 --- /dev/null +++ b/lucet-runtime/lucet-runtime-tests/src/timeout.rs @@ -0,0 +1,320 @@ +use crate::helpers::{MockExportBuilder, MockModuleBuilder}; +use lucet_module::FunctionPointer; +use lucet_runtime_internals::module::Module; +use lucet_runtime_internals::vmctx::lucet_vmctx; +use std::sync::Arc; + +pub fn mock_timeout_module() -> Arc { + extern "C" fn onetwothree(_vmctx: *mut lucet_vmctx) -> std::os::raw::c_int { + 123 + } + + extern "C" fn infinite_loop(_vmctx: *mut lucet_vmctx) -> () { + loop {} + } + + extern "C" fn do_nothing(_vmctx: *mut lucet_vmctx) -> () {} + + extern "C" fn run_slow_hostcall(vmctx: *mut lucet_vmctx) -> bool { + extern "C" { + fn slow_hostcall(vmctx: *mut lucet_vmctx) -> bool; + } + unsafe { slow_hostcall(vmctx) } + } + + extern "C" fn run_yielding_hostcall(vmctx: *mut lucet_vmctx) -> () { + extern "C" { + fn yielding_hostcall(vmctx: *mut lucet_vmctx) -> (); + } + unsafe { yielding_hostcall(vmctx) } + } + + MockModuleBuilder::new() + .with_export_func(MockExportBuilder::new( + "infinite_loop", + FunctionPointer::from_usize(infinite_loop as usize), + )) + .with_export_func(MockExportBuilder::new( + "do_nothing", + FunctionPointer::from_usize(do_nothing as usize), + )) + .with_export_func(MockExportBuilder::new( + "onetwothree", + FunctionPointer::from_usize(onetwothree as usize), + )) + .with_export_func(MockExportBuilder::new( + "run_slow_hostcall", + FunctionPointer::from_usize(run_slow_hostcall as usize), + )) + .with_export_func(MockExportBuilder::new( + "run_yielding_hostcall", + FunctionPointer::from_usize(run_yielding_hostcall as usize), + )) + .build() +} + +#[macro_export] +macro_rules! timeout_tests { + ( $TestRegion:path ) => { + use lucet_runtime::vmctx::{lucet_vmctx, Vmctx}; + use lucet_runtime::{ + lucet_hostcall, lucet_hostcall_terminate, DlModule, Error, FaultDetails, Instance, + KillError, KillSuccess, Limits, Region, RunResult, SignalBehavior, TerminationDetails, + TrapCode, YieldedVal, + }; + use nix::sys::mman::{mmap, MapFlags, ProtFlags}; + use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal}; + use nix::sys::wait::{waitpid, WaitStatus}; + use nix::unistd::{fork, ForkResult}; + use std::ptr; + use std::sync::{Arc, Mutex}; + use std::thread; + use std::time::Duration; + use $TestRegion as TestRegion; + use $crate::build::test_module_c; + use $crate::helpers::{FunctionPointer, MockExportBuilder, MockModuleBuilder}; + use $crate::timeout::mock_timeout_module; + + #[lucet_hostcall] + #[no_mangle] + pub fn slow_hostcall(vmctx: &mut Vmctx) -> bool { + // make a window of time so we can timeout in a hostcall + thread::sleep(Duration::from_millis(200)); + true + } + + #[lucet_hostcall] + #[no_mangle] + pub fn yielding_hostcall(vmctx: &mut Vmctx) { + vmctx.yield_(); + } + + fn run_onetwothree(inst: &mut Instance) { + let retval = inst + .run("onetwothree", &[]) + .expect("instance runs") + .unwrap_returned(); + assert_eq!(libc::c_int::from(retval), 123); + } + + #[test] + fn timeout_in_guest() { + let module = mock_timeout_module(); + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + let kill_switch = inst.kill_switch(); + + thread::spawn(move || { + thread::sleep(Duration::from_millis(100)); + assert_eq!(kill_switch.terminate(), Ok(KillSuccess::Signalled)); + }); + + match inst.run("infinite_loop", &[]) { + Err(Error::RuntimeTerminated(TerminationDetails::Remote)) => { + // this is what we want to see + } + res => panic!("unexpected result: {:?}", res), + } + + // after a timeout, can reset and run a normal function + inst.reset().expect("instance resets"); + + run_onetwothree(&mut inst); + } + + #[test] + fn timeout_before_guest() { + let module = mock_timeout_module(); + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + let kill_switch = inst.kill_switch(); + assert_eq!(kill_switch.terminate(), Err(KillError::NotTerminable)); + + // not being terminable, the instance still runs and is unaffected + run_onetwothree(&mut inst); + } + + #[test] + fn timeout_after_guest() { + let module = mock_timeout_module(); + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + match inst.run("do_nothing", &[]) { + Ok(_) => {} + res => panic!("unexpected result: {:?}", res), + } + + let kill_switch = inst.kill_switch(); + assert_eq!(kill_switch.terminate(), Err(KillError::NotTerminable)); + + // after a timeout, can reset and run a normal function + inst.reset().expect("instance resets"); + + run_onetwothree(&mut inst); + } + + #[test] + fn timeout_after_guest_fault() { + let module = test_module_c("timeout", "fault.c").expect("build and load module"); + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + match inst.run("main", &[0u32.into(), 0u32.into()]) { + Err(Error::RuntimeFault(details)) => { + assert_eq!(details.trapcode, Some(TrapCode::HeapOutOfBounds)); + } + res => panic!("unexpected result: {:?}", res), + } + + let kill_switch = inst.kill_switch(); + assert_eq!(kill_switch.terminate(), Err(KillError::NotTerminable)); + + // after a timeout, can reset and run a normal function + inst.reset().expect("instance resets"); + + run_onetwothree(&mut inst); + } + + #[test] + fn timeout_in_hostcall() { + let module = mock_timeout_module(); + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + let kill_switch = inst.kill_switch(); + + thread::spawn(move || { + thread::sleep(Duration::from_millis(100)); + assert_eq!(kill_switch.terminate(), Ok(KillSuccess::Pending)); + }); + + match inst.run("run_slow_hostcall", &[]) { + Err(Error::RuntimeTerminated(TerminationDetails::Remote)) => {} + res => panic!("unexpected result: {:?}", res), + } + + // after a timeout, can reset and run a normal function + inst.reset().expect("instance resets"); + + run_onetwothree(&mut inst); + } + + #[test] + fn timeout_while_yielded() { + let module = mock_timeout_module(); + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + let kill_switch = inst.kill_switch(); + + thread::spawn(move || { + thread::sleep(Duration::from_millis(100)); + assert_eq!(kill_switch.terminate(), Ok(KillSuccess::Pending)); + }); + + match inst.run("run_yielding_hostcall", &[]) { + Ok(RunResult::Yielded(EmptyYieldVal)) => {} + res => panic!("unexpected result: {:?}", res), + } + + println!("waiting......"); + + // wait for the timeout to expire + thread::sleep(Duration::from_millis(200)); + + match inst.resume() { + Err(Error::RuntimeTerminated(TerminationDetails::Remote)) => {} + res => panic!("unexpected result: {:?}", res), + } + + // after a timeout, can reset and run a normal function + inst.reset().expect("instance resets"); + + run_onetwothree(&mut inst); + } + + #[test] + fn timeout_killswitch_reuse() { + let module = test_module_c("timeout", "inf_loop.c").expect("build and load module"); + + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + let kill_switch = inst.kill_switch(); + + let t = thread::spawn(move || { + assert!(kill_switch.terminate().is_err()); // fails too soon + thread::sleep(Duration::from_millis(100)); + assert!(kill_switch.terminate().is_ok()); // works + thread::sleep(Duration::from_millis(100)); + assert!(kill_switch.terminate().is_err()); // fails too soon + }); + + thread::sleep(Duration::from_millis(10)); + + match inst.run("main", &[0u32.into(), 0u32.into()]) { + // the result we're expecting - the guest has been terminated! + Err(Error::RuntimeTerminated(TerminationDetails::Remote)) => {} + res => { + panic!("unexpected result: {:?}", res); + } + }; + + t.join().unwrap(); + } + + #[test] + fn double_timeout() { + let module = mock_timeout_module(); + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + let kill_switch = inst.kill_switch(); + let second_kill_switch = inst.kill_switch(); + + thread::spawn(move || { + thread::sleep(Duration::from_millis(100)); + assert_eq!(kill_switch.terminate(), Ok(KillSuccess::Signalled)); + }); + + thread::spawn(move || { + thread::sleep(Duration::from_millis(200)); + assert_eq!( + second_kill_switch.terminate(), + Err(KillError::NotTerminable) + ); + }); + + match inst.run("infinite_loop", &[]) { + Err(Error::RuntimeTerminated(TerminationDetails::Remote)) => { + // this is what we want to see + } + res => panic!("unexpected result: {:?}", res), + } + + // after a timeout, can reset and run a normal function + inst.reset().expect("instance resets"); + + run_onetwothree(&mut inst); + } + }; +} diff --git a/lucet-runtime/src/c_api.rs b/lucet-runtime/src/c_api.rs index 79b0a222c..4d69ab9c3 100644 --- a/lucet-runtime/src/c_api.rs +++ b/lucet-runtime/src/c_api.rs @@ -1,14 +1,15 @@ -extern crate lucet_runtime_internals; - -use crate::{DlModule, Instance, Limits, MmapRegion, Module, Region, TrapCode}; +use crate::{DlModule, Instance, Limits, MmapRegion, Module, Region}; use libc::{c_char, c_int, c_void}; +use lucet_module::TrapCode; use lucet_runtime_internals::c_api::*; use lucet_runtime_internals::instance::{ instance_handle_from_raw, instance_handle_to_raw, InstanceInternal, }; -use lucet_runtime_internals::vmctx::{instance_from_vmctx, lucet_vmctx, Vmctx, VmctxInternal}; +use lucet_runtime_internals::vmctx::{Vmctx, VmctxInternal}; use lucet_runtime_internals::WASM_PAGE_SIZE; -use lucet_runtime_internals::{assert_nonnull, with_ffi_arcs}; +use lucet_runtime_internals::{ + assert_nonnull, lucet_hostcall, lucet_hostcall_terminate, with_ffi_arcs, +}; use num_traits::FromPrimitive; use std::ffi::CStr; use std::ptr; @@ -39,11 +40,15 @@ pub extern "C" fn lucet_error_name(e: c_int) -> *const c_char { RegionFull => "lucet_error_region_full\0".as_ptr() as _, Module => "lucet_error_module\0".as_ptr() as _, LimitsExceeded => "lucet_error_limits_exceeded\0".as_ptr() as _, + NoLinearMemory => "lucet_error_no_linear_memory\0".as_ptr() as _, SymbolNotFound => "lucet_error_symbol_not_found\0".as_ptr() as _, FuncNotFound => "lucet_error_func_not_found\0".as_ptr() as _, RuntimeFault => "lucet_error_runtime_fault\0".as_ptr() as _, RuntimeTerminated => "lucet_error_runtime_terminated\0".as_ptr() as _, Dl => "lucet_error_dl\0".as_ptr() as _, + InstanceNotReturned => "lucet_error_instance_not_returned\0".as_ptr() as _, + InstanceNotYielded => "lucet_error_instance_not_yielded\0".as_ptr() as _, + StartYielded => "lucet_error_start_yielded\0".as_ptr() as _, Internal => "lucet_error_internal\0".as_ptr() as _, Unsupported => "lucet_error_unsupported\0".as_ptr() as _, } @@ -53,17 +58,18 @@ pub extern "C" fn lucet_error_name(e: c_int) -> *const c_char { } #[no_mangle] -pub extern "C" fn lucet_state_tag_name(tag: libc::c_int) -> *const c_char { - if let Some(tag) = lucet_state_tag::from_i32(tag) { - use self::lucet_state_tag::*; +pub extern "C" fn lucet_result_tag_name(tag: libc::c_int) -> *const c_char { + if let Some(tag) = lucet_result_tag::from_i32(tag) { + use self::lucet_result_tag::*; match tag { - Returned => "lucet_state_tag_returned\0".as_ptr() as _, - Running => "lucet_state_tag_running\0".as_ptr() as _, - Fault => "lucet_state_tag_fault\0".as_ptr() as _, - Terminated => "lucet_state_tag_terminated\0".as_ptr() as _, + Returned => "lucet_result_tag_returned\0".as_ptr() as _, + Yielded => "lucet_result_tag_yielded\0".as_ptr() as _, + Faulted => "lucet_result_tag_faulted\0".as_ptr() as _, + Terminated => "lucet_result_tag_terminated\0".as_ptr() as _, + Errored => "lucet_result_tag_errored\0".as_ptr() as _, } } else { - "!!! unknown lucet_state_tag variant!\0".as_ptr() as _ + "!!! unknown lucet_result_tag variant!\0".as_ptr() as _ } } @@ -150,6 +156,7 @@ pub unsafe extern "C" fn lucet_instance_run( entrypoint: *const c_char, argc: usize, argv: *const lucet_val::lucet_val, + result_out: *mut lucet_result::lucet_result, ) -> lucet_error { assert_nonnull!(entrypoint); if argc != 0 && argv.is_null() { @@ -163,14 +170,23 @@ pub unsafe extern "C" fn lucet_instance_run( .map(|v| v.into()) .collect() }; + let entrypoint = match CStr::from_ptr(entrypoint).to_str() { + Ok(entrypoint_str) => entrypoint_str, + Err(_) => { + return lucet_error::SymbolNotFound; + } + }; + with_instance_ptr!(inst, { - let entrypoint = CStr::from_ptr(entrypoint); - inst.run(entrypoint.to_bytes(), args.as_slice()) + let res = inst.run(entrypoint, args.as_slice()); + let ret = res + .as_ref() .map(|_| lucet_error::Ok) - .unwrap_or_else(|e| { - eprintln!("{}", e); - e.into() - }) + .unwrap_or_else(|e| e.into()); + if !result_out.is_null() { + std::ptr::write(result_out, res.into()); + } + ret }) } @@ -181,6 +197,7 @@ pub unsafe extern "C" fn lucet_instance_run_func_idx( func_idx: u32, argc: usize, argv: *const lucet_val::lucet_val, + result_out: *mut lucet_result::lucet_result, ) -> lucet_error { if argc != 0 && argv.is_null() { return lucet_error::InvalidArgument; @@ -194,38 +211,37 @@ pub unsafe extern "C" fn lucet_instance_run_func_idx( .collect() }; with_instance_ptr!(inst, { - inst.run_func_idx(table_idx, func_idx, args.as_slice()) + let res = inst.run_func_idx(table_idx, func_idx, args.as_slice()); + let ret = res + .as_ref() .map(|_| lucet_error::Ok) - .unwrap_or_else(|e| e.into()) + .unwrap_or_else(|e| e.into()); + if !result_out.is_null() { + std::ptr::write(result_out, res.into()); + } + ret }) } #[no_mangle] -pub unsafe extern "C" fn lucet_instance_state( +pub unsafe extern "C" fn lucet_instance_resume( inst: *const lucet_instance, - state_out: *mut lucet_state::lucet_state, + val: *mut c_void, + result_out: *mut lucet_result::lucet_result, ) -> lucet_error { - assert_nonnull!(state_out); with_instance_ptr!(inst, { - state_out.write(inst.state().into()); - lucet_error::Ok + let res = inst.resume_with_val(CYieldedVal { val }); + let ret = res + .as_ref() + .map(|_| lucet_error::Ok) + .unwrap_or_else(|e| e.into()); + if !result_out.is_null() { + std::ptr::write(result_out, res.into()); + } + ret }) } -#[no_mangle] -pub unsafe extern "C" fn lucet_state_release(state: *mut lucet_state::lucet_state) { - use lucet_runtime_internals::c_api::lucet_state::*; - use std::ffi::CString; - - let state = state.read(); - if let lucet_state_tag::Fault = state.tag { - let addr_details = state.val.fault.rip_addr_details; - // free the strings - CString::from_raw(addr_details.file_name as *mut _); - CString::from_raw(addr_details.sym_name as *mut _); - } -} - #[no_mangle] pub unsafe extern "C" fn lucet_instance_reset(inst: *mut lucet_instance) -> lucet_error { with_instance_ptr!(inst, { @@ -237,7 +253,7 @@ pub unsafe extern "C" fn lucet_instance_reset(inst: *mut lucet_instance) -> luce #[no_mangle] pub unsafe extern "C" fn lucet_instance_release(inst: *mut lucet_instance) { - instance_handle_from_raw(inst as *mut Instance); + instance_handle_from_raw(inst as *mut Instance, true); } #[no_mangle] @@ -282,7 +298,7 @@ pub unsafe extern "C" fn lucet_instance_grow_heap( pub unsafe extern "C" fn lucet_instance_embed_ctx(inst: *mut lucet_instance) -> *mut c_void { with_instance_ptr_unchecked!(inst, { inst.get_embed_ctx::<*mut c_void>() - .map(|p| *p) + .map(|r| r.map(|p| *p).unwrap_or(ptr::null_mut())) .unwrap_or(ptr::null_mut()) }) } @@ -293,14 +309,9 @@ pub unsafe extern "C" fn lucet_instance_set_signal_handler( inst: *mut lucet_instance, signal_handler: lucet_signal_handler, ) -> lucet_error { - let handler = move |inst: &Instance, trap: &TrapCode, signum, siginfo, context| { + let handler = move |inst: &Instance, trap: &Option, signum, siginfo, context| { let inst = inst as *const Instance as *mut lucet_instance; - let trap = trap.into(); - let trap_ptr = &trap as *const lucet_state::lucet_trapcode; - let res = signal_handler(inst, trap_ptr, signum, siginfo, context).into(); - // make sure `trap_ptr` is live until the signal handler returns - drop(trap); - res + signal_handler(inst, trap.into(), signum, siginfo, context).into() }; with_instance_ptr!(inst, { inst.set_signal_handler(handler); @@ -361,76 +372,154 @@ pub fn ensure_linked() { }); } +#[lucet_hostcall] #[no_mangle] -pub unsafe extern "C" fn lucet_vmctx_get_heap(vmctx: *mut lucet_vmctx) -> *mut u8 { - Vmctx::from_raw(vmctx).instance().alloc().slot().heap as *mut u8 +pub unsafe extern "C" fn lucet_vmctx_get_heap(vmctx: &mut Vmctx) -> *mut u8 { + vmctx.instance().alloc().slot().heap as *mut u8 } +#[lucet_hostcall] #[no_mangle] -pub unsafe extern "C" fn lucet_vmctx_get_globals(vmctx: *mut lucet_vmctx) -> *mut i64 { - Vmctx::from_raw(vmctx).instance().alloc().slot().globals as *mut i64 +pub unsafe extern "C" fn lucet_vmctx_get_globals(vmctx: &mut Vmctx) -> *mut i64 { + vmctx.instance().alloc().slot().globals as *mut i64 } -/// Get the number of WebAssembly pages currently in the heap. +#[lucet_hostcall] #[no_mangle] -pub unsafe extern "C" fn lucet_vmctx_current_memory(vmctx: *mut lucet_vmctx) -> libc::uint32_t { - Vmctx::from_raw(vmctx).instance().alloc().heap_len() as u32 / WASM_PAGE_SIZE +/// Get the number of WebAssembly pages currently in the heap. +pub unsafe extern "C" fn lucet_vmctx_current_memory(vmctx: &mut Vmctx) -> u32 { + vmctx.instance().alloc().heap_len() as u32 / WASM_PAGE_SIZE } +#[lucet_hostcall] #[no_mangle] /// Grows the guest heap by the given number of WebAssembly pages. /// /// On success, returns the number of pages that existed before the call. On failure, returns `-1`. -pub unsafe extern "C" fn lucet_vmctx_grow_memory( - vmctx: *mut lucet_vmctx, - additional_pages: libc::uint32_t, -) -> libc::int32_t { - let inst = instance_from_vmctx(vmctx); - if let Ok(old_pages) = inst.grow_memory(additional_pages) { - old_pages as libc::int32_t +pub unsafe extern "C" fn lucet_vmctx_grow_memory(vmctx: &mut Vmctx, additional_pages: u32) -> i32 { + if let Ok(old_pages) = vmctx.instance_mut().grow_memory(additional_pages) { + old_pages as i32 } else { -1 } } +#[lucet_hostcall] #[no_mangle] /// Check if a memory region is inside the instance heap. pub unsafe extern "C" fn lucet_vmctx_check_heap( - vmctx: *mut lucet_vmctx, + vmctx: &mut Vmctx, ptr: *mut c_void, len: libc::size_t, ) -> bool { - let inst = instance_from_vmctx(vmctx); - inst.check_heap(ptr, len) + vmctx.instance().check_heap(ptr, len) } +#[lucet_hostcall] #[no_mangle] pub unsafe extern "C" fn lucet_vmctx_get_func_from_idx( - vmctx: *mut lucet_vmctx, + vmctx: &mut Vmctx, table_idx: u32, func_idx: u32, ) -> *const c_void { - let inst = instance_from_vmctx(vmctx); - inst.module() + vmctx + .instance() + .module() .get_func_from_idx(table_idx, func_idx) - // the Rust API actually returns a pointer to a function pointer, so we want to dereference - // one layer of that to make it nicer in C - .map(|fptr| *(fptr as *const *const c_void)) + .map(|fptr| fptr.ptr.as_usize() as *const c_void) .unwrap_or(std::ptr::null()) } +#[lucet_hostcall] #[no_mangle] -pub unsafe extern "C" fn lucet_vmctx_terminate(vmctx: *mut lucet_vmctx, info: *mut c_void) { - Vmctx::from_raw(vmctx).terminate(info); +pub unsafe extern "C" fn lucet_vmctx_terminate(_vmctx: &mut Vmctx, details: *mut c_void) -> () { + lucet_hostcall_terminate!(CTerminationDetails { details }); } +#[lucet_hostcall] #[no_mangle] /// Get the delegate object for the current instance. /// /// TODO: rename -pub unsafe extern "C" fn lucet_vmctx_get_delegate(vmctx: *mut lucet_vmctx) -> *mut c_void { - let inst = instance_from_vmctx(vmctx); - inst.get_embed_ctx::<*mut c_void>() - .map(|p| *p) +/// +/// TODO: C implementations of hostcalls are highly questionable +pub unsafe extern "C" fn lucet_vmctx_get_delegate(vmctx: &mut Vmctx) -> *mut c_void { + vmctx + .instance() + .get_embed_ctx::<*mut c_void>() + .map(|r| r.map(|p| *p).unwrap_or(ptr::null_mut())) .unwrap_or(std::ptr::null_mut()) } + +/// TODO: C implementations of hostcalls are highly questionable +#[lucet_hostcall] +#[no_mangle] +pub unsafe extern "C" fn lucet_vmctx_yield(vmctx: &mut Vmctx, val: *mut c_void) -> *mut c_void { + vmctx + .yield_val_try_val(CYieldedVal { val }) + .map(|CYieldedVal { val }| val) + .unwrap_or(std::ptr::null_mut()) +} + +#[cfg(test)] +mod tests { + use super::lucet_dl_module; + use crate::DlModule; + use lucet_module::bindings::Bindings; + use lucet_wasi_sdk::{CompileOpts, LinkOpt, LinkOpts, Lucetc}; + use lucetc::LucetcOpts; + use std::sync::Arc; + use tempfile::TempDir; + + extern "C" { + fn lucet_runtime_test_expand_heap(module: *mut lucet_dl_module) -> bool; + fn lucet_runtime_test_yield_resume(module: *mut lucet_dl_module) -> bool; + } + + #[test] + fn expand_heap() { + let workdir = TempDir::new().expect("create working directory"); + + let native_build = Lucetc::new(&["tests/guests/null.c"]) + .with_cflag("-nostartfiles") + .with_link_opt(LinkOpt::NoDefaultEntryPoint) + .with_link_opt(LinkOpt::AllowUndefinedAll) + .with_link_opt(LinkOpt::ExportAll); + + let so_file = workdir.path().join("null.so"); + + native_build.build(so_file.clone()).unwrap(); + + let dlmodule = DlModule::load(so_file).unwrap(); + + unsafe { + assert!(lucet_runtime_test_expand_heap( + Arc::into_raw(dlmodule) as *mut lucet_dl_module + )); + } + } + + #[test] + fn yield_resume() { + let workdir = TempDir::new().expect("create working directory"); + + let native_build = Lucetc::new(&["tests/guests/yield_resume.c"]) + .with_cflag("-nostartfiles") + .with_link_opt(LinkOpt::NoDefaultEntryPoint) + .with_link_opt(LinkOpt::AllowUndefinedAll) + .with_link_opt(LinkOpt::ExportAll) + .with_bindings(Bindings::from_file("tests/guests/yield_resume_bindings.json").unwrap()); + + let so_file = workdir.path().join("yield_resume.so"); + + native_build.build(so_file.clone()).unwrap(); + + let dlmodule = DlModule::load(so_file).unwrap(); + + unsafe { + assert!(lucet_runtime_test_yield_resume( + Arc::into_raw(dlmodule) as *mut lucet_dl_module + )); + } + } +} diff --git a/lucet-runtime/src/lib.rs b/lucet-runtime/src/lib.rs index 9eeeabc48..d10ce4e08 100644 --- a/lucet-runtime/src/lib.rs +++ b/lucet-runtime/src/lib.rs @@ -37,10 +37,18 @@ //! arguments. These can be created using `From` implementations of primitive types, for example //! `5u64.into()` in the example below. //! +//! - [`RunResult`](enum.RunResult.html): the result of running or resuming an instance. These +//! contain either `UntypedRetVal`s for WebAssembly functions that have returned, or `YieldedVal`s +//! for WebAssembly programs that have yielded. +//! //! - [`UntypedRetVal`](struct.UntypedRetVal.html): values returned from WebAssembly //! functions. These must be interpreted at the correct type by the user via `From` implementations //! or `retval.as_T()` methods, for example `u64::from(retval)` in the example below. //! +//! - [`YieldedVal`](struct.YieldedVal.html): dynamically-values yielded by WebAssembly +//! programs. Not all yield points are given values, so this may be empty. To use the values, if +//! present, you must first downcast them with the provided methods. +//! //! To run a Lucet program, you start by creating a region, capable of backing a number of //! instances. You then load a module and then create a new instance using the region and the //! module. You can then run any of the functions that the Lucet program exports, retrieve return @@ -53,7 +61,7 @@ //! let region = MmapRegion::create(1, &Limits::default()).unwrap(); //! let mut inst = region.new_instance(module).unwrap(); //! -//! let retval = inst.run(b"factorial", &[5u64.into()]).unwrap(); +//! let retval = inst.run("factorial", &[5u64.into()]).unwrap().unwrap_returned(); //! assert_eq!(u64::from(retval), 120u64); //! ``` //! @@ -65,22 +73,22 @@ //! demo](https://wasm.fastly-labs.com/), hostcalls are provided for manipulating HTTP requests, //! accessing a key/value store, etc. //! -//! Some simple hostcalls can be implemented simply as an exported C function that takes an opaque -//! pointer argument (usually called `vmctx`). Hostcalls that require access to some underlying -//! state, such as the key/value store in Terrarium, can access a custom embedder context through -//! `vmctx`. For example, to make a `u32` available to hostcalls: +//! Some simple hostcalls can be implemented by using the +//! [`#[lucet_hostcall]`](attr.lucet_hostcall.html] attribute on a function that takes `&mut Vmctx` +//! as its first argument. Hostcalls that require access to some embedder-specific state, such as +//! Terrarium's key-value store, can access a custom embedder context through `vmctx`. For example, +//! to make a `u32` available to hostcalls: //! //! ```no_run -//! use lucet_runtime::{DlModule, Limits, MmapRegion, Region}; +//! use lucet_runtime::{DlModule, Limits, MmapRegion, Region, lucet_hostcall}; //! use lucet_runtime::vmctx::{Vmctx, lucet_vmctx}; //! //! struct MyContext { x: u32 } //! +//! #[lucet_hostcall] //! #[no_mangle] -//! unsafe extern "C" fn foo(vmctx: *mut lucet_vmctx) { -//! let mut vmctx = Vmctx::from_raw(vmctx); -//! let hostcall_context = vmctx -//! .get_embed_ctx_mut::(); +//! pub fn foo(vmctx: &mut Vmctx) { +//! let mut hostcall_context = vmctx.get_embed_ctx_mut::(); //! hostcall_context.x = 42; //! } //! @@ -92,9 +100,9 @@ //! .build() //! .unwrap(); //! -//! inst.run(b"call_foo", &[]).unwrap(); +//! inst.run("call_foo", &[]).unwrap(); //! -//! let context_after = inst.get_embed_ctx::().unwrap(); +//! let context_after = inst.get_embed_ctx::().unwrap().unwrap(); //! assert_eq!(context_after.x, 42); //! ``` //! @@ -121,7 +129,7 @@ //! .build() //! .unwrap(); //! -//! inst.run(b"main", &[]).unwrap(); +//! inst.run("main", &[]).unwrap(); //! //! // clean up embedder context //! drop(inst); @@ -130,6 +138,134 @@ //! unsafe { Box::from_raw(foreign_ctx) }; //! ``` //! +//! ## Yielding and Resuming +//! +//! Lucet hostcalls can use the `vmctx` argument to yield, suspending themselves and optionally +//! returning a value back to the host context. A yielded instance can then be resumed by the host, +//! and execution will continue from the point of the yield. +//! +//! Four yield methods are available for hostcall implementors: +//! +//! | | Yields value? | Expects value? | +//! |-------------------------------------------------------------------------------------|---------------|----------------| +//! | [`yield_`](vmctx/struct.Vmctx.html#method.yield_) | ❌ | ❌ | +//! | [`yield_val`](vmctx/struct.Vmctx.html#method.yield_val) | ✅ | ❌ | +//! | [`yield_expecting_val`](vmctx/struct.Vmctx.html#method.yield_expecting_val) | ❌ | ✅ | +//! | [`yield_val_expecting_val`](vmctx/struct.Vmctx.html#method.yield_val_expecting_val) | ✅ | ✅ | +//! +//! The host is free to ignore values yielded by guests, but a yielded instance may only be resumed +//! with a value of the correct type using +//! [`Instance::resume_with_val()`](struct.Instance.html#method.resume_with_val), if one is +//! expected. +//! +//! ### Factorial example +//! +//! In this example, we use yielding and resuming to offload multiplication to the host context, and +//! to incrementally return results to the host. While certainly overkill for computing a factorial +//! function, this structure mirrors that of many asynchronous workflows. +//! +//! Since the focus of this example is on the behavior of hostcalls that yield, our Lucet guest +//! program just invokes a hostcall: +//! +//! ```no_run +//! // factorials_guest.rs +//! extern "C" { +//! fn hostcall_factorials(n: u64) -> u64; +//! } +//! +//! #[no_mangle] +//! pub extern "C" fn run() -> u64 { +//! unsafe { +//! hostcall_factorials(5) +//! } +//! } +//! ``` +//! +//! In our hostcall, there are two changes from a standard recursive implementation of factorial. +//! +//! - Instead of performing the `n * fact(n - 1)` multiplication ourselves, we yield the operands +//! and expect the product when resumed. +//! +//! - Whenever we have computed a factorial, including both intermediate values and the final +//! answer, we yield it. +//! +//! The final answer is returned normally as the result of the guest function. +//! +//! To implement this, we introduce a new `enum` type to represent what we want the host to do next, +//! and yield it when appropriate. +//! +//! ```no_run +//! use lucet_runtime::lucet_hostcall; +//! use lucet_runtime::vmctx::Vmctx; +//! +//! pub enum FactorialsK { +//! Mult(u64, u64), +//! Result(u64), +//! } +//! +//! #[lucet_hostcall] +//! #[no_mangle] +//! pub fn hostcall_factorials(vmctx: &mut Vmctx, n: u64) -> u64 { +//! fn fact(vmctx: &mut Vmctx, n: u64) -> u64 { +//! let result = if n <= 1 { +//! 1 +//! } else { +//! let n_rec = fact(vmctx, n - 1); +//! // yield a request for the host to perform multiplication +//! vmctx.yield_val_expecting_val(FactorialsK::Mult(n, n_rec)) +//! // once resumed, that yield evaluates to the multiplication result +//! }; +//! // yield a result +//! vmctx.yield_val(FactorialsK::Result(result)); +//! result +//! } +//! fact(vmctx, n) +//! } +//! ``` +//! +//! The host side of the code, then, is an interpreter that repeatedly checks the yielded value and +//! performs the appropriate operation. The hostcall returns normally with the final answer when it +//! is finished, so we exit the loop when the run/resume result is `Ok`. +//! +//! ```no_run +//! # pub enum FactorialsK { +//! # Mult(u64, u64), +//! # Result(u64), +//! # } +//! use lucet_runtime::{DlModule, Error, Limits, MmapRegion, Region}; +//! +//! let module = DlModule::load("factorials_guest.so").unwrap(); +//! let region = MmapRegion::create(1, &Limits::default()).unwrap(); +//! let mut inst = region.new_instance(module).unwrap(); +//! +//! let mut factorials = vec![]; +//! +//! let mut res = inst.run("run", &[]).unwrap(); +//! +//! while let Ok(val) = res.yielded_ref() { +//! if let Some(k) = val.downcast_ref::() { +//! match k { +//! FactorialsK::Mult(n, n_rec) => { +//! // guest wants us to multiply for it +//! res = inst.resume_with_val(n * n_rec).unwrap(); +//! } +//! FactorialsK::Result(n) => { +//! // guest is returning an answer +//! factorials.push(*n); +//! res = inst.resume().unwrap(); +//! } +//! } +//! } else { +//! panic!("didn't yield with expected type"); +//! } +//! } +//! +//! // intermediate values are correct +//! assert_eq!(factorials.as_slice(), &[1, 2, 6, 24, 120]); +//! // final value is correct +//! assert_eq!(u64::from(res.unwrap_returned()), 120u64); +//! ``` +//! //! ## Custom Signal Handlers //! //! Since Lucet programs are run as native machine code, signals such as `SIGSEGV` and `SIGFPE` can @@ -144,7 +280,7 @@ //! //! ```no_run //! use lucet_runtime::{ -//! DlModule, Error, Instance, Limits, MmapRegion, Region, SignalBehavior, TrapCode +//! DlModule, Error, Instance, Limits, MmapRegion, Region, SignalBehavior, TrapCode, //! }; //! use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; //! @@ -152,7 +288,7 @@ //! //! fn signal_handler_count( //! _inst: &Instance, -//! _trapcode: &TrapCode, +//! _trapcode: &Option, //! _signum: libc::c_int, //! _siginfo_ptr: *const libc::siginfo_t, //! _ucontext_ptr: *const libc::c_void, @@ -168,7 +304,7 @@ //! // install the handler //! inst.set_signal_handler(signal_handler_count); //! -//! match inst.run(b"raise_a_signal", &[]) { +//! match inst.run("raise_a_signal", &[]) { //! Err(Error::RuntimeFault(_)) => { //! println!("I've now handled {} signals!", SIGNAL_COUNT.load(Ordering::SeqCst)); //! } @@ -182,7 +318,7 @@ //! //! ## Interaction With Host Signal Handlers //! -//! Great care must be taken if host application installs or otherwise modifies signal handlers +//! Great care must be taken if a host application installs or otherwise modifies signal handlers //! anywhere in the process. Lucet installs handlers for `SIGBUS`, `SIGFPE`, `SIGILL`, and `SIGSEGV` //! when the first Lucet instance begins running, and restores the preëxisting handlers when the //! last Lucet instance terminates. During this time, other threads in the host process *must not* @@ -195,19 +331,28 @@ //! that, for example, a `SIGSEGV` on a non-Lucet thread of a host program will still likely abort //! the entire process. -mod c_api; +#![deny(bare_trait_objects)] + +// This makes `lucet_runtime` in the expansion of `#[lucet_hostcall]` resolve to something +// meaningful when used in this crate. +extern crate self as lucet_runtime; +pub mod c_api; + +pub use lucet_module::{PublicKey, TrapCode}; pub use lucet_runtime_internals::alloc::Limits; pub use lucet_runtime_internals::error::Error; pub use lucet_runtime_internals::instance::{ - FaultDetails, Instance, InstanceHandle, SignalBehavior, TerminationDetails, + FaultDetails, Instance, InstanceHandle, KillError, KillSuccess, KillSwitch, RunResult, + SignalBehavior, TerminationDetails, YieldedVal, }; +#[allow(deprecated)] +pub use lucet_runtime_internals::lucet_hostcalls; pub use lucet_runtime_internals::module::{DlModule, Module}; pub use lucet_runtime_internals::region::mmap::MmapRegion; -pub use lucet_runtime_internals::region::{InstanceBuilder, Region}; -pub use lucet_runtime_internals::trapcode::{TrapCode, TrapCodeType}; +pub use lucet_runtime_internals::region::{InstanceBuilder, Region, RegionCreate}; pub use lucet_runtime_internals::val::{UntypedRetVal, Val}; -pub use lucet_runtime_internals::WASM_PAGE_SIZE; +pub use lucet_runtime_internals::{lucet_hostcall, lucet_hostcall_terminate, WASM_PAGE_SIZE}; pub mod vmctx { //! Functions for manipulating instances from hostcalls. @@ -223,6 +368,10 @@ pub mod vmctx { //! associated with a running instance. This should never occur if run in guest code on the //! pointer argument inserted by the compiler. pub use lucet_runtime_internals::vmctx::{lucet_vmctx, Vmctx}; + + // must be exported for `lucet_hostcall`, but we don't want to advertise it + #[doc(hidden)] + pub use lucet_runtime_internals::vmctx::VmctxInternal; } /// Call this if you're having trouble with `lucet_*` symbols not being exported. diff --git a/lucet-runtime/tests/c_api.c b/lucet-runtime/tests/c_api.c new file mode 100644 index 000000000..4f2dc3418 --- /dev/null +++ b/lucet-runtime/tests/c_api.c @@ -0,0 +1,178 @@ +#include +#include + +#include "lucet.h" + +bool lucet_runtime_test_expand_heap(struct lucet_dl_module *mod) +{ + struct lucet_region * region; + struct lucet_alloc_limits limits = { + .heap_memory_size = 4 * 1024 * 1024, + .heap_address_space_size = 8 * 1024 * 1024, + .stack_size = 64 * 1024, + .globals_size = 4096, + }; + + enum lucet_error err; + + err = lucet_mmap_region_create(1, &limits, ®ion); + if (err != lucet_error_ok) { + fprintf(stderr, "failed to create region\n"); + goto fail1; + } + + struct lucet_instance *inst; + err = lucet_region_new_instance(region, mod, &inst); + if (err != lucet_error_ok) { + fprintf(stderr, "failed to create instance\n"); + goto fail2; + } + + uint32_t newpage_start; + err = lucet_instance_grow_heap(inst, 1, &newpage_start); + if (err != lucet_error_ok) { + fprintf(stderr, "failed to grow memory\n"); + goto fail3; + } + + lucet_instance_release(inst); + lucet_region_release(region); + lucet_dl_module_release(mod); + + return true; + +fail3: + lucet_instance_release(inst); +fail2: + lucet_region_release(region); +fail1: + lucet_dl_module_release(mod); + return false; +} + +enum yield_resume_tag { + yield_resume_tag_mult, + yield_resume_tag_result, +}; + +struct yield_resume_mult { + uint64_t x; + uint64_t y; +}; + +union yield_resume_val_inner { + struct yield_resume_mult mult; + uint64_t result; +}; + +struct yield_resume_val { + enum yield_resume_tag tag; + union yield_resume_val_inner val; +}; + +uint64_t lucet_runtime_test_hostcall_yield_resume(struct lucet_vmctx *vmctx, uint64_t n) +{ + if (n <= 1) { + struct yield_resume_val result_val = { .tag = yield_resume_tag_result, + .val = { .result = 1 } }; + lucet_vmctx_yield(vmctx, &result_val); + return 1; + } else { + uint64_t n_rec = lucet_runtime_test_hostcall_yield_resume(vmctx, n - 1); + struct yield_resume_val mult_val = { .tag = yield_resume_tag_mult, + .val = { .mult = { .x = n, .y = n_rec } } }; + uint64_t n = *(uint64_t *) lucet_vmctx_yield(vmctx, &mult_val); + struct yield_resume_val result_val = { .tag = yield_resume_tag_result, + .val = { .result = n } }; + lucet_vmctx_yield(vmctx, &result_val); + return n; + } +} + +bool lucet_runtime_test_yield_resume(struct lucet_dl_module *mod) +{ + struct lucet_region * region; + struct lucet_alloc_limits limits = { + .heap_memory_size = 4 * 1024 * 1024, + .heap_address_space_size = 8 * 1024 * 1024, + .stack_size = 64 * 1024, + .globals_size = 4096, + }; + + enum lucet_error err; + + err = lucet_mmap_region_create(1, &limits, ®ion); + if (err != lucet_error_ok) { + fprintf(stderr, "failed to create region\n"); + goto fail1; + } + + struct lucet_instance *inst; + err = lucet_region_new_instance(region, mod, &inst); + if (err != lucet_error_ok) { + fprintf(stderr, "failed to create instance\n"); + goto fail2; + } + + uint64_t results[5] = { 0 }; + size_t i = 0; + + struct lucet_result res; + lucet_instance_run(inst, "f", 0, (const struct lucet_val[]){}, &res); + while (res.tag == lucet_result_tag_yielded) { + if (i >= 5) { + fprintf(stderr, "hostcall yielded too many results\n"); + goto fail3; + } + + struct yield_resume_val val = *(struct yield_resume_val *) res.val.yielded.val; + + switch (val.tag) { + case yield_resume_tag_mult: { + uint64_t mult_result = val.val.mult.x * val.val.mult.y; + lucet_instance_resume(inst, &mult_result, &res); + continue; + } + case yield_resume_tag_result: { + results[i++] = val.val.result; + lucet_instance_resume(inst, NULL, &res); + continue; + } + default: { + fprintf(stderr, "unexpected yield_resume_tag\n"); + goto fail3; + } + } + } + if (err != lucet_error_ok) { + fprintf(stderr, "instance finished with non-ok error: %s\n", lucet_error_name(err)); + goto fail3; + } + + if (res.tag != lucet_result_tag_returned) { + fprintf(stderr, "final instance result wasn't returned\n"); + goto fail3; + } + + uint64_t final_result = LUCET_UNTYPED_RETVAL_TO_U64(res.val.returned); + + lucet_instance_release(inst); + lucet_region_release(region); + lucet_dl_module_release(mod); + + uint64_t expected_results[5] = { 1, 2, 6, 24, 120 }; + bool results_correct = final_result == 120; + for (i = 0; i < 5; i++) { + results_correct = results_correct && (results[i] == expected_results[i]); + } + + return results_correct; + +fail3: + lucet_instance_release(inst); +fail2: + lucet_region_release(region); +fail1: + lucet_dl_module_release(mod); + return false; +} diff --git a/lucet-runtime/tests/guests/null.c b/lucet-runtime/tests/guests/null.c new file mode 100644 index 000000000..dbec2355b --- /dev/null +++ b/lucet-runtime/tests/guests/null.c @@ -0,0 +1,3 @@ +void f() { + return; +} diff --git a/lucet-runtime/tests/guests/yield_resume.c b/lucet-runtime/tests/guests/yield_resume.c new file mode 100644 index 000000000..22b68f37e --- /dev/null +++ b/lucet-runtime/tests/guests/yield_resume.c @@ -0,0 +1,8 @@ +#include + +extern uint64_t lucet_runtime_test_hostcall_yield_resume(uint64_t n); + +uint64_t f() +{ + return lucet_runtime_test_hostcall_yield_resume(5); +} diff --git a/lucet-runtime/tests/guests/yield_resume_bindings.json b/lucet-runtime/tests/guests/yield_resume_bindings.json new file mode 100644 index 000000000..fe9ffc6af --- /dev/null +++ b/lucet-runtime/tests/guests/yield_resume_bindings.json @@ -0,0 +1,5 @@ +{ + "env": { + "lucet_runtime_test_hostcall_yield_resume": "lucet_runtime_test_hostcall_yield_resume" + } +} diff --git a/lucet-runtime/tests/instruction_counting.rs b/lucet-runtime/tests/instruction_counting.rs new file mode 100644 index 000000000..9230e4618 --- /dev/null +++ b/lucet-runtime/tests/instruction_counting.rs @@ -0,0 +1,72 @@ +use anyhow::Error; +use lucet_runtime::{DlModule, Limits, MmapRegion, Region, RunResult}; +use lucetc::{Lucetc, LucetcOpts}; +use rayon::prelude::*; +use std::fs::DirEntry; +use std::path::Path; +use std::sync::Arc; +use tempfile::TempDir; + +pub fn wasm_test>(wasm_file: P) -> Result, Error> { + let workdir = TempDir::new().expect("create working directory"); + + let native_build = Lucetc::new(wasm_file).with_count_instructions(true); + + let so_file = workdir.path().join("out.so"); + + native_build.shared_object_file(so_file.clone())?; + + let dlmodule = DlModule::load(so_file)?; + + Ok(dlmodule) +} + +#[test] +pub fn check_instruction_counts() { + let files: Vec = std::fs::read_dir("./tests/instruction_counting") + .expect("can iterate test files") + .map(|ent| { + let ent = ent.expect("can get test files"); + assert!( + ent.file_type().unwrap().is_file(), + "directories not supported in test/instruction_counting" + ); + ent + }) + .collect(); + + assert!( + files.len() > 0, + "there are no test cases in the `instruction_counting` directory" + ); + + files.par_iter().for_each(|ent| { + let wasm_path = ent.path(); + let module = wasm_test(&wasm_path).expect("can load module"); + + let region = MmapRegion::create(1, &Limits::default()).expect("region can be created"); + + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + inst.run("test_function", &[]).expect("instance runs"); + + let instruction_count = inst.get_instruction_count(); + + assert_eq!( + instruction_count, + match inst + .run("instruction_count", &[]) + .expect("instance still runs") + { + RunResult::Returned(value) => value.as_i64() as u64, + RunResult::Yielded(_) => { + panic!("instruction counting test runner doesn't support yielding"); + } + }, + "instruction count for test case {} is incorrect", + wasm_path.display() + ); + }); +} diff --git a/lucet-runtime/tests/instruction_counting/arithmetic_count.wat b/lucet-runtime/tests/instruction_counting/arithmetic_count.wat new file mode 100644 index 000000000..90891313a --- /dev/null +++ b/lucet-runtime/tests/instruction_counting/arithmetic_count.wat @@ -0,0 +1,11 @@ +(module + (func $main (export "test_function") + i32.const 4 + i32.const -5 + i32.add + drop + ) + (func $instruction_count (export "instruction_count") (result i64) + i64.const 3 + ) +) diff --git a/lucet-runtime/tests/instruction_counting/br_table_count.wat b/lucet-runtime/tests/instruction_counting/br_table_count.wat new file mode 100644 index 000000000..29846f083 --- /dev/null +++ b/lucet-runtime/tests/instruction_counting/br_table_count.wat @@ -0,0 +1,25 @@ +(module + (func $main (export "test_function") + block $a + i64.const 1 + drop + + block $b + i64.const 2 + drop + + block $c + i64.const 3 + drop + + ;; 3 for above consts, one for i32.const below, 2 for br_table + ;; totalling to an expected count of 6 + (br_table 0 1 2 3 (i32.const 4)) + end + end + end + ) + (func $instruction_count (export "instruction_count") (result i64) + i64.const 6 + ) +) diff --git a/lucet-runtime/tests/instruction_counting/calls.wat b/lucet-runtime/tests/instruction_counting/calls.wat new file mode 100644 index 000000000..6fba4c4e6 --- /dev/null +++ b/lucet-runtime/tests/instruction_counting/calls.wat @@ -0,0 +1,22 @@ +(module + (func $mul2 (param $in i64) (result i64) + get_local $in + get_local $in + i64.mul + ) + (func $main (export "test_function") + i64.const 1 + call $mul2 ;; one from the call, three from the called function, one for the return + drop + i64.const 2 + call $mul2 ;; and again + drop + i64.const 3 + call $mul2 ;; and again + ;; for a total of 3 * 6 == 18 instructions + drop + ) + (func $instruction_count (export "instruction_count") (result i64) + i64.const 18 + ) +) diff --git a/lucet-runtime/tests/instruction_counting/count_after_br.wat b/lucet-runtime/tests/instruction_counting/count_after_br.wat new file mode 100644 index 000000000..52e180f6f --- /dev/null +++ b/lucet-runtime/tests/instruction_counting/count_after_br.wat @@ -0,0 +1,21 @@ +(module + (func $main (export "test_function") + block $ops + i32.const 11 + i32.const 10 + i32.add + + br 0 ;; branch to enclosing scope (end of this block) + ;; at this point we've counted four operations... + end + + i32.const 14 + i32.const 15 + i32.add + ;; this puts us at 7 operations + drop + ) + (func $instruction_count (export "instruction_count") (result i64) + i64.const 7 + ) +) diff --git a/lucet-runtime/tests/instruction_counting/empty_loop.wat b/lucet-runtime/tests/instruction_counting/empty_loop.wat new file mode 100644 index 000000000..181774313 --- /dev/null +++ b/lucet-runtime/tests/instruction_counting/empty_loop.wat @@ -0,0 +1,9 @@ +(module + (func $main (export "test_function") (local $i i32) + loop $inner + end + ) + (func $instruction_count (export "instruction_count") (result i64) + i64.const 0 + ) +) diff --git a/lucet-runtime/tests/instruction_counting/empty_loop_2.wat b/lucet-runtime/tests/instruction_counting/empty_loop_2.wat new file mode 100644 index 000000000..302e0df8e --- /dev/null +++ b/lucet-runtime/tests/instruction_counting/empty_loop_2.wat @@ -0,0 +1,11 @@ +(module + (func $main (export "test_function") (local $i i32) + block $a + loop $inner + end + end + ) + (func $instruction_count (export "instruction_count") (result i64) + i64.const 0 + ) +) diff --git a/lucet-runtime/tests/instruction_counting/if_count.wat b/lucet-runtime/tests/instruction_counting/if_count.wat new file mode 100644 index 000000000..e4bcb617e --- /dev/null +++ b/lucet-runtime/tests/instruction_counting/if_count.wat @@ -0,0 +1,21 @@ +(module + (func $main (export "test_function") + i32.const 11 + i32.const 10 + i32.gt_s + ;; this counts up to 3 + (if ;; this makes 4 + (then + i64.const 5 + i64.const 10 + i64.mul + ;; and these were another 3 + drop + ;; drop is ignored for a total of 7 operations + ) + ) + ) + (func $instruction_count (export "instruction_count") (result i64) + i64.const 7 + ) +) diff --git a/lucet-runtime/tests/instruction_counting/if_not_taken_count.wat b/lucet-runtime/tests/instruction_counting/if_not_taken_count.wat new file mode 100644 index 000000000..af5794d5a --- /dev/null +++ b/lucet-runtime/tests/instruction_counting/if_not_taken_count.wat @@ -0,0 +1,21 @@ +(module + (func $main (export "test_function") + i32.const 10 + i32.const 11 + i32.gt_s + ;; this counts up to 3 + (if ;; this makes 4 + ;; but the `then` branch is not taken + (then + i64.const 5 + i64.const 10 + i64.mul + drop + ) + ) + ;; so we only execute 4 operations + ) + (func $instruction_count (export "instruction_count") (result i64) + i64.const 4 + ) +) diff --git a/lucet-runtime/tests/instruction_counting/indirect_calls.wat b/lucet-runtime/tests/instruction_counting/indirect_calls.wat new file mode 100644 index 000000000..e6a5bbf99 --- /dev/null +++ b/lucet-runtime/tests/instruction_counting/indirect_calls.wat @@ -0,0 +1,68 @@ +(module + (type $mulfn (func (param i64) (result i64))) + + ;; mul2 is 3 operations + (func $mul2 (param $in i64) (result i64) + get_local $in + get_local $in + i64.add + ) + ;; mul4 is 2 * |mul2| + 2 call + 3 == 13 + (func $mul4 (param $in i64) (result i64) + get_local $in + call $mul2 + get_local $in + call $mul2 + i64.add + ) + ;; mul8 is 2 * |mul4| + 2 call + 3 == 33 + (func $mul8 (param $in i64) (result i64) + get_local $in + call $mul4 + get_local $in + call $mul4 + i64.add + ) + ;; mul16 is 2 * |mul8| + 2 call + 3 == 73 + ;; by entire accident the number of instructions for + ;; subsequent similar functions for higher powers is given by + ;; mul(n^2) == 10 * (2 ^ (n - 1)) - 10 + 3 + (func $mul16 (param $in i64) (result i64) + get_local $in + call $mul8 + get_local $in + call $mul8 + i64.add + ) + + (table anyfunc + (elem + $mul2 $mul4 $mul8 $mul16 + ) + ) + + (func $main (export "test_function") + ;; call_indirect here is 2, plus 1 for the const, one for the index, and + ;; 1 for return + ;; the called function (mul2) is 3 instructions, for 8 total. + (call_indirect (type $mulfn) (i64.const 0) (i32.const 0)) + drop + + ;; call_indirect here is 2, plus 1 for the const, one for the index, and + ;; 1 for return + ;; the called function (mul4) is 13 instructions, for 18 total. + (call_indirect (type $mulfn) (i64.const 1) (i32.const 1)) + drop + + ;; call_indirect here is 2, plus 1 for the const, one for the index, and + ;; 1 for return + ;; the called function (mul16) is 73 instructions, for 78 total. + (call_indirect (type $mulfn) (i64.const 2) (i32.const 3)) + drop + + ;; for a total of 8 + 18 + 78 == 104 instructions + ) + (func $instruction_count (export "instruction_count") (result i64) + i64.const 104 + ) +) diff --git a/lucet-runtime/tests/instruction_counting/loops.wat b/lucet-runtime/tests/instruction_counting/loops.wat new file mode 100644 index 000000000..3b68a0abd --- /dev/null +++ b/lucet-runtime/tests/instruction_counting/loops.wat @@ -0,0 +1,30 @@ +(module + (func $main (export "test_function") (local $i i32) + i32.const 0 + set_local $i + block $outer + loop $inner + ;; each loop iteration is: + ;; * 4 operations to increment i + ;; * 3 operations to test i == 10 + ;; * 1 branch to break (untaken) + ;; * 1 branch to loop + get_local $i + i32.const 1 + i32.add + set_local $i + get_local $i + i32.const 10 + i32.eq + br_if $outer + br $inner + end + end + ;; iterating i = 0..9, that's 10 * 8 instructions from full executions, + ;; plus 9 instructions from the last round. + ;; add two for initializing i and that gives 80 + 9 + 2 = 91 instructions + ) + (func $instruction_count (export "instruction_count") (result i64) + i64.const 91 + ) +) diff --git a/lucet-runtime/tests/instruction_counting/unreachable_call.wat b/lucet-runtime/tests/instruction_counting/unreachable_call.wat new file mode 100644 index 000000000..3caf47428 --- /dev/null +++ b/lucet-runtime/tests/instruction_counting/unreachable_call.wat @@ -0,0 +1,17 @@ +(module + (func $main (export "test_function") (result i64) + ;; counting the const + i64.const 1 + ;; return is counted by the caller, so we count 1 so far + return + + ;; we had a bug where calls in unreachable code would still add + ;; one to the instruction counter to track a matching return, + ;; but the call would never be made, so the return would never + ;; occur, and the count was in error. + call $main + ) + (func $instruction_count (export "instruction_count") (result i64) + i64.const 1 + ) +) diff --git a/lucet-runtime/tests/timeout.rs b/lucet-runtime/tests/timeout.rs new file mode 100644 index 000000000..9c2f044ed --- /dev/null +++ b/lucet-runtime/tests/timeout.rs @@ -0,0 +1,3 @@ +use lucet_runtime_tests::timeout_tests; + +timeout_tests!(lucet_runtime::MmapRegion); diff --git a/lucet-runtime/tests/val.rs b/lucet-runtime/tests/val.rs new file mode 100644 index 000000000..7fe78e3e2 --- /dev/null +++ b/lucet-runtime/tests/val.rs @@ -0,0 +1,61 @@ +use lucet_runtime_internals::val::UntypedRetVal; + +#[test] +fn untyped_ret_val_from_f32() { + assert_eq!(1.2, f32::from(UntypedRetVal::from(1.2f32))); +} + +#[test] +fn untyped_ret_val_from_f64() { + assert_eq!(1.2, f64::from(UntypedRetVal::from(1.2f64))); +} + +#[test] +fn untyped_ret_val_from_u8() { + assert_eq!(5, u8::from(UntypedRetVal::from(5u8))); +} + +#[test] +fn untyped_ret_val_from_u16() { + assert_eq!(5, u16::from(UntypedRetVal::from(5u16))); +} + +#[test] +fn untyped_ret_val_from_u32() { + assert_eq!(5, u32::from(UntypedRetVal::from(5u32))); +} + +#[test] +fn untyped_ret_val_from_u64() { + assert_eq!(5, u64::from(UntypedRetVal::from(5u64))); +} + +#[test] +fn untyped_ret_val_from_i8() { + assert_eq!(5, i8::from(UntypedRetVal::from(5i8))); +} + +#[test] +fn untyped_ret_val_from_i16() { + assert_eq!(5, i16::from(UntypedRetVal::from(5i16))); +} + +#[test] +fn untyped_ret_val_from_i32() { + assert_eq!(5, i32::from(UntypedRetVal::from(5i32))); +} + +#[test] +fn untyped_ret_val_from_i64() { + assert_eq!(5, i64::from(UntypedRetVal::from(5i64))); +} + +#[test] +fn untyped_ret_val_from_bool_true() { + assert_eq!(true, bool::from(UntypedRetVal::from(true))); +} + +#[test] +fn untyped_ret_val_from_bool_false() { + assert_eq!(false, bool::from(UntypedRetVal::from(false))); +} diff --git a/lucet-runtime/tests/version_checks.rs b/lucet-runtime/tests/version_checks.rs new file mode 100644 index 000000000..fa5c9a30c --- /dev/null +++ b/lucet-runtime/tests/version_checks.rs @@ -0,0 +1,16 @@ +use lucet_runtime::{DlModule, Error}; + +#[test] +pub fn reject_old_modules() { + let err = DlModule::load("./tests/version_checks/old_module.so") + .err() + .unwrap(); + + if let Error::ModuleError(e) = err { + let msg = format!("{}", e); + assert!(msg.contains("reserved bit is not set")); + assert!(msg.contains("module is likely too old")); + } else { + panic!("unexpected error loading module: {}", err); + } +} diff --git a/lucet-runtime/tests/version_checks/old_module.so b/lucet-runtime/tests/version_checks/old_module.so new file mode 100755 index 000000000..cf04b77a7 Binary files /dev/null and b/lucet-runtime/tests/version_checks/old_module.so differ diff --git a/lucet-spectest/Cargo.toml b/lucet-spectest/Cargo.toml index 86ee785eb..7ee2f0b0c 100644 --- a/lucet-spectest/Cargo.toml +++ b/lucet-spectest/Cargo.toml @@ -1,10 +1,12 @@ [package] name = "lucet-spectest" -version = "0.1.0" +version = "0.5.0" description = "Test harness to run WebAssembly spec tests (.wast) against the Lucet toolchain" +homepage = "https://github.com/fastly/lucet" repository = "https://github.com/fastly/lucet" -authors = ["Pat Hickey "] license = "Apache-2.0 WITH LLVM-exception" +authors = ["Lucet team "] +categories = ["wasm"] edition = "2018" [lib] @@ -15,12 +17,14 @@ name = "spec-test" path = "src/main.rs" [dependencies] -lucetc = { path = "../lucetc" } -lucet-runtime = { path = "../lucet-runtime" } -parity-wasm = "0.35" -wabt = "0.7" +anyhow = "1" +lucetc = { path = "../lucetc", version = "=0.5.0" } +lucet-module = { path = "../lucet-module", version = "=0.5.0" } +lucet-runtime = { path = "../lucet-runtime", version = "=0.5.0" } +wabt = "0.9.2" serde = "1.0" serde_json = "1.0" -failure = "0.1" clap="2.32" tempfile = "3.0" +target-lexicon = "0.9" +thiserror = "1.0.4" diff --git a/lucet-spectest/src/bindings.rs b/lucet-spectest/src/bindings.rs index 8a0fe06d3..79f443563 100644 --- a/lucet-spectest/src/bindings.rs +++ b/lucet-spectest/src/bindings.rs @@ -1,6 +1,26 @@ -use lucetc::bindings::Bindings; +use lucet_module::bindings::Bindings; +use lucet_runtime::lucet_hostcall; +use lucet_runtime::vmctx::Vmctx; use serde_json::json; +#[lucet_hostcall] +#[no_mangle] +pub fn print(_vmctx: &mut Vmctx) { + println!("hello, world!"); +} + +#[lucet_hostcall] +#[no_mangle] +pub fn print_i32(_vmctx: &mut Vmctx, x: i32) { + println!("{}", x); +} + +#[lucet_hostcall] +#[no_mangle] +pub fn print_f32(_vmctx: &mut Vmctx, x: i32) { + println!("{}", x); +} + pub fn spec_test_bindings() -> Bindings { let imports: serde_json::Value = json!({ "test": { diff --git a/lucet-spectest/src/error.rs b/lucet-spectest/src/error.rs index ea49ae1ca..f0da58470 100644 --- a/lucet-spectest/src/error.rs +++ b/lucet-spectest/src/error.rs @@ -1,56 +1,21 @@ -use failure::{Backtrace, Context, Fail}; -use std::fmt::{self, Display}; +use thiserror::Error; -#[derive(Debug)] -pub struct SpecTestError { - inner: Context, -} - -impl From> for SpecTestError { - fn from(inner: Context) -> SpecTestError { - SpecTestError { inner } - } -} - -impl From for SpecTestError { - fn from(kind: SpecTestErrorKind) -> SpecTestError { - SpecTestError { - inner: Context::new(kind), - } - } -} - -impl SpecTestError { - pub fn get_context(&self) -> &SpecTestErrorKind { - self.inner.get_context() - } -} - -impl Fail for SpecTestError { - fn cause(&self) -> Option<&Fail> { - self.inner.cause() - } - fn backtrace(&self) -> Option<&Backtrace> { - self.inner.backtrace() - } -} - -impl Display for SpecTestError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - Display::fmt(&self.inner, f) - } -} - -#[derive(Fail, Debug, PartialEq, Eq)] -pub enum SpecTestErrorKind { - #[fail(display = "Unsupported command")] - UnsupportedCommand, - #[fail(display = "Unexpected success")] +#[derive(Debug, Error)] +pub enum Error { + #[error("Parse error")] + ParseError(#[from] wabt::script::Error), + #[error("Read error")] + ReadError(#[from] std::io::Error), + #[error("Run failed with {0} failures")] + RunError(usize), + #[error("Unsupported command: {0}")] + UnsupportedCommand(String), + #[error("Unexpected success")] UnexpectedSuccess, - #[fail(display = "Unexpected failure")] - UnexpectedFailure, - #[fail(display = "Incorrect result")] - IncorrectResult, - #[fail(display = "Unsupported by lucetc")] + #[error("Unexpected failure: {0}")] + UnexpectedFailure(String), + #[error("Incorrect result: {0}")] + IncorrectResult(String), + #[error("Unsupported by lucetc")] UnsupportedLucetc, } diff --git a/lucet-spectest/src/instance.rs b/lucet-spectest/src/instance.rs deleted file mode 100644 index d91650719..000000000 --- a/lucet-spectest/src/instance.rs +++ /dev/null @@ -1,113 +0,0 @@ -use failure::{format_err, Error}; -use lucet_runtime::{ - Error as RuntimeError, Module as LucetModule, Region as LucetRegion, UntypedRetVal, Val, -}; -use lucetc::program::Program; -pub use parity_wasm::elements::ValueType; -use parity_wasm::elements::{Internal, Type}; -use std::sync::Arc; - -// some of the fields of this are not used, but they need to be stored -// because lifetimes -#[allow(dead_code)] -pub struct Instance { - program: Program, - lucet_module: Arc, - lucet_region: Arc, - lucet_instance: lucet_runtime::InstanceHandle, -} - -impl Instance { - pub fn new( - program: Program, - lucet_module: Arc, - lucet_region: Arc, - lucet_instance: lucet_runtime::InstanceHandle, - ) -> Self { - Self { - program, - lucet_module, - lucet_region, - lucet_instance, - } - } - - pub fn run(&mut self, field: &str, args: &[Val]) -> Result { - let res = self.lucet_instance.run(field.as_bytes(), args); - if let Err(_) = res { - self.lucet_instance - .reset() - .expect("possible to reset instance"); - } - res - } - - pub fn type_of(&self, field: &str) -> Result { - if let Some(ref export_section) = self.program.module().export_section() { - export_section - .entries() - .iter() - .find(|entry| entry.field() == field) - .map(|entry| match entry.internal() { - Internal::Function(func_ix) => self.func_type(*func_ix), - Internal::Global(global_ix) => self.global_type(*global_ix), - _ => Err(format_err!( - "cannot take type of export \"{}\": {:?}", - field, - entry.internal() - ))?, - }) - .ok_or_else(|| format_err!("cannot find export named \"{}\"", field))? - } else { - Err(format_err!("no exports to find \"{}\" in", field)) - } - } - - fn func_type(&self, func_ix: u32) -> Result { - if let Some(func_section) = self.program.module().function_section() { - if let Some(func_entry) = func_section.entries().get(func_ix as usize) { - if let Some(type_section) = self.program.module().type_section() { - if let Some(Type::Function(func_type)) = - type_section.types().get(func_entry.type_ref() as usize) - { - Ok(ExportType::Function( - func_type.params().to_vec(), - func_type.return_type(), - )) - } else { - Err(format_err!( - "type ix {} out of bounds", - func_entry.type_ref() - )) - } - } else { - Err(format_err!("no type section!")) - } - } else { - Err(format_err!("func ix {} out of bounds", func_ix)) - } - } else { - Err(format_err!("no func section!")) - } - } - - fn global_type(&self, global_ix: u32) -> Result { - if let Some(global_section) = self.program.module().global_section() { - if let Some(global_entry) = global_section.entries().get(global_ix as usize) { - Ok(ExportType::Global( - global_entry.global_type().content_type(), - )) - } else { - Err(format_err!("no such global {}", global_ix)) - } - } else { - Err(format_err!("no section to find global {}", global_ix)) - } - } -} - -#[derive(Debug)] -pub enum ExportType { - Function(Vec, Option), - Global(ValueType), -} diff --git a/lucet-spectest/src/lib.rs b/lucet-spectest/src/lib.rs index 307388b2e..362b393ed 100644 --- a/lucet-spectest/src/lib.rs +++ b/lucet-spectest/src/lib.rs @@ -1,17 +1,16 @@ +#![deny(bare_trait_objects)] + pub mod error; -pub mod instance; pub mod script; -pub use crate::error::{SpecTestError, SpecTestErrorKind}; +pub use crate::error::Error; pub use crate::result::{command_description, SpecScriptResult}; mod bindings; mod result; -use crate::instance::{ExportType, ValueType}; use crate::script::{ScriptEnv, ScriptError}; -use failure::{format_err, Error, ResultExt}; -use lucet_runtime::{Error as RuntimeError, TrapCodeType, UntypedRetVal, Val}; +use lucet_runtime::{Error as RuntimeError, TrapCode, UntypedRetVal, Val}; use std::fs; use std::path::PathBuf; use wabt::script::{Action, CommandKind, ScriptParser, Value}; @@ -26,11 +25,15 @@ pub fn run_spec_test(spec_path: &PathBuf) -> Result { while let Some(ref cmd) = parser.next()? { match step(&mut script, &cmd.kind) { Ok(()) => res.pass(cmd), - Err(e) => match e.get_context() { - SpecTestErrorKind::UnsupportedCommand | SpecTestErrorKind::UnsupportedLucetc => { + Err(e) => match e { + Error::UnsupportedCommand(_) | Error::UnsupportedLucetc => { + println!("skipped unsupported command"); res.skip(cmd, e) } - _ => res.fail(cmd, e), + _ => { + println!("command failed"); + res.fail(cmd, e) + } }, } } @@ -38,61 +41,68 @@ pub fn run_spec_test(spec_path: &PathBuf) -> Result { Ok(res) } -fn step(script: &mut ScriptEnv, cmd: &CommandKind) -> Result<(), SpecTestError> { +fn unexpected_failure(e: ScriptError) -> Error { + if e.unsupported() { + Error::UnsupportedLucetc + } else { + Error::UnexpectedFailure(e.to_string()) + } +} + +fn step(script: &mut ScriptEnv, cmd: &CommandKind) -> Result<(), Error> { match cmd { CommandKind::Module { ref module, ref name, } => { + println!("module {:?}", name); let module = module.clone().into_vec(); - script.instantiate(module, name).map_err(|e| { - if e.unsupported() { - Error::from(e).context(SpecTestErrorKind::UnsupportedLucetc) - } else { - Error::from(e).context(SpecTestErrorKind::UnexpectedFailure) - } - })?; + script + .instantiate(&module, name) + .map_err(unexpected_failure)?; Ok(()) } CommandKind::AssertInvalid { ref module, .. } => { + println!("assert_invalid"); let module = module.clone().into_vec(); - match script.instantiate(module, &None) { + match script.instantiate(&module, &None) { Err(ScriptError::ValidationError(_)) => Ok(()), Ok(_) => { script.delete_last(); - Err(SpecTestErrorKind::UnexpectedSuccess)? + Err(Error::UnexpectedSuccess)? } - Err(e) => Err(Error::from(e).context(SpecTestErrorKind::UnexpectedFailure))?, + Err(e) => Err(unexpected_failure(e))?, } } CommandKind::AssertMalformed { ref module, .. } => { + println!("assert_malformed"); let module = module.clone().into_vec(); - match script.instantiate(module, &None) { - Err(ScriptError::DeserializeError(_)) | Err(ScriptError::ValidationError(_)) => { - Ok(()) - } - Ok(_) => Err(SpecTestErrorKind::UnexpectedSuccess)?, - Err(e) => Err(Error::from(e).context(SpecTestErrorKind::UnexpectedFailure))?, + match script.instantiate(&module, &None) { + Err(ScriptError::ValidationError(_)) => Ok(()), + Ok(_) => Err(Error::UnexpectedSuccess)?, + Err(e) => Err(unexpected_failure(e))?, } } CommandKind::AssertUninstantiable { module, .. } => { + println!("assert_uninstantiable"); let module = module.clone().into_vec(); - match script.instantiate(module, &None) { - Err(ScriptError::DeserializeError(_)) => Ok(()), // XXX This is probably the wrong ScriptError to look for - FIXME - Ok(_) => Err(SpecTestErrorKind::UnexpectedSuccess)?, - Err(e) => Err(Error::from(e).context(SpecTestErrorKind::UnexpectedFailure))?, + match script.instantiate(&module, &None) { + Err(ScriptError::InstantiateError(_)) => Ok(()), + Ok(_) => Err(Error::UnexpectedSuccess)?, + Err(e) => Err(unexpected_failure(e))?, } } CommandKind::AssertUnlinkable { module, .. } => { + println!("assert_unlinkable"); let module = module.clone().into_vec(); - match script.instantiate(module, &None) { - Err(ScriptError::CompileError(_)) => Ok(()), // XXX could other errors trigger this too? - Ok(_) => Err(SpecTestErrorKind::UnexpectedSuccess)?, - Err(e) => Err(Error::from(e).context(SpecTestErrorKind::UnexpectedFailure))?, + match script.instantiate(&module, &None) { + Err(ScriptError::ValidationError(_)) => Ok(()), + Ok(_) => Err(Error::UnexpectedSuccess)?, + Err(e) => Err(unexpected_failure(e))?, } } @@ -100,9 +110,8 @@ fn step(script: &mut ScriptEnv, cmd: &CommandKind) -> Result<(), SpecTestError> ref name, ref as_name, } => { - script - .register(name, as_name) - .context(SpecTestErrorKind::UnexpectedFailure)?; + println!("register {:?} {}", name, as_name); + script.register(name, as_name).map_err(unexpected_failure)?; Ok(()) } @@ -112,41 +121,53 @@ fn step(script: &mut ScriptEnv, cmd: &CommandKind) -> Result<(), SpecTestError> ref field, ref args, } => { + println!("invoke {:?} {} {:?}", module, field, args); let args = translate_args(args); let _res = script .run(module, field, args) - .context(SpecTestErrorKind::UnexpectedFailure)?; + .map_err(unexpected_failure)?; Ok(()) } - _ => Err(SpecTestErrorKind::UnsupportedCommand)?, + _ => { + let message = format!("invoke {:?}", action); + Err(Error::UnsupportedCommand(message))? + } }, - CommandKind::AssertExhaustion { ref action } => match action { + // TODO: verify the exhaustion message is what we expected + CommandKind::AssertExhaustion { + ref action, + message: _, + } => match action { Action::Invoke { ref module, ref field, ref args, } => { + println!("assert_exhaustion {:?} {} {:?}", module, field, args); let args = translate_args(args); let res = script.run(module, field, args); match res { - Ok(_) => Err(SpecTestErrorKind::UnexpectedSuccess)?, + Ok(_) => Err(Error::UnexpectedSuccess)?, Err(ScriptError::RuntimeError(RuntimeError::RuntimeFault(details))) => { - match details.trapcode.ty { - TrapCodeType::StackOverflow => Ok(()), - e => Err(format_err!( - "AssertExhaustion expects stack overflow, got {:?}", - e - ) - .context(SpecTestErrorKind::UnexpectedFailure))?, + match details.trapcode { + Some(TrapCode::StackOverflow) => Ok(()), + e => { + let message = + format!("AssertExhaustion expects stack overflow, got {:?}", e); + Err(Error::UnexpectedFailure(message))? + } } } - Err(e) => Err(Error::from(e).context(SpecTestErrorKind::UnexpectedFailure))?, + Err(e) => Err(unexpected_failure(e))?, } } - _ => Err(SpecTestErrorKind::UnsupportedCommand)?, + _ => { + let message = format!("invoke {:?}", action); + Err(Error::UnsupportedCommand(message))? + } }, CommandKind::AssertReturn { @@ -158,15 +179,18 @@ fn step(script: &mut ScriptEnv, cmd: &CommandKind) -> Result<(), SpecTestError> ref field, ref args, } => { + println!( + "assert_return (invoke {:?} {} {:?}) {:?}", + module, field, args, expected + ); let args = translate_args(args); let res = script .run(module, field, args) - .context(SpecTestErrorKind::UnexpectedFailure)?; + .map_err(unexpected_failure)?; check_retval(expected, res)?; Ok(()) } - _ => Err(format_err!("non-invoke action")) - .context(SpecTestErrorKind::UnsupportedCommand)?, + _ => Err(Error::UnsupportedCommand("non-invoke action".to_owned()))?, }, CommandKind::AssertReturnCanonicalNan { action } | CommandKind::AssertReturnArithmeticNan { action } => match action { @@ -175,41 +199,19 @@ fn step(script: &mut ScriptEnv, cmd: &CommandKind) -> Result<(), SpecTestError> ref field, ref args, } => { + println!("assert_nan"); let args = translate_args(args); let res = script .run(module, field, args) - .context(SpecTestErrorKind::UnexpectedFailure)?; - let res_type = script - .instance_named(module) - .expect("just used that instance") - .type_of(field) - .expect("field has type"); - match res_type { - ExportType::Function(_, Some(ValueType::F32)) => { - if res.as_f32().is_nan() { - Ok(()) - } else { - Err(format_err!("expected NaN, got {}", res.as_f32())) - .context(SpecTestErrorKind::IncorrectResult)? - } - } - ExportType::Function(_, Some(ValueType::F64)) => { - if res.as_f64().is_nan() { - Ok(()) - } else { - Err(format_err!("expected NaN, got {}", res.as_f64())) - .context(SpecTestErrorKind::IncorrectResult)? - } - } - _ => Err(format_err!( - "expected a function returning point, got {:?}", - res_type - )) - .context(SpecTestErrorKind::UnexpectedFailure)?, + .map_err(unexpected_failure)?; + if res.as_f32().is_nan() || res.as_f64().is_nan() { + Ok(()) + } else { + let message = format!("expected NaN, got {}", res); + Err(Error::IncorrectResult(message))? } } - _ => Err(format_err!("non-invoke action")) - .context(SpecTestErrorKind::UnsupportedCommand)?, + _ => Err(Error::UnsupportedCommand("non-invoke action".to_owned()))?, }, CommandKind::AssertTrap { ref action, .. } => match action { Action::Invoke { @@ -217,50 +219,60 @@ fn step(script: &mut ScriptEnv, cmd: &CommandKind) -> Result<(), SpecTestError> field, args, } => { + println!("assert_trap (invoke {:?} {} {:?})", module, field, args); let args = translate_args(args); let res = script.run(module, field, args); match res { Err(ScriptError::RuntimeError(_luceterror)) => Ok(()), - Err(e) => Err(Error::from(e).context(SpecTestErrorKind::UnexpectedFailure))?, - Ok(_) => Err(SpecTestErrorKind::UnexpectedSuccess)?, + Err(e) => Err(unexpected_failure(e)), + Ok(_) => Err(Error::UnexpectedSuccess)?, } } - _ => Err(SpecTestErrorKind::UnsupportedCommand)?, + _ => { + let message = format!("invoke {:?}", action); + Err(Error::UnsupportedCommand(message))? + } }, } } -fn check_retval(expected: &[Value], got: UntypedRetVal) -> Result<(), SpecTestError> { +fn check_retval(expected: &[Value], got: UntypedRetVal) -> Result<(), Error> { match expected.len() { 0 => {} 1 => match expected[0] { Value::I32(expected) => { if expected != got.as_i32() { - Err(format_err!("expected {}, got {}", expected, got.as_i32())) - .context(SpecTestErrorKind::IncorrectResult)? + let message = format!("expected {}, got {}", expected, got.as_i32()); + Err(Error::IncorrectResult(message))? } } Value::I64(expected) => { if expected != got.as_i64() { - Err(format_err!("expected {}, got {}", expected, got.as_i64())) - .context(SpecTestErrorKind::IncorrectResult)? + let message = format!("expected {}, got {}", expected, got.as_i64()); + Err(Error::IncorrectResult(message))? } } Value::F32(expected) => { if expected != got.as_f32() && !expected.is_nan() && !got.as_f32().is_nan() { - Err(format_err!("expected {}, got {}", expected, got.as_f32())) - .context(SpecTestErrorKind::IncorrectResult)? + let message = format!("expected {}, got {}", expected, got.as_f32()); + Err(Error::IncorrectResult(message))? } } Value::F64(expected) => { if expected != got.as_f64() && !expected.is_nan() && !got.as_f64().is_nan() { - Err(format_err!("expected {}, got {}", expected, got.as_f64())) - .context(SpecTestErrorKind::IncorrectResult)? + let message = format!("expected {}, got {}", expected, got.as_f64()); + Err(Error::IncorrectResult(message))? } } + Value::V128(v) => { + let message = format!("got unsupported SIMD V128 value: {}", v); + Err(Error::UnsupportedCommand(message))?; + } }, - n => Err(format_err!("{} expected return values not supported", n)) - .context(SpecTestErrorKind::UnsupportedCommand)?, + n => { + let message = format!("{} expected return values not supported", n); + Err(Error::UnsupportedCommand(message))? + } } Ok(()) } @@ -273,6 +285,7 @@ fn translate_args(args: &[Value]) -> Vec { Value::I64(ref i) => Val::U64(*i as u64), Value::F32(ref f) => Val::F32(*f), Value::F64(ref f) => Val::F64(*f), + Value::V128(_) => panic!("unsupported SIMD argument size: v128"), }; out.push(v); } diff --git a/lucet-spectest/src/main.rs b/lucet-spectest/src/main.rs index ff8a923c2..aaa99bff3 100644 --- a/lucet-spectest/src/main.rs +++ b/lucet-spectest/src/main.rs @@ -1,10 +1,13 @@ -use clap::{App, Arg}; -use failure::{format_err, Error}; -use lucet_spectest; +#[macro_use] +extern crate clap; + +use clap::Arg; +use lucet_spectest::Error; use std::path::PathBuf; fn main() -> Result<(), Error> { - let matches = App::new("lucet-spectest") + let _ = include_str!("../Cargo.toml"); + let matches = app_from_crate!() .arg( Arg::with_name("input") .multiple(false) @@ -18,7 +21,7 @@ fn main() -> Result<(), Error> { run.report(); if run.failed().len() > 0 { - Err(format_err!("{} failures", run.failed().len())) + Err(Error::RunError(run.failed().len())) } else { Ok(()) } diff --git a/lucet-spectest/src/result.rs b/lucet-spectest/src/result.rs index 9cf418c4a..f35d2470c 100644 --- a/lucet-spectest/src/result.rs +++ b/lucet-spectest/src/result.rs @@ -1,10 +1,10 @@ -use crate::error::SpecTestError; +use crate::error::Error; use wabt::script::{Command, CommandKind}; pub struct SpecScriptResult { pass: Vec, - skip: Vec<(Command, SpecTestError)>, - fail: Vec<(Command, SpecTestError)>, + skip: Vec<(Command, Error)>, + fail: Vec<(Command, Error)>, } impl SpecScriptResult { @@ -20,11 +20,11 @@ impl SpecScriptResult { self.pass.push(command.clone()) } - pub fn skip(&mut self, command: &Command, reason: SpecTestError) { + pub fn skip(&mut self, command: &Command, reason: Error) { self.skip.push((command.clone(), reason)) } - pub fn fail(&mut self, command: &Command, reason: SpecTestError) { + pub fn fail(&mut self, command: &Command, reason: Error) { self.fail.push((command.clone(), reason)) } @@ -32,11 +32,11 @@ impl SpecScriptResult { &self.pass } - pub fn skipped(&self) -> &[(Command, SpecTestError)] { + pub fn skipped(&self) -> &[(Command, Error)] { &self.skip } - pub fn failed(&self) -> &[(Command, SpecTestError)] { + pub fn failed(&self) -> &[(Command, Error)] { &self.fail } diff --git a/lucet-spectest/src/script.rs b/lucet-spectest/src/script.rs index 16821bdc8..a7b783de3 100644 --- a/lucet-spectest/src/script.rs +++ b/lucet-spectest/src/script.rs @@ -1,48 +1,43 @@ use crate::bindings; -use crate::instance::Instance; -use failure::{format_err, Error, Fail}; use lucet_runtime::{self, MmapRegion, Module as LucetModule, Region, UntypedRetVal, Val}; -use lucetc::{ - compile, - compiler::OptLevel, - error::{LucetcError, LucetcErrorKind}, - program::{HeapSettings, Program}, -}; -use parity_wasm::{self, deserialize_buffer}; +use lucetc::{Compiler, CpuFeatures, Error as LucetcError, HeapSettings, OptLevel}; use std::io; use std::process::Command; use std::sync::Arc; +use target_lexicon::Triple; +use thiserror::Error; -#[derive(Fail, Debug)] +#[derive(Debug, Error)] pub enum ScriptError { - #[fail(display = "Deserialization error: {}", _0)] - DeserializeError(parity_wasm::elements::Error), - #[fail(display = "Validation error: {}", _0)] - ValidationError(LucetcError), - #[fail(display = "Program creation error: {}", _0)] - ProgramError(LucetcError), - #[fail(display = "Compilation error: {}", _0)] - CompileError(LucetcError), - #[fail(display = "Codegen error: {}", _0)] - CodegenError(Error), - #[fail(display = "Load error: {}", _0)] - LoadError(lucet_runtime::Error), - #[fail(display = "Instaitiation error: {}", _0)] - InstantiateError(lucet_runtime::Error), - #[fail(display = "Runtime error: {}", _0)] - RuntimeError(lucet_runtime::Error), - #[fail(display = "Malformed script: {}", _0)] + #[error("Validation error")] + ValidationError(#[source] LucetcError), + #[error("Program error")] + ProgramError(#[source] LucetcError), + #[error("Compilation error")] + CompileError(#[source] LucetcError), + #[error("Codegen error")] + CodegenError(#[source] LucetcError), + #[error("Load error")] + LoadError(#[source] lucet_runtime::Error), + #[error("Instantiation error")] + InstantiateError(#[source] lucet_runtime::Error), + #[error("Runtime error")] + RuntimeError(#[source] lucet_runtime::Error), + #[error("Malformed script: {0}")] MalformedScript(String), - #[fail(display = "IO error: {}", _0)] - IoError(io::Error), + #[error("IO error")] + IoError(#[from] io::Error), + #[error("run_ld error: {0}")] + LdError(String), } impl ScriptError { pub fn unsupported(&self) -> bool { match self { ScriptError::ProgramError(ref lucetc_err) - | ScriptError::CompileError(ref lucetc_err) => match lucetc_err.get_context() { - &LucetcErrorKind::Unsupported(_) => true, + | ScriptError::ValidationError(ref lucetc_err) + | ScriptError::CompileError(ref lucetc_err) => match lucetc_err { + &LucetcError::Unsupported(_) => true, _ => false, }, _ => false, @@ -50,19 +45,13 @@ impl ScriptError { } } -impl From for ScriptError { - fn from(e: io::Error) -> ScriptError { - ScriptError::IoError(e) - } -} - pub struct ScriptEnv { - instances: Vec<(Option, Instance)>, + instances: Vec<(Option, lucet_runtime::InstanceHandle)>, } fn program_error(e: LucetcError) -> ScriptError { - match e.get_context() { - LucetcErrorKind::Validation => ScriptError::ValidationError(e), + match e { + LucetcError::WasmValidation(_) => ScriptError::ValidationError(e), _ => ScriptError::ProgramError(e), } } @@ -73,36 +62,29 @@ impl ScriptEnv { instances: Vec::new(), } } - pub fn instantiate( - &mut self, - module: Vec, - name: &Option, - ) -> Result<(), ScriptError> { + pub fn instantiate(&mut self, module: &[u8], name: &Option) -> Result<(), ScriptError> { let bindings = bindings::spec_test_bindings(); - - let module = deserialize_buffer(&module).map_err(ScriptError::DeserializeError)?; - - let program = - Program::new(module, bindings, HeapSettings::default()).map_err(program_error)?; + let compiler = Compiler::new( + module, + Triple::host(), + OptLevel::default(), + CpuFeatures::baseline(), + &bindings, + HeapSettings::default(), + true, + &None, + ) + .map_err(program_error)?; let dir = tempfile::Builder::new().prefix("codegen").tempdir()?; let objfile_path = dir.path().join("a.o"); let sofile_path = dir.path().join("a.so"); - { - let compiler = compile( - &program, - &name.clone().unwrap_or("default".to_owned()), - OptLevel::Default, - ) - .map_err(ScriptError::CompileError)?; - - let object = compiler.codegen().map_err(ScriptError::CodegenError)?; - - object - .write(&objfile_path) - .map_err(ScriptError::CodegenError)?; - } + compiler + .object_file() + .map_err(ScriptError::CompileError)? + .write(&objfile_path) + .map_err(ScriptError::CodegenError)?; let mut cmd_ld = Command::new("ld"); cmd_ld.arg(objfile_path.clone()); @@ -111,34 +93,39 @@ impl ScriptEnv { cmd_ld.arg(sofile_path.clone()); let run_ld = cmd_ld.output()?; if !run_ld.status.success() { - Err(ScriptError::CodegenError(format_err!( + let message = format!( "ld {:?}: {}", objfile_path, String::from_utf8_lossy(&run_ld.stderr) - )))?; + ); + + Err(ScriptError::LdError(message))?; } let lucet_module: Arc = lucet_runtime::DlModule::load(sofile_path).map_err(ScriptError::LoadError)?; - let lucet_region = - MmapRegion::create(1, &lucet_runtime::Limits::default()).expect("valid region"); + let lucet_region = MmapRegion::create( + 1, + &lucet_runtime::Limits { + heap_memory_size: 4 * 1024 * 1024 * 1024, + ..lucet_runtime::Limits::default() + }, + ) + .expect("valid region"); let lucet_instance = lucet_region .new_instance(lucet_module.clone()) .map_err(ScriptError::InstantiateError)?; - self.instances.push(( - name.clone(), - Instance::new(program, lucet_module, lucet_region, lucet_instance), - )); + self.instances.push((name.clone(), lucet_instance)); Ok(()) } fn instance_named_mut( &mut self, name: &Option, - ) -> Result<&mut (Option, Instance), ScriptError> { + ) -> Result<&mut (Option, lucet_runtime::InstanceHandle), ScriptError> { Ok(match name { // None means the last defined module should be used None => self @@ -153,7 +140,10 @@ impl ScriptEnv { }) } - pub fn instance_named(&self, name: &Option) -> Result<&Instance, ScriptError> { + pub fn instance_named( + &self, + name: &Option, + ) -> Result<&lucet_runtime::InstanceHandle, ScriptError> { Ok(match name { // None means the last defined module should be used None => self @@ -177,8 +167,9 @@ impl ScriptEnv { args: Vec, ) -> Result { let (_, ref mut inst) = self.instance_named_mut(name)?; - inst.run(&field, &args) - .map_err(|e| ScriptError::RuntimeError(e)) + inst.run(field, &args) + .and_then(|rr| rr.returned()) + .map_err(ScriptError::RuntimeError) } pub fn register(&mut self, name: &Option, as_name: &str) -> Result<(), ScriptError> { diff --git a/lucet-spectest/tests/wasm-spec.rs b/lucet-spectest/tests/wasm-spec.rs index d4a49baf2..51410a729 100644 --- a/lucet-spectest/tests/wasm-spec.rs +++ b/lucet-spectest/tests/wasm-spec.rs @@ -35,7 +35,7 @@ core_spec_test!(break_drop, "break-drop"); // PASS core_spec_test!(br_if); // PASS core_spec_test!(br_table); // PASS core_spec_test!(br); // PASS -core_spec_test!(call_indirect); // FAIL: BadSignature runtime error in AssertReturn +core_spec_test!(call_indirect); // PASS core_spec_test!(call); // PASS core_spec_test!(comments); // PASS core_spec_test!(const_, "const"); // PASS @@ -57,14 +57,16 @@ core_spec_test!(float_literals); // PASS core_spec_test!(float_memory); // PASS core_spec_test!(float_misc); // PASS core_spec_test!(forward); // PASS -core_spec_test!(func_ptrs); // FAIL: dlopen error, need print_i32 etc -core_spec_test!(func); // FAIL: in BadSignature runtime error AssertReturn +core_spec_test!(func_ptrs); // PASS +core_spec_test!(func); // PASS core_spec_test!(get_local); // PASS core_spec_test!(globals); // FAIL: exports mutable globals, which wabt does not support core_spec_test!(i32_, "i32"); // PASS core_spec_test!(i64_, "i64"); // PASS core_spec_test!(if_, "if"); // PASS -core_spec_test!(imports); // FAIL: lots of unexpected success or incorrect results, some BadSignature faults, some "symbol not found" errors indicating test harness isnt correct + // currently stops at 'creation of elements for undeclared table!', which is actually due to an elem section populating an imported table. + // past that, lots of unexpected success or incorrect results, some BadSignature faults, some "symbol not found" errors indicating test harness isnt correct. +core_spec_test!(imports); // FAIL: see above comment core_spec_test!(inline_module, "inline-module"); // PASS core_spec_test!(int_exprs); // PASS core_spec_test!(int_literals); // PASS @@ -72,18 +74,19 @@ core_spec_test!(labels); // PASS core_spec_test!(left_to_right, "left-to-right"); // PASS core_spec_test!(linking); // FAIL: exports mutable globals core_spec_test!(loop_, "loop"); // PASS -core_spec_test!(memory_grow); // FAIL but i think its because a test asked for 4gb of memory? could increase memory limit in test harness. +core_spec_test!(memory_grow); // PASS core_spec_test!(memory_redundancy); // PASS -core_spec_test!(memory_trap); // FAIL incorrect result -core_spec_test!(memory); // FAIL panic related to heap guard -core_spec_test!(names); // FAIL lots of symbol not found errors +core_spec_test!(memory_trap); // PASS +core_spec_test!(memory); // PASS + // too noisy to keep enabled: + // core_spec_test!(names); // FAIL hundreds of errors because we dont support unicode names yet. core_spec_test!(nop); // PASS core_spec_test!(return_, "return"); // PASS core_spec_test!(select); // PASS core_spec_test!(set_local); // PASS -core_spec_test!(skip_stack_guard_page, "skip-stack-guard-page"); // PASS but takes over 1 minute +core_spec_test!(skip_stack_guard_page, "skip-stack-guard-page"); // PASS but takes over 1 minute in cranelift building function-with-many-locals core_spec_test!(stack); // PASS -core_spec_test!(start); // FAIL we dont support start functions yet, so results are incorrect +core_spec_test!(start); // PASS core_spec_test!(store_retval); // PASS core_spec_test!(switch); // PASS core_spec_test!(tee_local); // PASS diff --git a/lucet-validate/Cargo.toml b/lucet-validate/Cargo.toml new file mode 100644 index 000000000..35be8670d --- /dev/null +++ b/lucet-validate/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "lucet-validate" +version = "0.5.0" +description = "Parse and validate webassembly files against witx interface" +homepage = "https://github.com/fastly/lucet" +repository = "https://github.com/fastly/lucet" +license = "Apache-2.0 WITH LLVM-exception" +categories = ["wasm"] +authors = ["Pat Hickey "] +edition = "2018" + +[lib] +crate-type=["rlib"] + +[[bin]] +name = "lucet-validate" +path = "src/main.rs" + +[dependencies] +clap = "2" +witx = { path = "../wasi/tools/witx", version = "0.6.0" } +cranelift-entity = { path = "../cranelift/cranelift-entity", version = "0.51.0" } +thiserror = "1.0.4" +wasmparser = "0.39.1" + +[dev-dependencies] +lucet-wasi-sdk = { path = "../lucet-wasi-sdk", version = "=0.5.0" } +tempfile = "3.0" +wabt = "0.9.2" + +[package.metadata.deb] +name = "fst-lucet-validate" +maintainer = "Lucet team " +depends = "$auto" +priority = "optional" +assets = [ + ["target/release/lucet-validate", "/opt/fst-lucet-validate/bin/lucet-validate", "755"], + ["target/release/liblucet_validate.rlib", "/opt/fst-lucet-validate/lib/", "644"], + ["LICENSE", "/opt/fst-lucet-validate/share/doc/lucet-validate/", "644"], + ["../wasi/phases/old/snapshot_0/witx/typenames.witx", + "/opt/fst-lucet-validate/share/wasi/snapshot_0/typenames.witx", "644"], + ["../wasi/phases/old/snapshot_0/witx/wasi_unstable.witx", + "/opt/fst-lucet-validate/share/wasi/snapshot_0/wasi_unstable.witx", "644"], +] diff --git a/lucet-idl/LICENSE b/lucet-validate/LICENSE similarity index 99% rename from lucet-idl/LICENSE rename to lucet-validate/LICENSE index f9d81955f..be1d7c438 100644 --- a/lucet-idl/LICENSE +++ b/lucet-validate/LICENSE @@ -217,4 +217,3 @@ 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. - diff --git a/lucet-validate/README.md b/lucet-validate/README.md new file mode 100644 index 000000000..42b260ca2 --- /dev/null +++ b/lucet-validate/README.md @@ -0,0 +1,50 @@ +# lucet-validate + +Validates a WebAssembly module against a witx spec. + +## What is witx? + +* Witx is a specification languaged developed as part of the + [WASI](https://github.com/WebAssembly/WASI) effort. The `witx` crate lives in + that repository, as well as `.witx` files that describe the WASI standard. + +* A Witx specification is parsed and validated from `.witx` files by the `witx` + crate. The set of types and modules defined by these files is called a Witx document. + +* A Witx specification contains modules, and modules contain interface functions. + These are functions defined in terms of parameters (inputs) and results + (outputs), all of which can have complex types like pointers, arrays, strings, + structs etc. + +* Each interface function has a method to calculate its type signature in terms + of the "core" WebAssembly types (i32, i64, f32, f64 used in WebAssembly 1.0 + function types). This calculation takes into account that some complex types + are passed as pointers into linear memory, or a pointer-length pair, while + others (smaller ints like u8 or s16) can be represented by atomic values + (i32, in this example). + + +## What is validated? + +* The WebAssembly module itself is validated to be WebAssembly 1.0. We don't + support validating extensions to the spec yet but ought to be able to without + any issues. + +* Each import of the WebAssembly module is validated to be present, and have + the expected core type signature, given by the Witx document. + +* If the Validator is set to validate an `wasi-exe`, it additionally checks + that the module exports a function named `_start` with the type signature `[] + -> ()`. (This is not to be confused with having a `start` section, which is a + different concept from the WASI executable entrypoint `_start`.) + + +## What is not? + +* The WebAssembly module does not contain enough information to determine that + the core types found in the type signature are used by the WebAssembly + program in a way that matches the complex types (strings arrays structs etc) + in the witx document. This property could only be validated in the source + language before it is compiled to WebAssembly. + + diff --git a/lucet-validate/src/lib.rs b/lucet-validate/src/lib.rs new file mode 100644 index 000000000..03412fc69 --- /dev/null +++ b/lucet-validate/src/lib.rs @@ -0,0 +1,141 @@ +mod moduletype; +mod types; + +use std::path::Path; +use std::rc::Rc; +use thiserror::Error; +use wasmparser; +use witx::{self, Id, Module}; + +pub use self::moduletype::ModuleType; +pub use self::types::{FuncSignature, ImportFunc}; +pub use witx::{AtomType, Document, WitxError}; + +#[derive(Debug, Error)] +pub enum Error { + #[error("WebAssembly validation error at offset {1}: {0}")] + WasmValidation(&'static str, usize), + #[error("Unsupported: {0}")] + Unsupported(String), + #[error("Module not found: {0}")] + ModuleNotFound(String), + #[error("Import not found: {module}::{field}")] + ImportNotFound { module: String, field: String }, + #[error("Export not found: {field}")] + ExportNotFound { field: String }, + #[error("Import type error: for {module}::{field}, expected {expected:?}, got {got:?}")] + ImportTypeError { + module: String, + field: String, + expected: FuncSignature, + got: FuncSignature, + }, + #[error("Export type error: for {field}, expected {expected:?}, got {got:?}")] + ExportTypeError { + field: String, + expected: FuncSignature, + got: FuncSignature, + }, +} + +impl From for Error { + fn from(e: wasmparser::BinaryReaderError) -> Error { + Error::WasmValidation(e.message, e.offset) + } +} + +pub struct Validator { + witx: Document, + wasi_exe: bool, +} + +impl Validator { + pub fn new(witx: Document, wasi_exe: bool) -> Self { + Self { witx, wasi_exe } + } + + pub fn parse(source: &str) -> Result { + let witx = witx::parse(source)?; + Ok(Self { + witx, + wasi_exe: false, + }) + } + + pub fn load>(source_path: P) -> Result { + let witx = witx::load(&[source_path.as_ref()])?; + Ok(Self { + witx, + wasi_exe: false, + }) + } + + pub fn wasi_exe(&mut self, check: bool) { + self.wasi_exe = check; + } + + pub fn with_wasi_exe(mut self, check: bool) -> Self { + self.wasi_exe(check); + self + } + + pub fn validate(&self, module_contents: &[u8]) -> Result<(), Error> { + wasmparser::validate(module_contents, None)?; + + let moduletype = ModuleType::parse_wasm(module_contents)?; + + for import in moduletype.imports() { + let func = self + .witx_module(&import.module)? + .func(&Id::new(&import.field)) + .ok_or_else(|| Error::ImportNotFound { + module: import.module.clone(), + field: import.field.clone(), + })?; + let spec_type = FuncSignature::from(func.core_type()); + if spec_type != import.ty { + Err(Error::ImportTypeError { + module: import.module, + field: import.field, + got: import.ty, + expected: spec_type, + })?; + } + } + + if self.wasi_exe { + self.check_wasi_start_func(&moduletype)?; + } + + Ok(()) + } + + fn witx_module(&self, module: &str) -> Result, Error> { + self.witx + .module(&Id::new(module)) + .ok_or_else(|| Error::ModuleNotFound(module.to_string())) + } + + fn check_wasi_start_func(&self, moduletype: &ModuleType) -> Result<(), Error> { + let start_name = "_start"; + let expected = FuncSignature { + args: vec![], + ret: None, + }; + if let Some(startfunc) = moduletype.export(start_name) { + if startfunc != &expected { + Err(Error::ExportTypeError { + field: start_name.to_string(), + expected, + got: startfunc.clone(), + }) + } else { + Ok(()) + } + } else { + Err(Error::ExportNotFound { + field: start_name.to_string(), + }) + } + } +} diff --git a/lucet-validate/src/main.rs b/lucet-validate/src/main.rs new file mode 100644 index 000000000..634e573df --- /dev/null +++ b/lucet-validate/src/main.rs @@ -0,0 +1,98 @@ +#[macro_use] +extern crate clap; +use clap::Arg; +use lucet_validate::{self, Validator}; +use std::fs::File; +use std::io::{self, Read}; +use std::path::{Path, PathBuf}; +use std::process; +use thiserror::Error; +use witx; + +pub fn main() { + // rebuild if env vars used by app_from_crate! change: + let _ = include_str!("../Cargo.toml"); + + let matches = app_from_crate!() + .arg( + Arg::with_name("module") + .takes_value(true) + .required(true) + .help("WebAssembly module"), + ) + .arg( + Arg::with_name("witx") + .takes_value(true) + .required(true) + .help("validate against interface in this witx file"), + ) + .arg( + Arg::with_name("wasi-exe") + .takes_value(false) + .required(false) + .short("w") + .long("wasi-exe") + .help("validate exports of WASI executable"), + ) + .arg( + Arg::with_name("verbose") + .short("v") + .takes_value(false) + .required(false), + ) + .get_matches(); + + let module_path = matches + .value_of("module") + .map(Path::new) + .expect("module arg required"); + + match run( + &module_path, + Path::new(matches.value_of("witx").expect("witx path required")), + matches.is_present("wasi-exe"), + ) { + Ok(()) => { + if matches.is_present("verbose") { + println!("validated successfully") + } + } + Err(e) => { + if matches.is_present("verbose") { + match e { + Error::Witx(e) => { + println!("{}", e.report()); + } + _ => { + println!("{:?}", e); + } + } + } else { + println!("{}", e); + } + process::exit(-1); + } + } +} + +fn run(module_path: &Path, witx_path: &Path, wasi_exe: bool) -> Result<(), Error> { + let mut module_contents = Vec::new(); + let mut file = File::open(module_path).map_err(|e| Error::Io(module_path.into(), e))?; + file.read_to_end(&mut module_contents) + .map_err(|e| Error::Io(module_path.into(), e))?; + + let validator = Validator::load(witx_path)?.with_wasi_exe(wasi_exe); + validator.validate(&module_contents)?; + + Ok(()) +} + +#[derive(Debug, Error)] +enum Error { + #[error("Witx error")] + Witx(#[from] witx::WitxError), + #[error("With file {0:?}: {1}")] + Io(PathBuf, #[source] io::Error), + #[error("Validate error")] + Validate(#[from] lucet_validate::Error), +} diff --git a/lucet-validate/src/moduletype.rs b/lucet-validate/src/moduletype.rs new file mode 100644 index 000000000..e9d216547 --- /dev/null +++ b/lucet-validate/src/moduletype.rs @@ -0,0 +1,157 @@ +use crate::{AtomType, Error, FuncSignature, ImportFunc}; +use cranelift_entity::{entity_impl, PrimaryMap}; +use std::collections::HashMap; +use wasmparser::{ + ExternalKind, FuncType, ImportSectionEntryType, ModuleReader, SectionContent, Type as WType, +}; + +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] +struct TypeIndex(u32); +entity_impl!(TypeIndex); + +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] +struct FuncIndex(u32); +entity_impl!(FuncIndex); + +#[derive(Clone)] +struct Func { + pub ty: TypeIndex, + pub import: Option<(String, String)>, +} + +#[derive(Clone)] +pub struct ModuleType { + types: PrimaryMap, + funcs: PrimaryMap, + exports: HashMap, +} + +impl ModuleType { + pub fn imports(&self) -> Vec { + self.funcs + .iter() + .filter_map(|(_, f)| { + f.import.clone().map(|(module, field)| ImportFunc { + module, + field, + ty: self.types.get(f.ty).expect("get type").clone(), + }) + }) + .collect() + } + + pub fn export(&self, name: &str) -> Option<&FuncSignature> { + self.exports.get(name).map(|funcix| { + let func = self.funcs.get(*funcix).expect("valid funcix"); + self.types.get(func.ty).expect("valid typeix") + }) + } + + pub fn parse_wasm(module_contents: &[u8]) -> Result { + let mut module = ModuleType { + types: PrimaryMap::new(), + funcs: PrimaryMap::new(), + exports: HashMap::new(), + }; + + let mut module_reader = ModuleReader::new(module_contents)?; + while !module_reader.eof() { + let section = module_reader.read()?; + match section.content()? { + SectionContent::Type(types) => { + for entry in types { + match entry? { + FuncType { + form: WType::Func, + params, + returns, + } => { + let ret = match returns.len() { + 0 => None, + 1 => Some(wasmparser_to_atomtype(&returns[0])?), + _ => Err(Error::Unsupported(format!( + "more than 1 return value: {:?}", + returns, + )))?, + }; + let args = params + .iter() + .map(|a| wasmparser_to_atomtype(a)) + .collect::, _>>()?; + module.types.push(FuncSignature { args, ret }); + } + _ => Err(Error::Unsupported("type section entry".to_string()))?, + } + } + } + SectionContent::Import(imports) => { + for import in imports { + let import = import?; + match import.ty { + ImportSectionEntryType::Function(ftype) => { + module.funcs.push(Func { + ty: TypeIndex::from_u32(ftype), + import: Some(( + import.module.to_owned(), + import.field.to_owned(), + )), + }); + } + ImportSectionEntryType::Memory(_) => { + Err(Error::Unsupported(format!( + "memory import {}:{}", + import.module, import.field + )))?; + } + ImportSectionEntryType::Table(_) => { + Err(Error::Unsupported(format!( + "table import {}:{}", + import.module, import.field + )))?; + } + ImportSectionEntryType::Global(_) => { + Err(Error::Unsupported(format!( + "global import {}:{}", + import.module, import.field + )))?; + } + } + } + } + SectionContent::Export(exports) => { + for export in exports { + let export = export?; + match export.kind { + ExternalKind::Function => { + module.exports.insert( + export.field.to_string(), + FuncIndex::from_u32(export.index), + ); + } + _ => {} // Dont care about other exports + } + } + } + SectionContent::Function(functions) => { + for function_ty in functions { + let ty = TypeIndex::from_u32(function_ty?); + module.funcs.push(Func { ty, import: None }); + } + } + _ => {} // Dont care about other sections + } + } + + Ok(module) + } +} + +fn wasmparser_to_atomtype(a: &WType) -> Result { + match a { + WType::I32 => Ok(AtomType::I32), + WType::I64 => Ok(AtomType::I64), + WType::F32 => Ok(AtomType::F32), + WType::F64 => Ok(AtomType::F64), + _ => Err(Error::Unsupported(format!("wasmparser type {:?}", a))), + } +} diff --git a/lucet-validate/src/types.rs b/lucet-validate/src/types.rs new file mode 100644 index 000000000..426974e45 --- /dev/null +++ b/lucet-validate/src/types.rs @@ -0,0 +1,24 @@ +use crate::AtomType; +use witx::CoreFuncType; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct FuncSignature { + pub args: Vec, + pub ret: Option, +} + +impl From for FuncSignature { + fn from(m: CoreFuncType) -> FuncSignature { + FuncSignature { + args: m.args.iter().map(|a| a.repr()).collect(), + ret: m.ret.map(|r| r.repr()), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ImportFunc { + pub module: String, + pub field: String, + pub ty: FuncSignature, +} diff --git a/lucet-validate/tests/wasitests.rs b/lucet-validate/tests/wasitests.rs new file mode 100644 index 000000000..82d1991b9 --- /dev/null +++ b/lucet-validate/tests/wasitests.rs @@ -0,0 +1,63 @@ +#[cfg(test)] +mod lucet_validate_tests { + use lucet_validate::Validator; + use std::fs; + use std::path::Path; + use wabt; + + fn c_to_wasm(c_path: &Path) -> Vec { + use lucet_wasi_sdk::Link; + use std::fs::File; + use std::io::Read; + use tempfile::TempDir; + + let tmp = TempDir::new().expect("create temporary directory"); + + let wasmfile = tmp.path().join("out.wasm"); + let linker = Link::new(&[c_path]); + linker.link(wasmfile.clone()).expect("link out.wasm"); + + let mut module_contents = Vec::new(); + let mut file = File::open(wasmfile).expect("open out.wasm"); + file.read_to_end(&mut module_contents) + .expect("read out.wasm"); + + module_contents + } + + fn wat_to_wasm(wat_path: &Path) -> Vec { + use std::fs::File; + use std::io::Read; + + let mut wat_contents = Vec::new(); + let mut file = File::open(wat_path).expect("open wat"); + file.read_to_end(&mut wat_contents).expect("read wat"); + wabt::wat2wasm(wat_contents).expect("wat2wasm") + } + + #[test] + fn validate_lucet_wasi_test_guests() { + let validator = Validator::load("../wasi/phases/old/snapshot_0/witx/wasi_unstable.witx") + .expect("load wasi_unstable_preview0"); + + for entry in + fs::read_dir("../lucet-wasi/tests/guests").expect("read lucet_wasi test guests dir") + { + let entry_path = entry.expect("file from lucet_wasi test guests dir").path(); + let entry_wasm = match entry_path + .extension() + .map(|s| s.to_str().expect("extension is str")) + { + Some("c") => c_to_wasm(&entry_path), + Some("wat") => wat_to_wasm(&entry_path), + _ => { + eprintln!("unsupported extension: {:?}", entry_path); + continue; + } + }; + validator + .validate(&entry_wasm) + .expect(&format!("validate {:?}", entry_path)); + } + } +} diff --git a/lucet-wasi-fuzz/Cargo.toml b/lucet-wasi-fuzz/Cargo.toml new file mode 100644 index 000000000..1156ff7eb --- /dev/null +++ b/lucet-wasi-fuzz/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "lucet-wasi-fuzz" +version = "0.5.0" +description = "Test the Lucet toolchain against native code execution using Csmith" +homepage = "https://github.com/fastly/lucet" +repository = "https://github.com/fastly/lucet" +license = "Apache-2.0 WITH LLVM-exception" +categories = ["wasm"] +authors = ["Lucet team "] +edition = "2018" + +[dependencies] +anyhow = "1" +clap = "2.32" +libc = "0.2.65" +lucetc = { path = "../lucetc" } +lucet-runtime = { path = "../lucet-runtime" } +lucet-module = { path = "../lucet-module" } +lucet-wasi = { path = "../lucet-wasi" } +lucet-wasi-sdk = { path = "../lucet-wasi-sdk" } +nix = "0.15" +num_cpus = "1.10" +progress = "0.2" +rand = "0.6" +regex = "1.1" +rayon = "1.0" +structopt = "0.3.3" +tempfile = "3.0" +wait-timeout = "0.2" diff --git a/lucet-wasi-fuzz/src/main.rs b/lucet-wasi-fuzz/src/main.rs new file mode 100644 index 000000000..1613e37df --- /dev/null +++ b/lucet-wasi-fuzz/src/main.rs @@ -0,0 +1,443 @@ +#![deny(bare_trait_objects)] + +use anyhow::{bail, format_err, Error}; +use libc::c_ulong; +use lucet_module::bindings::Bindings; +use lucet_runtime::{DlModule, Limits, MmapRegion, Module, Region}; +use lucet_wasi::{WasiCtx, WasiCtxBuilder, __wasi_exitcode_t}; +use lucet_wasi_sdk::{CompileOpts, Link}; +use lucetc::{Lucetc, LucetcOpts}; +use rand::prelude::random; +use rayon::prelude::*; +use regex::Regex; +use std::fs::File; +use std::io::{Read, Write}; +use std::os::unix::prelude::{FromRawFd, IntoRawFd, OpenOptionsExt}; +use std::path::{Path, PathBuf}; +use std::process::{exit, Command, Stdio}; +use std::sync::{Arc, Mutex}; +use std::time::Duration; +use structopt::StructOpt; +use tempfile::TempDir; +use wait_timeout::ChildExt; + +const LUCET_WASI_FUZZ_ROOT: &'static str = env!("CARGO_MANIFEST_DIR"); + +type Seed = c_ulong; + +#[derive(StructOpt, Debug)] +#[structopt(name = "lucet-wasi-fuzz")] +enum Config { + /// Test the Lucet toolchain against native code execution using Csmith + #[structopt(name = "fuzz")] + Fuzz { + #[structopt(short = "n", long = "num-tests", default_value = "100")] + /// The number of tests to run + num_tests: usize, + }, + + /// Reduce a test case, starting from the given Csmith seed + #[structopt(name = "creduce")] + Creduce { seed: Seed }, + + /// Creduce interestingness check (probably not useful directly) + #[structopt(name = "creduce-interesting")] + CreduceInteresting { creduce_src: PathBuf }, + + /// Run a test case with the given Csmith seed + #[structopt(name = "test-seed")] + TestSeed { seed: Seed }, +} + +fn main() { + lucet_runtime::lucet_internal_ensure_linked(); + lucet_wasi::export_wasi_funcs(); + + match Config::from_args() { + Config::Fuzz { num_tests } => run_many(num_tests), + Config::Creduce { seed } => run_creduce_driver(seed), + Config::CreduceInteresting { creduce_src } => run_creduce_interestingness(creduce_src), + Config::TestSeed { seed } => run_one_seed(seed), + } +} + +fn run_creduce_driver(seed: Seed) { + let tmpdir = TempDir::new().unwrap(); + + // make the driver script + + let mut script = std::fs::OpenOptions::new() + .create(true) + .write(true) + .mode(0o777) + .open(tmpdir.path().join("script.sh")) + .unwrap(); + + let current_exe = std::env::current_exe().unwrap(); + + write!( + script, + "{}", + format!( + "#!/usr/bin/env sh\n{} creduce-interesting gen.c", + current_exe.display() + ), + ) + .unwrap(); + + drop(script); + + // reproduce the generated program, and then preprocess it + + let st = Command::new("csmith") + .arg("-s") + .arg(format!("{}", seed)) + .arg("-o") + .arg(tmpdir.path().join("gen-original.c")) + .status() + .unwrap(); + assert!(st.success()); + + let st = Command::new(host_clang()) + .arg("-I/usr/include/csmith") + .arg("-m32") + .arg("-E") + .arg("-P") + .arg(tmpdir.path().join("gen-original.c")) + .arg("-o") + .arg(tmpdir.path().join("gen.c")) + .status() + .unwrap(); + assert!(st.success()); + + let st = Command::new("creduce") + .current_dir(tmpdir.path()) + .arg("--n") + .arg(format!("{}", std::cmp::max(1, num_cpus::get() - 1))) + .arg("script.sh") + .arg("gen.c") + .status() + .unwrap(); + assert!(st.success()); + + print!( + "{}", + std::fs::read_to_string(tmpdir.path().join("gen.c")).unwrap() + ); +} + +fn run_creduce_interestingness>(src: P) { + let tmpdir = TempDir::new().unwrap(); + + match run_both(&tmpdir, src, None) { + Ok(TestResult::Passed) => println!("test passed"), + Ok(TestResult::Ignored) => println!("native build/execution failed"), + Ok(TestResult::Failed { + expected, actual, .. + }) => { + println!("test failed:\n"); + let expected = String::from_utf8_lossy(&expected); + let actual = String::from_utf8_lossy(&actual); + println!("native: {}", &expected); + println!("lucet-wasi: {}", &actual); + + let re = Regex::new(r"^checksum = ([[:xdigit:]]{8})").unwrap(); + + // a coarse way to stop creduce from producing degenerate cases that happen to yield + // different output + + let expected_checksum = if let Some(caps) = re.captures(&expected) { + if let Some(cap) = caps.get(1) { + cap.as_str().to_owned() + } else { + // not interesting: no checksum captured from native output + exit(1); + } + } else { + // not interesting: no checksum captured from native output + exit(1); + }; + + let actual_checksum = if let Some(caps) = re.captures(&actual) { + if let Some(cap) = caps.get(1) { + cap.as_str().to_owned() + } else { + // interesting: checksum captured from native output but not wasm + exit(0) + } + } else { + // interesting: checksum captured from native output but not wasm + exit(0) + }; + + if expected_checksum == actual_checksum { + // they match; not interesting + exit(1); + } else { + exit(0); + } + } + Ok(TestResult::Errored { error }) | Err(error) => println!("test errored: {}", error), + } + exit(1); +} + +fn run_one_seed(seed: Seed) { + match run_one(Some(seed)) { + Ok(TestResult::Passed) => println!("test passed"), + Ok(TestResult::Ignored) => println!("native build/execution failed"), + Ok(TestResult::Failed { + expected, actual, .. + }) => { + println!("test failed:\n"); + println!("native: {}", String::from_utf8_lossy(&expected)); + println!("lucet-wasi: {}", String::from_utf8_lossy(&actual)); + exit(1); + } + Ok(TestResult::Errored { error }) | Err(error) => { + println!("test errored: {}", error); + exit(1); + } + } +} + +fn run_many(num_tests: usize) { + let mut progress = progress::Bar::new(); + progress.set_job_title(&format!("Running {} tests", num_tests)); + + let progress = Arc::new(Mutex::new(progress)); + let num_finished = Arc::new(Mutex::new(0)); + + let res = (0..num_tests) + .into_par_iter() + .try_for_each(|_| match run_one(None) { + Ok(TestResult::Passed) | Ok(TestResult::Ignored) => { + let mut num_finished = num_finished.lock().unwrap(); + *num_finished += 1; + let percentage = (*num_finished as f32 / num_tests as f32) * 100.0; + progress + .lock() + .unwrap() + .reach_percent(percentage.floor() as i32); + Ok(()) + } + Ok(fail) => Err(fail), + Err(error) => Err(TestResult::Errored { error }), + }); + + progress.lock().unwrap().jobs_done(); + + match res { + Err(TestResult::Failed { + seed, + expected, + actual, + }) => { + println!("test failed with seed {}\n", seed.unwrap()); + println!("native: {}", String::from_utf8_lossy(&expected)); + println!("lucet-wasi: {}", String::from_utf8_lossy(&actual)); + exit(1); + } + Err(TestResult::Errored { error }) => println!("test errored: {}", error), + Err(_) => unreachable!(), + Ok(()) => println!("all tests passed"), + } +} + +fn gen_c>(gen_c_path: P, seed: Seed) -> Result<(), Error> { + Command::new("csmith") + .arg("-s") + .arg(format!("{}", seed)) + .arg("-o") + .arg(gen_c_path.as_ref()) + .status()?; + Ok(()) +} + +fn run_both>( + tmpdir: &TempDir, + src: P, + seed: Option, +) -> Result { + let native_stdout = if let Some(stdout) = run_native(&tmpdir, src.as_ref())? { + stdout + } else { + return Ok(TestResult::Ignored); + }; + + let (exitcode, wasm_stdout) = run_with_stdout(&tmpdir, src.as_ref())?; + + assert_eq!(exitcode, 0); + + if &wasm_stdout != &native_stdout { + Ok(TestResult::Failed { + seed, + expected: native_stdout, + actual: wasm_stdout, + }) + } else { + Ok(TestResult::Passed) + } +} + +fn run_native>(tmpdir: &TempDir, gen_c_path: P) -> Result>, Error> { + let gen_path = tmpdir.path().join("gen"); + + let mut cmd = Command::new(host_clang()); + cmd.arg("-m32") + .arg("-std=c11") + .arg("-Werror=format") + .arg("-Werror=uninitialized") + .arg("-Werror=conditional-uninitialized") + .arg("-I/usr/include/csmith") + .arg(gen_c_path.as_ref()) + .arg("-o") + .arg(&gen_path); + if let Ok(flags) = std::env::var("HOST_CLANG_FLAGS") { + cmd.args(flags.split_whitespace()); + } + let res = cmd.output()?; + + if !res.status.success() { + bail!( + "native C compilation failed: {}", + String::from_utf8_lossy(&res.stderr) + ); + } + + if String::from_utf8_lossy(&res.stderr).contains("too few arguments in call") { + bail!("saw \"too few arguments in call\" warning"); + } + + let mut native_child = Command::new(&gen_path).stdout(Stdio::piped()).spawn()?; + + let exitcode = match native_child.wait_timeout(Duration::from_millis(1000))? { + Some(status) => status.code(), + None => { + native_child.kill()?; + native_child.wait()?.code() + } + }; + + match exitcode { + None => { + // native code diverged or took too long, so was killed by the timeout + return Ok(None); + } + Some(0) => (), + Some(code) => { + println!("native code returned non-zero exit code: {}", code); + return Ok(None); + } + } + + let mut native_stdout = vec![]; + native_child + .stdout + .ok_or(format_err!("couldn't get stdout"))? + .read_to_end(&mut native_stdout)?; + + Ok(Some(native_stdout)) +} + +enum TestResult { + Passed, + Ignored, + Failed { + seed: Option, + expected: Vec, + actual: Vec, + }, + Errored { + error: Error, + }, +} + +fn run_one(seed: Option) -> Result { + let tmpdir = TempDir::new().unwrap(); + + let gen_c_path = tmpdir.path().join("gen.c"); + + let seed = seed.unwrap_or(random::()); + gen_c(&gen_c_path, seed)?; + + run_both(&tmpdir, &gen_c_path, Some(seed)) +} + +fn run_with_stdout>( + tmpdir: &TempDir, + path: P, +) -> Result<(__wasi_exitcode_t, Vec), Error> { + let ctx = WasiCtxBuilder::new().args(&["gen"]); + + let (pipe_out, pipe_in) = nix::unistd::pipe()?; + + let ctx = ctx.stdout(unsafe { File::from_raw_fd(pipe_in) }).build()?; + + let exitcode = run(tmpdir, path, ctx)?; + + let mut stdout_file = unsafe { File::from_raw_fd(pipe_out) }; + let mut stdout = vec![]; + stdout_file.read_to_end(&mut stdout)?; + nix::unistd::close(stdout_file.into_raw_fd())?; + + Ok((exitcode, stdout)) +} + +fn run>( + tmpdir: &TempDir, + path: P, + ctx: WasiCtx, +) -> Result<__wasi_exitcode_t, Error> { + let region = MmapRegion::create(1, &Limits::default())?; + let module = wasi_test(tmpdir, path)?; + + let mut inst = region + .new_instance_builder(module) + .with_embed_ctx(ctx) + .build()?; + + match inst.run("_start", &[]) { + // normal termination implies 0 exit code + Ok(_) => Ok(0), + Err(lucet_runtime::Error::RuntimeTerminated( + lucet_runtime::TerminationDetails::Provided(any), + )) => Ok(*any + .downcast_ref::<__wasi_exitcode_t>() + .expect("termination yields an exitcode")), + Err(e) => bail!("runtime error: {}", e), + } +} + +fn wasi_test>(tmpdir: &TempDir, c_file: P) -> Result, Error> { + let wasm_build = Link::new(&[c_file]).with_cflag("-I/usr/include/csmith"); + + let wasm_file = tmpdir.path().join("out.wasm"); + + wasm_build.link(wasm_file.clone())?; + + let bindings = Bindings::from_file( + Path::new(LUCET_WASI_FUZZ_ROOT) + .parent() + .unwrap() + .join("lucet-wasi") + .join("bindings.json"), + )?; + + let native_build = Lucetc::new(wasm_file).with_bindings(bindings); + + let so_file = tmpdir.path().join("out.so"); + + native_build.shared_object_file(so_file.clone())?; + + let dlmodule = DlModule::load(so_file)?; + + Ok(dlmodule as Arc) +} + +fn host_clang() -> PathBuf { + match std::env::var("HOST_CLANG") { + Ok(clang) => PathBuf::from(clang), + Err(_) => PathBuf::from("clang"), + } +} diff --git a/lucet-wasi-sdk/Cargo.lock b/lucet-wasi-sdk/Cargo.lock deleted file mode 100644 index 0474b8a10..000000000 --- a/lucet-wasi-sdk/Cargo.lock +++ /dev/null @@ -1,216 +0,0 @@ -[[package]] -name = "autocfg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "backtrace" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "backtrace-sys" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "cc" -version = "1.0.30" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "cfg-if" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "failure" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "backtrace 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "failure_derive" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.28 (registry+https://github.com/rust-lang/crates.io-index)", - "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "libc" -version = "0.2.50" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "lucet-wasi-sdk" -version = "0.1.0" -dependencies = [ - "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "proc-macro2" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "quote" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "remove_dir_all" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "syn" -version = "0.15.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "synstructure" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.28 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tempdir" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "unicode-xid" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[metadata] -"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" -"checksum backtrace 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "cd5a90e2b463010cd0e0ce9a11d4a9d5d58d9f41d4a6ba3dcaf9e68b466e88b4" -"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" -"checksum cc 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)" = "d01c69d08ff207f231f07196e30f84c70f1c815b04f980f8b7b01ff01f05eb92" -"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" -"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" -"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" -"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -"checksum libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "aab692d7759f5cd8c859e169db98ae5b52c924add2af5fbbca11d12fefb567c1" -"checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" -"checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1" -"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" -"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" -"checksum rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "adacaae16d02b6ec37fdc7acfcddf365978de76d1983d3ee22afc260e1ca9619" -"checksum syn 0.15.28 (registry+https://github.com/rust-lang/crates.io-index)" = "218aa5a01ab9805df6e9e48074c8d88f317cc9660b1ad6c3dabac2d627d185d6" -"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" -"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" -"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" -"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" -"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/lucet-wasi-sdk/Cargo.toml b/lucet-wasi-sdk/Cargo.toml index f6fd26f46..b15cec639 100644 --- a/lucet-wasi-sdk/Cargo.toml +++ b/lucet-wasi-sdk/Cargo.toml @@ -1,12 +1,21 @@ [package] name = "lucet-wasi-sdk" -version = "0.1.0" -authors = ["Pat Hickey "] -edition = "2018" +version = "0.5.0" +description = "A Rust interface to the wasi-sdk compiler and linker" +homepage = "https://github.com/fastly/lucet" +repository = "https://github.com/fastly/lucet" license = "Apache-2.0 WITH LLVM-exception" +categories = ["wasm"] +authors = ["Lucet team "] +edition = "2018" [dependencies] -failure = "0.1" +anyhow = "1" +lucetc = { path = "../lucetc", version = "=0.5.0" } +lucet-module = { path = "../lucet-module", version = "=0.5.0" } +target-lexicon = "0.9" +tempfile = "3.0" +thiserror = "1.0.4" [dev-dependencies] -tempfile = "3.0" +lucet-validate = { path = "../lucet-validate", version = "=0.5.0" } diff --git a/lucet-wasi-sdk/src/lib.rs b/lucet-wasi-sdk/src/lib.rs index 9e633ca0c..685580f29 100644 --- a/lucet-wasi-sdk/src/lib.rs +++ b/lucet-wasi-sdk/src/lib.rs @@ -1,15 +1,24 @@ -use failure::Fail; +#![deny(bare_trait_objects)] + use std::env; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::{Command, Output}; +use tempfile::TempDir; +use thiserror::Error; + +const WASI_TARGET: &str = "wasm32-unknown-wasi"; -#[derive(Debug, Fail)] +#[derive(Debug, Error)] pub enum CompileError { - #[fail(display = "File not found: {}", _0)] + #[error("File not found: {0}")] FileNotFound(String), - #[fail(display = "Clang reported error: {}", _0)] + #[error("Clang reported error: {stdout}")] Execution { stdout: String, stderr: String }, + #[error("Lucetc error")] + Lucetc(#[from] lucetc::Error), + #[error("IO error")] + IO(#[from] std::io::Error), } impl CompileError { @@ -29,11 +38,32 @@ impl CompileError { } } -fn wasi_sdk_clang() -> PathBuf { - let mut base = PathBuf::from(env::var("WASI_SDK").unwrap_or("/opt/wasi-sdk".to_owned())); - base.push("bin"); - base.push("clang"); - base +fn wasi_sdk() -> PathBuf { + Path::new(&env::var("WASI_SDK").unwrap_or("/opt/wasi-sdk".to_owned())).to_path_buf() +} + +fn wasi_sysroot() -> PathBuf { + match env::var("WASI_SYSROOT") { + Ok(wasi_sysroot) => Path::new(&wasi_sysroot).to_path_buf(), + Err(_) => { + let mut path = wasi_sdk(); + path.push("share"); + path.push("wasi-sysroot"); + path + } + } +} + +fn wasm_clang() -> PathBuf { + match env::var("CLANG") { + Ok(clang) => Path::new(&clang).to_path_buf(), + Err(_) => { + let mut path = wasi_sdk(); + path.push("bin"); + path.push("clang"); + path + } + } } pub struct Compile { @@ -42,40 +72,55 @@ pub struct Compile { print_output: bool, } -impl Compile { - pub fn new>(input: P) -> Self { - Compile { - input: PathBuf::from(input.as_ref()), - cflags: Vec::new(), - print_output: false, - } +pub trait CompileOpts { + fn cflag>(&mut self, cflag: S); + fn with_cflag>(self, cflag: S) -> Self; + + fn include>(&mut self, include: S); + fn with_include>(self, include: S) -> Self; +} + +impl CompileOpts for Compile { + fn cflag>(&mut self, cflag: S) { + self.cflags.push(cflag.as_ref().to_owned()); } - pub fn cflag>(mut self, cflag: S) -> Self { - self.with_cflag(cflag); + fn with_cflag>(mut self, cflag: S) -> Self { + self.cflag(cflag); self } - pub fn with_cflag>(&mut self, cflag: S) { - self.cflags.push(cflag.as_ref().to_owned()); + fn include>(&mut self, include: S) { + self.cflags + .push(format!("-I{}", include.as_ref().display())); } - pub fn include>(mut self, include: S) -> Self { - self.with_include(include); + fn with_include>(mut self, include: S) -> Self { + self.include(include); self } +} - pub fn with_include>(&mut self, include: S) { - self.cflags.push(format!("-I{}", include.as_ref())); +impl Compile { + pub fn new>(input: P) -> Self { + Compile { + input: PathBuf::from(input.as_ref()), + cflags: Vec::new(), + print_output: false, + } } - pub fn print_output(mut self, print: bool) -> Self { + pub fn print_output(&mut self, print: bool) { self.print_output = print; + } + + pub fn with_print_output(mut self, print: bool) -> Self { + self.print_output(print); self } pub fn compile>(&self, output: P) -> Result<(), CompileError> { - let clang = wasi_sdk_clang(); + let clang = wasm_clang(); if !clang.exists() { Err(CompileError::FileNotFound( clang.to_string_lossy().into_owned(), @@ -87,6 +132,8 @@ impl Compile { ))?; } let mut cmd = Command::new(clang); + cmd.arg(format!("--target={}", WASI_TARGET)); + cmd.arg(format!("--sysroot={}", wasi_sysroot().display())); cmd.arg("-c"); cmd.arg(self.input.clone()); cmd.arg("-o"); @@ -94,6 +141,9 @@ impl Compile { for cflag in self.cflags.iter() { cmd.arg(cflag); } + if self.print_output { + println!("running: {:?}", cmd); + } let run = cmd.output().expect("clang executable exists"); CompileError::check(run, self.print_output) } @@ -110,55 +160,24 @@ impl Link { pub fn new>(input: &[P]) -> Self { Link { input: input.iter().map(|p| PathBuf::from(p.as_ref())).collect(), - cflags: Vec::new(), - ldflags: Vec::new(), + cflags: vec![], + ldflags: vec![], print_output: false, } + .with_link_opt(LinkOpt::DefaultOpts) } - pub fn cflag>(mut self, cflag: S) -> Self { - self.with_cflag(cflag); - self - } - - pub fn with_cflag>(&mut self, cflag: S) { - self.cflags.push(cflag.as_ref().to_owned()); - } - - pub fn include>(mut self, include: S) -> Self { - self.with_include(include); - self - } - - pub fn with_include>(&mut self, include: S) { - self.cflags.push(format!("-I{}", include.as_ref())); - } - - pub fn ldflag>(mut self, ldflag: S) -> Self { - self.with_ldflag(ldflag); - self - } - - pub fn with_ldflag>(&mut self, ldflag: S) { - self.ldflags.push(ldflag.as_ref().to_owned()); - } - - pub fn export>(mut self, export: S) -> Self { - self.with_export(export); - self - } - - pub fn with_export>(&mut self, export: S) { - self.ldflags.push(format!("--export={}", export.as_ref())); + pub fn print_output(&mut self, print: bool) { + self.print_output = print; } - pub fn print_output(mut self, print: bool) -> Self { - self.print_output = print; + pub fn with_print_output(mut self, print: bool) -> Self { + self.print_output(print); self } pub fn link>(&self, output: P) -> Result<(), CompileError> { - let clang = wasi_sdk_clang(); + let clang = wasm_clang(); if !clang.exists() { Err(CompileError::FileNotFound( clang.to_string_lossy().into_owned(), @@ -173,6 +192,8 @@ impl Link { } cmd.arg(input.clone()); } + cmd.arg(format!("--target={}", WASI_TARGET)); + cmd.arg(format!("--sysroot={}", wasi_sysroot().display())); cmd.arg("-o"); cmd.arg(output.as_ref()); for cflag in self.cflags.iter() { @@ -181,88 +202,169 @@ impl Link { for ldflag in self.ldflags.iter() { cmd.arg(format!("-Wl,{}", ldflag)); } + if self.print_output { + println!("running: {:?}", cmd); + } let run = cmd.output().expect("clang executable exists"); CompileError::check(run, self.print_output) } } -#[cfg(test)] -mod tests { - use super::*; - use tempfile::TempDir; - #[test] - fn wasi_sdk_installed() { - let clang = wasi_sdk_clang(); - assert!(clang.exists(), "clang executable exists"); - } +pub trait AsLink { + fn as_link(&mut self) -> &mut Link; +} - fn test_file(name: &str) -> PathBuf { - let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - p.push("test"); - p.push(name); - assert!(p.exists(), "test file does not exist"); - p +impl AsLink for Link { + fn as_link(&mut self) -> &mut Link { + self } +} - #[test] - fn compile_a() { - let tmp = TempDir::new().expect("create temporary directory"); +#[derive(Clone, Copy, Debug)] +pub enum LinkOpt<'t> { + /// Allow references to any undefined function. They will be resolved later by the dynamic linker + AllowUndefinedAll, - let compiler = Compile::new(test_file("a.c")); + /// Default options, possibly enabling workarounds for temporary bugs + DefaultOpts, - let objfile = tmp.path().join("a.o"); + /// Export a symbol + Export(&'t str), - compiler.compile(objfile.clone()).expect("compile a.c"); + /// Preserve all the symbols during LTO, even if they are not used + ExportAll, - assert!(objfile.exists(), "object file created"); + /// Do not assume that the library has a predefined entry point + NoDefaultEntryPoint, - let mut linker = Link::new(&[objfile]); - linker.with_cflag("-nostartfiles"); - linker.with_ldflag("--no-entry"); + /// Create a shared library + Shared, - let wasmfile = tmp.path().join("a.wasm"); + /// Do not put debug information (STABS or DWARF) in the output file + StripDebug, - linker.link(wasmfile.clone()).expect("link a.wasm"); + /// Remove functions and data that are unreachable by the entry point or exported symbols + StripUnused, +} - assert!(wasmfile.exists(), "wasm file created"); +impl<'t> LinkOpt<'t> { + fn as_ldflags(&self) -> Vec { + match self { + LinkOpt::AllowUndefinedAll => vec!["--allow-undefined".to_string()], + LinkOpt::DefaultOpts => vec!["--no-threads".to_string()], + LinkOpt::Export(symbol) => vec![format!("--export={}", symbol).to_string()], + LinkOpt::ExportAll => vec!["--export-all".to_string()], + LinkOpt::NoDefaultEntryPoint => vec!["--no-entry".to_string()], + LinkOpt::Shared => vec!["--shared".to_string()], + LinkOpt::StripDebug => vec!["--strip-debug".to_string()], + LinkOpt::StripUnused => vec!["--strip-discarded".to_string()], + } } +} + +pub trait LinkOpts { + fn link_opt(&mut self, link_opt: LinkOpt<'_>); + fn with_link_opt(self, link_opt: LinkOpt<'_>) -> Self; - #[test] - fn compile_b() { - let tmp = TempDir::new().expect("create temporary directory"); + fn export>(&mut self, export: S); + fn with_export>(self, export: S) -> Self; +} + +impl LinkOpts for T { + fn link_opt(&mut self, link_opt: LinkOpt<'_>) { + self.as_link().ldflags.extend(link_opt.as_ldflags()); + } + + fn with_link_opt(mut self, link_opt: LinkOpt<'_>) -> Self { + self.link_opt(link_opt); + self + } - let compiler = Compile::new(test_file("b.c")); + fn export>(&mut self, export: S) { + self.link_opt(LinkOpt::Export(export.as_ref())); + } - let objfile = tmp.path().join("b.o"); + fn with_export>(mut self, export: S) -> Self { + self.export(export); + self + } +} - compiler.compile(objfile.clone()).expect("compile b.c"); +impl CompileOpts for T { + fn cflag>(&mut self, cflag: S) { + self.as_link().cflags.push(cflag.as_ref().to_owned()); + } - assert!(objfile.exists(), "object file created"); + fn with_cflag>(mut self, cflag: S) -> Self { + self.cflag(cflag); + self + } - let mut linker = Link::new(&[objfile]); - linker.with_cflag("-nostartfiles"); - linker.with_ldflag("--no-entry"); - linker.with_ldflag("--allow-undefined"); + fn include>(&mut self, include: S) { + self.as_link() + .cflags + .push(format!("-I{}", include.as_ref().display())); + } - let wasmfile = tmp.path().join("b.wasm"); + fn with_include>(mut self, include: S) -> Self { + self.include(include); + self + } +} - linker.link(wasmfile.clone()).expect("link b.wasm"); +pub struct Lucetc { + link: Link, + lucetc: lucetc::Lucetc, + tmpdir: TempDir, + wasm_file: PathBuf, +} - assert!(wasmfile.exists(), "wasm file created"); +impl Lucetc { + pub fn new>(input: &[P]) -> Self { + let link = Link::new(input); + let tmpdir = TempDir::new().expect("temporary directory creation failed"); + let wasm_file = tmpdir.path().join("out.wasm"); + let lucetc = lucetc::Lucetc::new(&wasm_file); + Lucetc { + link, + lucetc, + tmpdir, + wasm_file, + } } - #[test] - fn compile_a_and_b() { - let tmp = TempDir::new().expect("create temporary directory"); + pub fn print_output(&mut self, print: bool) { + self.link.print_output = print; + } - let mut linker = Link::new(&[test_file("a.c"), test_file("b.c")]); - linker.with_cflag("-nostartfiles"); - linker.with_ldflag("--no-entry"); + pub fn with_print_output(mut self, print: bool) -> Self { + self.print_output(print); + self + } - let wasmfile = tmp.path().join("ab.wasm"); + pub fn build>(self, output: P) -> Result<(), CompileError> { + self.link.link(&self.wasm_file)?; + self.lucetc + .shared_object_file(output.as_ref()) + .map_err(CompileError::Lucetc)?; + Ok(self.tmpdir.close()?) + } +} - linker.link(wasmfile.clone()).expect("link ab.wasm"); +impl AsLink for Lucetc { + fn as_link(&mut self) -> &mut Link { + &mut self.link + } +} - assert!(wasmfile.exists(), "wasm file created"); +impl lucetc::AsLucetc for Lucetc { + fn as_lucetc(&mut self) -> &mut lucetc::Lucetc { + &mut self.lucetc } } + +#[test] +fn wasi_sdk_installed() { + let clang = wasm_clang(); + assert!(clang.exists(), "clang executable exists"); +} diff --git a/lucet-wasi-sdk/test/a.c b/lucet-wasi-sdk/tests/a.c similarity index 100% rename from lucet-wasi-sdk/test/a.c rename to lucet-wasi-sdk/tests/a.c diff --git a/lucet-wasi-sdk/test/b.c b/lucet-wasi-sdk/tests/b.c similarity index 100% rename from lucet-wasi-sdk/test/b.c rename to lucet-wasi-sdk/tests/b.c diff --git a/lucetc/tests/wasi-sdk/a.c b/lucet-wasi-sdk/tests/c.c similarity index 62% rename from lucetc/tests/wasi-sdk/a.c rename to lucet-wasi-sdk/tests/c.c index 76dad32b6..4e7984145 100644 --- a/lucetc/tests/wasi-sdk/a.c +++ b/lucet-wasi-sdk/tests/c.c @@ -1,5 +1,5 @@ -int a(int arg) +int c(int arg) { return arg + 1; } diff --git a/lucet-wasi-sdk/tests/compile_and_link.rs b/lucet-wasi-sdk/tests/compile_and_link.rs new file mode 100644 index 000000000..3544631f6 --- /dev/null +++ b/lucet-wasi-sdk/tests/compile_and_link.rs @@ -0,0 +1,91 @@ +#[cfg(test)] +mod compile_and_link_tests { + use lucet_wasi_sdk::*; + use std::path::PathBuf; + use tempfile::TempDir; + + fn test_file(name: &str) -> PathBuf { + let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + p.push("tests"); + p.push(name); + assert!(p.exists(), "test file does not exist"); + p + } + + #[test] + fn compile_a() { + let tmp = TempDir::new().expect("create temporary directory"); + + let compiler = Compile::new(test_file("a.c")); + + let objfile = tmp.path().join("a.o"); + + compiler.compile(objfile.clone()).expect("compile a.c"); + + assert!(objfile.exists(), "object file created"); + + let mut linker = Link::new(&[objfile]); + linker.cflag("-nostartfiles"); + linker.link_opt(LinkOpt::NoDefaultEntryPoint); + + let wasmfile = tmp.path().join("a.wasm"); + + linker.link(wasmfile.clone()).expect("link a.wasm"); + + assert!(wasmfile.exists(), "wasm file created"); + } + + #[test] + fn compile_b() { + let tmp = TempDir::new().expect("create temporary directory"); + + let compiler = Compile::new(test_file("b.c")); + + let objfile = tmp.path().join("b.o"); + + compiler.compile(objfile.clone()).expect("compile b.c"); + + assert!(objfile.exists(), "object file created"); + + let mut linker = Link::new(&[objfile]); + linker.cflag("-nostartfiles"); + linker.link_opt(LinkOpt::NoDefaultEntryPoint); + linker.link_opt(LinkOpt::AllowUndefinedAll); + + let wasmfile = tmp.path().join("b.wasm"); + + linker.link(wasmfile.clone()).expect("link b.wasm"); + + assert!(wasmfile.exists(), "wasm file created"); + } + + #[test] + fn compile_a_and_b() { + let tmp = TempDir::new().expect("create temporary directory"); + + let mut linker = Link::new(&[test_file("a.c"), test_file("b.c")]); + linker.cflag("-nostartfiles"); + linker.link_opt(LinkOpt::NoDefaultEntryPoint); + + let wasmfile = tmp.path().join("ab.wasm"); + + linker.link(wasmfile.clone()).expect("link ab.wasm"); + + assert!(wasmfile.exists(), "wasm file created"); + } + + #[test] + fn compile_to_lucet() { + let tmp = TempDir::new().expect("create temporary directory"); + + let mut lucetc = Lucetc::new(&[test_file("a.c"), test_file("b.c")]); + lucetc.cflag("-nostartfiles"); + lucetc.link_opt(LinkOpt::NoDefaultEntryPoint); + + let so_file = tmp.path().join("ab.so"); + + lucetc.build(&so_file).expect("compile ab.so"); + + assert!(so_file.exists(), "so file created"); + } +} diff --git a/lucet-wasi-sdk/tests/d.c b/lucet-wasi-sdk/tests/d.c new file mode 100644 index 000000000..950fe8e53 --- /dev/null +++ b/lucet-wasi-sdk/tests/d.c @@ -0,0 +1,7 @@ + +extern int c(int); + +int d(int arg) +{ + return 3 * c(arg); +} diff --git a/lucetc/tests/wasi-sdk/empty.c b/lucet-wasi-sdk/tests/empty.c similarity index 100% rename from lucetc/tests/wasi-sdk/empty.c rename to lucet-wasi-sdk/tests/empty.c diff --git a/lucet-wasi-sdk/tests/hello.c b/lucet-wasi-sdk/tests/hello.c new file mode 100644 index 000000000..02f86c083 --- /dev/null +++ b/lucet-wasi-sdk/tests/hello.c @@ -0,0 +1,8 @@ + +#include + +int main(int argc, char *argv[]) +{ + printf("hello, world! i am %s\n", argv[0]); + return 0; +} diff --git a/lucet-wasi-sdk/tests/lucetc.rs b/lucet-wasi-sdk/tests/lucetc.rs new file mode 100644 index 000000000..e880ad0d4 --- /dev/null +++ b/lucet-wasi-sdk/tests/lucetc.rs @@ -0,0 +1,216 @@ +#[cfg(test)] +mod lucetc_tests { + use anyhow::Error; + use lucet_module::bindings::Bindings; + use lucet_validate::Validator; + use lucet_wasi_sdk::*; + use lucetc::{Compiler, CpuFeatures, HeapSettings, OptLevel}; + use std::collections::HashMap; + use std::fs::File; + use std::io::Read; + use std::path::PathBuf; + use target_lexicon::Triple; + + /// Compile C -> WebAssembly using wasi-sdk's clang. Does not use the wasi-sdk + /// libc, and does not produce a wasi executable, just a wasm module with the given set of + /// export functions. + fn module_from_c(cfiles: &[&str], exports: &[&str]) -> Result, Error> { + let cfiles: Vec = cfiles + .iter() + .map(|ref name| PathBuf::from(format!("tests/{}.c", name))) + .collect(); + let tempdir = tempfile::Builder::new().prefix("wasi-sdk-test").tempdir()?; + + let mut wasm = PathBuf::from(tempdir.path()); + wasm.push("out.wasm"); + + let mut linker = Link::new(&cfiles) + .with_cflag("-nostartfiles") + .with_link_opt(LinkOpt::NoDefaultEntryPoint) + .with_link_opt(LinkOpt::AllowUndefinedAll); + for export in exports { + linker.export(export); + } + linker.link(wasm.clone())?; + + let mut wasm_file = File::open(wasm)?; + let mut wasm_contents = Vec::new(); + wasm_file.read_to_end(&mut wasm_contents)?; + Ok(wasm_contents) + } + + #[test] + fn empty() { + let m = module_from_c(&["empty"], &[]).expect("build module for empty"); + let b = Bindings::empty(); + let h = HeapSettings::default(); + let v = Validator::parse("").expect("empty validation environment"); + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &Some(v), + ) + .expect("compile empty"); + let mdata = c.module_data().unwrap(); + assert!(mdata.heap_spec().is_some()); + // clang creates just 1 global: + assert_eq!(mdata.globals_spec().len(), 1); + assert!(mdata.globals_spec()[0].is_internal()); + + assert_eq!(mdata.import_functions().len(), 0, "import functions"); + assert_eq!(mdata.export_functions().len(), 0, "export functions"); + + /* FIXME: module data doesn't contain the information to check these properties: + assert_eq!(num_import_globals(&p), 0, "import globals"); + */ + + let _obj = c.object_file().expect("generate code from empty"); + } + + fn d_only_test_bindings() -> Bindings { + let imports: HashMap = [ + ("c".into(), "c".into()), // d_only + ] + .iter() + .cloned() + .collect(); + + Bindings::env(imports) + } + + #[test] + fn just_c() { + let m = module_from_c(&["c"], &["c"]).expect("build module for c"); + let b = Bindings::empty(); + let h = HeapSettings::default(); + let v = Validator::parse("").expect("empty validation environment"); + + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &Some(v), + ) + .expect("compile c"); + let mdata = c.module_data().unwrap(); + assert_eq!(mdata.import_functions().len(), 0, "import functions"); + assert_eq!(mdata.export_functions().len(), 1, "export functions"); + + /* FIXME: module data doesn't contain the information to check these properties: + assert_eq!(num_import_globals(&p), 0, "import globals"); + */ + + let _obj = c.object_file().expect("generate code from c"); + } + + #[test] + fn just_d() { + let m = module_from_c(&["d"], &["d"]).expect("build module for d"); + let b = d_only_test_bindings(); + let h = HeapSettings::default(); + let v = Validator::parse( + "(module $env (@interface func (export \"c\") (param $a1 s32) (result $r1 s32)))", + ) + .expect("empty validation environment"); + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &Some(v), + ) + .expect("compile d"); + let mdata = c.module_data().unwrap(); + assert_eq!(mdata.import_functions().len(), 1, "import functions"); + assert_eq!(mdata.export_functions().len(), 1, "export functions"); + + /* FIXME: module data doesn't contain the information to check these properties: + assert_eq!(num_import_globals(&p), 0, "import globals"); + */ + let _obj = c.object_file().expect("generate code from d"); + } + + #[test] + fn c_and_d() { + let m = module_from_c(&["c", "d"], &["c", "d"]).expect("build module for c & d"); + let b = Bindings::empty(); + let h = HeapSettings::default(); + let v = Validator::parse("").expect("empty validation environment"); + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &Some(v), + ) + .expect("compile c & d"); + let mdata = c.module_data().unwrap(); + assert_eq!(mdata.import_functions().len(), 0, "import functions"); + assert_eq!(mdata.export_functions().len(), 2, "export functions"); + + /* FIXME: module data doesn't contain the information to check these properties: + assert_eq!(num_import_globals(&p), 0, "import globals"); + */ + let _obj = c.object_file().expect("generate code from c & d"); + } + + #[test] + fn hello() { + let m = { + // Unlike in module_from_c, use wasi-sdk to compile a C file to a wasi executable, + // linking in wasi-libc and exposing the wasi _start entry point only: + let tempdir = tempfile::Builder::new() + .prefix("wasi-sdk-test") + .tempdir() + .expect("create tempdir"); + let mut wasm = PathBuf::from(tempdir.path()); + wasm.push("out.wasm"); + + let linker = Link::new(&[PathBuf::from("tests/hello.c")]); + linker.link(wasm.clone()).expect("link"); + + let mut wasm_file = File::open(wasm).expect("open wasm"); + let mut wasm_contents = Vec::new(); + wasm_file + .read_to_end(&mut wasm_contents) + .expect("read wasm"); + wasm_contents + }; + + let b = + Bindings::from_file("../lucet-wasi/bindings.json").expect("load lucet-wasi bindings"); + let h = HeapSettings::default(); + let v = Validator::load("../wasi/phases/old/snapshot_0/witx/wasi_unstable.witx") + .expect("wasi spec validation") + .with_wasi_exe(true); + // Compiler will only unwrap if the Validator defined above accepts the module + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &Some(v), + ) + .expect("compile empty"); + let mdata = c.module_data().unwrap(); + assert!(mdata.heap_spec().is_some()); + } +} diff --git a/lucet-wasi/Cargo.toml b/lucet-wasi/Cargo.toml index b20379ee2..60f52ee01 100644 --- a/lucet-wasi/Cargo.toml +++ b/lucet-wasi/Cargo.toml @@ -1,34 +1,55 @@ [package] name = "lucet-wasi" -version = "0.1.0" -authors = ["Adam C. Foltzer "] -edition = "2018" +version = "0.5.0" +description = "Fastly's runtime for the WebAssembly System Interface (WASI)" +homepage = "https://github.com/fastly/lucet" +repository = "https://github.com/fastly/lucet" license = "Apache-2.0 WITH LLVM-exception" +categories = ["wasm"] +authors = ["Lucet team "] +edition = "2018" + +# `src/wasi_host.rs` is automatically generated using clang and +# wasi-libc headers. This requires these to be present, and installed +# at specific paths, which is not something we can rely on outside +# of our environment. +# So, we follow what most other tools using `bindgen` do, and provide +# a pre-generated version of the file, along with a way to update it. +# This is what the `update-bindings` feature do. It requires the WASI SDK +# to be either installed in `/opt/wasi-sdk`, or at a location defined by +# a `WASI_SDK` environment variable, as well as `clang` headers either +# being part of `WASI_SDK`, or found in a path defined by a +# `CLANG_ROOT` environment variable. +[features] +update-bindings = ["bindgen"] [dependencies] +anyhow = "1" cast = "0.2" clap = "2.23" -failure = "0.1" human-size = "0.4" -libc = "0.2" -lucet-runtime = { path = "../lucet-runtime" } -lucet-runtime-internals = { path = "../lucet-runtime/lucet-runtime-internals" } -nix = "0.13" +lucet-runtime = { path = "../lucet-runtime", version = "=0.5.0" } +lucet-runtime-internals = { path = "../lucet-runtime/lucet-runtime-internals", version = "=0.5.0" } +lucet-module = { path = "../lucet-module", version = "=0.5.0" } +libc = "0.2.65" +nix = "0.15" rand = "0.6" +wasi-common = "0.7" [dev-dependencies] -lucetc = { path = "../lucetc" } -lucet-wasi-sdk = { path = "../lucet-wasi-sdk" } +lucet-wasi-sdk = { path = "../lucet-wasi-sdk", version = "=0.5.0" } +lucetc = { path = "../lucetc", version = "=0.5.0" } tempfile = "3.0" [build-dependencies] -bindgen = "0.47" +bindgen = { version = "0.51.1", optional = true } [lib] name = "lucet_wasi" crate-type = ["rlib", "staticlib", "cdylib"] [package.metadata.deb] +name = "fst-lucet-wasi" maintainer = "Adam C. Foltzer " depends = "$auto" priority = "optional" @@ -37,8 +58,8 @@ assets = [ ["target/release/liblucet_wasi.rlib", "/opt/fst-lucet-wasi/lib/", "644"], ["target/release/liblucet_wasi.so", "/opt/fst-lucet-wasi/lib/", "755"], ["include/*.h", "/opt/fst-lucet-wasi/include/", "644"], - ["README.md", "/opt/fst-lucet-wasi/share/doc/lucet-wasi/", "644"], ["LICENSE", "/opt/fst-lucet-wasi/share/doc/lucet-wasi/", "644"], ["LICENSE.wasmtime", "/opt/fst-lucet-wasi/share/doc/lucet-wasi/", "644"], ["LICENSE.cloudabi-utils", "/opt/fst-lucet-wasi/share/doc/lucet-wasi/", "644"], + ["bindings.json", "/opt/fst-lucet-wasi/share/", "644"], ] diff --git a/lucet-wasi/README.md b/lucet-wasi/README.md deleted file mode 100644 index 0584db24a..000000000 --- a/lucet-wasi/README.md +++ /dev/null @@ -1,79 +0,0 @@ -# lucet-wasi - -Experimental WASI embedding for the Lucet runtime. - -Much of this code is a direct port of the `cloudabi-utils`-based syscall emulation layer via -[`wasmtime`](https://github.com/CraneStation/wasmtime/tree/master/wasmtime-wasi/sandboxed-system-primitives). It -is currently suitable for demonstration purposes, but needs to be rewritten in a more Rust-native -style to reduce the code complexity and the number of potential panics. - -If you have questions or suggestions, the authors of `lucet-wasi` and others in the WASI community -can be found in [`#wasi` on Mozilla IRC](https://wiki.mozilla.org/IRC). - -## Examples - -Example WASI programs are in the [`examples`](examples) directory. - -## Supported syscalls - -We support a subset of the [WASI -API](https://github.com/CraneStation/wasmtime/blob/master/docs/WASI-api.md), though we are adding -new syscalls on a regular basis. We currently implement: - -- `__wasi_args_get` -- `__wasi_args_sizes_get` -- `__wasi_clock_res_get` -- `__wasi_clock_time_get` -- `__wasi_environ_get` -- `__wasi_environ_sizes_get` -- `__wasi_fd_close` -- `__wasi_fd_fdstat_get` -- `__wasi_fd_fdstat_set_flags` -- `__wasi_fd_prestat_dir_name` -- `__wasi_fd_prestat_get` -- `__wasi_fd_read` -- `__wasi_fd_seek` -- `__wasi_fd_write` -- `__wasi_path_open` -- `__wasi_proc_exit` -- `__wasi_random_get` - -This is enough to run basic C and Rust programs, including those that use command-line arguments, -environment variables, stdio, and basic file operations. - -## Thread safety - -Lucet guests are currently single-threaded only. The WASI embedding assumes this, and so the syscall -implementations are not thread-safe. This is not a fundamental limitation, should Lucet support -multi-threaded guests in the future. - -## TODOs - -### Complete the WASI API syscalls - -We are missing support for advanced filesystem operations, sockets, and polling, among others. - -### Introduce optional abstraction between system clocks and WASI clocks - -The current implementations of `__wasi_clock_*` delegate to the host system's `clock_getres` and -`clock_gettime`. For untrusted code, it would be useful to limit the precision of these clocks to -reduce the potential impact of side channels. Furthermore, the `CLOCK_*_CPUTIME_ID` clocks currently -give timings for the host process, but a measure of the guest instance runtime would be more -accurate. - -### Rewrite the code that implements capability checking - -Much of this code is a direct port of the [`wasmtime` C -implementation](https://github.com/CraneStation/wasmtime/tree/master/wasmtime-wasi/sandboxed-system-primitives), -and as such contains a fair amount of unsafety and low-level operations on bytestrings and -bitfields. Since this code is critical to the integrity of the sandboxing model, we intend to -rewrite this code in higher-level Rust that is easier to test and verify. - -## Third-Party Code - -`src/wasm32.rs` is copied from -[wasmtime](https://github.com/CraneStation/wasmtime/blob/master/wasmtime-wasi/src/wasm32.rs), along -with the associated `LICENSE.wasmtime` file. - -Significant parts of our syscall implementations are derived from the C implementations in -`cloudabi-utils`. See `LICENSE.cloudabi-utils` for license information. diff --git a/lucet-wasi/bindings.json b/lucet-wasi/bindings.json index 1ef1660d3..568ad7eb6 100644 --- a/lucet-wasi/bindings.json +++ b/lucet-wasi/bindings.json @@ -6,16 +6,40 @@ "clock_time_get": "__wasi_clock_time_get", "environ_get": "__wasi_environ_get", "environ_sizes_get": "__wasi_environ_sizes_get", - "proc_exit": "__wasi_proc_exit", + "fd_advise": "__wasi_fd_advise", + "fd_allocate": "__wasi_fd_allocate", "fd_close": "__wasi_fd_close", + "fd_datasync": "__wasi_fd_datasync", "fd_fdstat_get": "__wasi_fd_fdstat_get", "fd_fdstat_set_flags": "__wasi_fd_fdstat_set_flags", - "fd_prestat_get": "__wasi_fd_prestat_get", + "fd_fdstat_set_rights": "__wasi_fd_fdstat_set_rights", + "fd_filestat_get": "__wasi_fd_filestat_get", + "fd_filestat_set_size": "__wasi_fd_filestat_set_size", + "fd_filestat_set_times": "__wasi_fd_filestat_set_times", + "fd_pread": "__wasi_fd_pread", "fd_prestat_dir_name": "__wasi_fd_prestat_dir_name", + "fd_prestat_get": "__wasi_fd_prestat_get", + "fd_pwrite": "__wasi_fd_pwrite", "fd_read": "__wasi_fd_read", + "fd_readdir": "__wasi_fd_readdir", + "fd_renumber": "__wasi_fd_renumber", "fd_seek": "__wasi_fd_seek", + "fd_sync": "__wasi_fd_sync", + "fd_tell": "__wasi_fd_tell", "fd_write": "__wasi_fd_write", + "path_create_directory": "__wasi_path_create_directory", + "path_filestat_get": "__wasi_path_filestat_get", + "path_filestat_set_times": "__wasi_path_filestat_set_times", + "path_link": "__wasi_path_link", "path_open": "__wasi_path_open", - "random_get": "__wasi_random_get" + "path_readlink": "__wasi_path_readlink", + "path_remove_directory": "__wasi_path_remove_directory", + "path_rename": "__wasi_path_rename", + "path_symlink": "__wasi_path_symlink", + "path_unlink_file": "__wasi_path_unlink_file", + "poll_oneoff": "__wasi_poll_oneoff", + "proc_exit": "__wasi_proc_exit", + "random_get": "__wasi_random_get", + "sched_yield": "__wasi_sched_yield" } } diff --git a/lucet-wasi/build.rs b/lucet-wasi/build.rs index b51ec3159..068e214fa 100644 --- a/lucet-wasi/build.rs +++ b/lucet-wasi/build.rs @@ -1,14 +1,77 @@ +#![allow(unused)] + use std::env; use std::fs::File; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; +fn wasi_sdk() -> PathBuf { + Path::new(&env::var("WASI_SDK").unwrap_or("/opt/wasi-sdk".to_owned())).to_path_buf() +} + +fn wasi_sysroot() -> PathBuf { + match env::var("WASI_SYSROOT") { + Ok(wasi_sysroot) => Path::new(&wasi_sysroot).to_path_buf(), + Err(_) => { + let mut path = wasi_sdk(); + path.push("share"); + path.push("wasi-sysroot"); + path + } + } +} + +fn wasm_clang_root() -> PathBuf { + match env::var("CLANG_ROOT") { + Ok(clang) => Path::new(&clang).to_path_buf(), + Err(_) => { + let mut path = wasi_sdk(); + path.push("lib"); + path.push("clang"); + path.push("8.0.1"); + path + } + } +} + +// `src/wasi_host.rs` is automatically generated using clang and +// wasi-libc headers. This requires these to be present, and installed +// at specific paths, which is not something we can rely on outside +// of our environment. +// So, we follow what most other tools using `bindgen` do, and provide +// a pre-generated version of the file, along with a way to update it. +// This is what the `update-bindings` feature do. It requires the WASI SDK +// to be either installed in `/opt/wasi-sdk`, or at a location defined by +// a `WASI_SDK` environment variable, as well as `clang` headers either +// being part of `WASI_SDK`, or found in a path defined by a +// `CLANG_ROOT` environment variable. +#[cfg(not(feature = "update-bindings"))] +fn main() {} + +#[cfg(feature = "update-bindings")] fn main() { - let wasi_sdk = - Path::new(&env::var("WASI_SDK").unwrap_or("/opt/wasi-sdk".to_owned())).to_path_buf(); - let wasi_sdk_core_h = wasi_sdk.join("share/sysroot/include/wasi/core.h"); + let wasi_sysroot = wasi_sysroot(); + let wasm_clang_root = wasm_clang_root(); + assert!( + wasi_sysroot.exists(), + "wasi-sysroot not present at {:?}", + wasi_sysroot + ); + assert!( + wasm_clang_root.exists(), + "clang-root not present at {:?}", + wasm_clang_root + ); + + let wasi_sysroot_core_h = wasi_sysroot.join("include/wasi/core.h"); - println!("cargo:rerun-if-changed={}", wasi_sdk_core_h.display()); + assert!( + wasi_sysroot_core_h.exists(), + "wasi-sysroot core.h not present at {:?}", + wasi_sysroot_core_h + ); + + println!("cargo:rerun-if-changed={}", wasi_sysroot_core_h.display()); let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); @@ -17,29 +80,35 @@ fn main() { // `bindgen` doesn't understand typed constant macros like `UINT8_C(123)`, so this fun regex // strips them off to yield a copy of `wasi/core.h` with bare constants. - Command::new("sed") + let sed_result = Command::new("sed") .arg("-E") .arg(r#"s/U?INT[0-9]+_C\(((0x)?[0-9]+)\)/\1/g"#) - .arg(wasi_sdk_core_h) + .arg(wasi_sysroot_core_h) .stdout(Stdio::from(core_h)) .status() - .unwrap(); + .expect("can execute sed"); + + if !sed_result.success() { + // something failed, but how? + match sed_result.code() { + Some(code) => panic!("sed failed with code {}", code), + None => panic!("sed exited abnormally"), + } + } let host_builder = bindgen::Builder::default() .clang_arg("-nostdinc") .clang_arg("-D__wasi__") - .clang_arg(format!( - "-isystem={}/share/sysroot/include/", - wasi_sdk.display() - )) - .clang_arg(format!("-I{}/lib/clang/8.0.0/include/", wasi_sdk.display())) + .clang_arg(format!("-isystem={}/include/", wasi_sysroot.display())) + .clang_arg(format!("-I{}/include/", wasm_clang_root.display())) .header(core_h_path.to_str().unwrap()) .whitelist_type("__wasi_.*") .whitelist_var("__WASI_.*"); + let src_path = Path::new("src"); host_builder .generate() .expect("can generate host bindings") - .write_to_file(out_path.join("wasi_host.rs")) + .write_to_file(src_path.join("wasi_host.rs")) .expect("can write host bindings"); } diff --git a/lucet-wasi/include/lucet_wasi.h b/lucet-wasi/include/lucet_wasi.h index 577b21a2d..b0c5d6edc 100644 --- a/lucet-wasi/include/lucet_wasi.h +++ b/lucet-wasi/include/lucet_wasi.h @@ -5,12 +5,14 @@ struct lucet_wasi_ctx; -struct lucet_wasi_ctx *lucet_wasi_ctx_create(); +struct lucet_wasi_ctx *lucet_wasi_ctx_create(void); enum lucet_error lucet_wasi_ctx_args(struct lucet_wasi_ctx *wasi_ctx, size_t argc, char **argv); enum lucet_error lucet_wasi_ctx_inherit_env(struct lucet_wasi_ctx *wasi_ctx); +enum lucet_error lucet_wasi_ctx_inherit_stdio(struct lucet_wasi_ctx *wasi_ctx); + void lucet_wasi_ctx_destroy(struct lucet_wasi_ctx *wasi_ctx); enum lucet_error lucet_region_new_instance_with_wasi_ctx(const struct lucet_region * region, diff --git a/lucet-wasi/src/bindings.rs b/lucet-wasi/src/bindings.rs new file mode 100644 index 000000000..0d454a1f0 --- /dev/null +++ b/lucet-wasi/src/bindings.rs @@ -0,0 +1,11 @@ +use lucet_module::bindings::Bindings; + +pub fn bindings() -> Bindings { + Bindings::from_str(include_str!("../bindings.json")).expect("lucet-wasi bindings.json is valid") +} + +#[cfg(test)] +#[test] +fn test_bindings_parses() { + let _ = bindings(); +} diff --git a/lucet-wasi/src/c_api.rs b/lucet-wasi/src/c_api.rs index c81ca0c49..dde8447e2 100644 --- a/lucet-wasi/src/c_api.rs +++ b/lucet-wasi/src/c_api.rs @@ -1,4 +1,5 @@ -use crate::ctx::WasiCtxBuilder; +use crate::WasiCtxBuilder; + use lucet_runtime::{DlModule, Module, Region}; use lucet_runtime_internals::c_api::{lucet_dl_module, lucet_error, lucet_instance, lucet_region}; use lucet_runtime_internals::instance::instance_handle_to_raw; @@ -26,23 +27,35 @@ pub unsafe extern "C" fn lucet_wasi_ctx_args( assert_nonnull!(wasi_ctx); let mut b = Box::from_raw(wasi_ctx as *mut WasiCtxBuilder); let args_raw = std::slice::from_raw_parts(argv, argc); - // TODO: error handling - let args = args_raw + let args: Result, _> = args_raw .into_iter() - .map(|arg| CStr::from_ptr(*arg)) - .collect::>(); - *b = b.c_args(&args); + .map(|arg| CStr::from_ptr(*arg).to_str()) + .collect(); + let args = match args { + Ok(args) => args, + Err(_) => return lucet_error::InvalidArgument, + }; + *b = b.args(args.iter()); Box::into_raw(b); lucet_error::Ok } #[no_mangle] -pub unsafe extern "C" fn lucet_wasi_ctx_inherit_env( +pub unsafe extern "C" fn lucet_wasi_ctx_inherit_env(wasi_ctx: *mut lucet_wasi_ctx) -> lucet_error { + assert_nonnull!(wasi_ctx); + let mut b = Box::from_raw(wasi_ctx as *mut WasiCtxBuilder); + *b = b.inherit_env(); + Box::into_raw(b); + lucet_error::Ok +} + +#[no_mangle] +pub unsafe extern "C" fn lucet_wasi_ctx_inherit_stdio( wasi_ctx: *mut lucet_wasi_ctx, ) -> lucet_error { assert_nonnull!(wasi_ctx); let mut b = Box::from_raw(wasi_ctx as *mut WasiCtxBuilder); - *b = b.inherit_env(); + *b = b.inherit_stdio(); Box::into_raw(b); lucet_error::Ok } @@ -78,7 +91,6 @@ pub unsafe extern "C" fn lucet_region_new_instance_with_wasi_ctx( }) } - /// Call this if you're having trouble with `__wasi_*` symbols not being exported. /// /// This is pretty hackish; we will hopefully be able to avoid this altogether once [this @@ -86,5 +98,5 @@ pub unsafe extern "C" fn lucet_region_new_instance_with_wasi_ctx( #[no_mangle] #[doc(hidden)] pub extern "C" fn lucet_wasi_internal_ensure_linked() { - crate::hostcalls::ensure_linked(); + crate::wasi::export_wasi_funcs(); } diff --git a/lucet-wasi/src/ctx.rs b/lucet-wasi/src/ctx.rs deleted file mode 100644 index fcb9bed5b..000000000 --- a/lucet-wasi/src/ctx.rs +++ /dev/null @@ -1,232 +0,0 @@ -use crate::fdentry::FdEntry; -use crate::host; -use failure::{bail, format_err, Error}; -use nix::unistd::dup; -use std::collections::HashMap; -use std::ffi::{CStr, CString}; -use std::fs::File; -use std::io::{stderr, stdin, stdout}; -use std::os::unix::prelude::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; -use std::path::{Path, PathBuf}; - -pub struct WasiCtxBuilder { - fds: HashMap, - preopens: HashMap, - args: Vec, - env: HashMap, -} - -impl WasiCtxBuilder { - /// Builder for a new `WasiCtx`. - pub fn new() -> Self { - WasiCtxBuilder { - fds: HashMap::new(), - preopens: HashMap::new(), - args: vec![], - env: HashMap::new(), - } - .fd_dup(0, stdin()) - .fd_dup(1, stdout()) - .fd_dup(2, stderr()) - } - - pub fn args(mut self, args: &[&str]) -> Self { - self.args = args - .into_iter() - .map(|arg| CString::new(*arg).expect("argument can be converted to a CString")) - .collect(); - self - } - - pub fn arg(mut self, arg: &str) -> Self { - self.args - .push(CString::new(arg).expect("argument can be converted to a CString")); - self - } - - pub fn c_args>(mut self, args: &[S]) -> Self { - self.args = args - .into_iter() - .map(|arg| arg.as_ref().to_owned()) - .collect(); - self - } - - pub fn c_arg>(mut self, arg: S) -> Self { - self.args.push(arg.as_ref().to_owned()); - self - } - - pub fn inherit_env(mut self) -> Self { - self.env = std::env::vars() - .map(|(k, v)| { - // TODO: handle errors, and possibly assert that the key is valid per POSIX - ( - CString::new(k).expect("environment key can be converted to a CString"), - CString::new(v).expect("environment value can be converted to a CString"), - ) - }) - .collect(); - self - } - - pub fn env(mut self, k: &str, v: &str) -> Self { - self.env.insert( - // TODO: handle errors, and possibly assert that the key is valid per POSIX - CString::new(k).expect("environment key can be converted to a CString"), - CString::new(v).expect("environment value can be converted to a CString"), - ); - self - } - - pub fn c_env(mut self, k: S, v: T) -> Self - where - S: AsRef, - T: AsRef, - { - self.env - .insert(k.as_ref().to_owned(), v.as_ref().to_owned()); - self - } - - /// Add an existing file-like object as a file descriptor in the context. - /// - /// When the `WasiCtx` is dropped, all of its associated file descriptors are `close`d. If you - /// do not want this to close the existing object, use `WasiCtxBuilder::fd_dup()`. - pub fn fd(self, wasm_fd: host::__wasi_fd_t, fd: F) -> Self { - // safe because we're getting a valid RawFd from the F directly - unsafe { self.raw_fd(wasm_fd, fd.into_raw_fd()) } - } - - /// Add an existing file-like object as a duplicate file descriptor in the context. - /// - /// The underlying file descriptor of this object will be duplicated before being added to the - /// context, so it will not be closed when the `WasiCtx` is dropped. - /// - /// TODO: handle `dup` errors - pub fn fd_dup(self, wasm_fd: host::__wasi_fd_t, fd: F) -> Self { - // safe because we're getting a valid RawFd from the F directly - unsafe { self.raw_fd(wasm_fd, dup(fd.as_raw_fd()).unwrap()) } - } - - /// Add an existing file descriptor to the context. - /// - /// When the `WasiCtx` is dropped, this file descriptor will be `close`d. If you do not want to - /// close the existing descriptor, use `WasiCtxBuilder::raw_fd_dup()`. - pub unsafe fn raw_fd(mut self, wasm_fd: host::__wasi_fd_t, fd: RawFd) -> Self { - self.fds.insert(wasm_fd, FdEntry::from_raw_fd(fd)); - self - } - - /// Add a duplicate of an existing file descriptor to the context. - /// - /// The file descriptor will be duplicated before being added to the context, so it will not be - /// closed when the `WasiCtx` is dropped. - /// - /// TODO: handle `dup` errors - pub unsafe fn raw_fd_dup(self, wasm_fd: host::__wasi_fd_t, fd: RawFd) -> Self { - self.raw_fd(wasm_fd, dup(fd).unwrap()) - } - - pub fn preopened_dir>(mut self, dir: File, guest_path: P) -> Self { - self.preopens.insert(guest_path.as_ref().to_owned(), dir); - self - } - - pub fn build(mut self) -> Result { - // startup code starts looking at fd 3 for preopens - let mut preopen_fd = 3; - for (guest_path, dir) in self.preopens { - if !dir.metadata()?.is_dir() { - bail!("preopened file is not a directory"); - } - while self.fds.contains_key(&preopen_fd) { - preopen_fd = preopen_fd - .checked_add(1) - .ok_or(format_err!("not enough file handles"))?; - } - let mut fe = FdEntry::from_file(dir); - fe.preopen_path = Some(guest_path); - self.fds.insert(preopen_fd, fe); - preopen_fd += 1; - } - - let env = self - .env - .into_iter() - .map(|(k, v)| { - let mut pair = k.into_bytes(); - pair.extend_from_slice(b"="); - pair.extend_from_slice(v.to_bytes_with_nul()); - // constructing a new CString from existing CStrings is safe - unsafe { CString::from_vec_unchecked(pair) } - }) - .collect(); - - Ok(WasiCtx { - fds: self.fds, - args: self.args, - env, - }) - } -} - -#[derive(Debug)] -pub struct WasiCtx { - pub fds: HashMap, - pub args: Vec, - pub env: Vec, -} - -impl WasiCtx { - /// Make a new `WasiCtx` with some default settings. - /// - /// - File descriptors 0, 1, and 2 inherit stdin, stdout, and stderr from the host process. - /// - /// - Environment variables are inherited from the host process. - /// - /// To override these behaviors, use `WasiCtxBuilder`. - pub fn new(args: &[&str]) -> WasiCtx { - WasiCtxBuilder::new() - .args(args) - .inherit_env() - .build() - .expect("default options don't fail") - } - - pub fn get_fd_entry( - &self, - fd: host::__wasi_fd_t, - rights_base: host::__wasi_rights_t, - rights_inheriting: host::__wasi_rights_t, - ) -> Result<&FdEntry, host::__wasi_errno_t> { - if let Some(fe) = self.fds.get(&fd) { - // validate rights - if !fe.rights_base & rights_base != 0 || !fe.rights_inheriting & rights_inheriting != 0 - { - Err(host::__WASI_ENOTCAPABLE as host::__wasi_errno_t) - } else { - Ok(fe) - } - } else { - Err(host::__WASI_EBADF as host::__wasi_errno_t) - } - } - - pub fn insert_fd_entry( - &mut self, - fe: FdEntry, - ) -> Result { - // never insert where stdio handles usually are - let mut fd = 3; - while self.fds.contains_key(&fd) { - if let Some(next_fd) = fd.checked_add(1) { - fd = next_fd; - } else { - return Err(host::__WASI_EMFILE as host::__wasi_errno_t); - } - } - self.fds.insert(fd, fe); - Ok(fd) - } -} diff --git a/lucet-wasi/src/fdentry.rs b/lucet-wasi/src/fdentry.rs deleted file mode 100644 index 84d4a793c..000000000 --- a/lucet-wasi/src/fdentry.rs +++ /dev/null @@ -1,143 +0,0 @@ -use crate::host; -use std::fs::File; -use std::os::unix::prelude::{FileTypeExt, FromRawFd, IntoRawFd, RawFd}; -use std::path::PathBuf; - -#[derive(Debug)] -pub struct FdEntry { - pub fd_object: FdObject, - pub rights_base: host::__wasi_rights_t, - pub rights_inheriting: host::__wasi_rights_t, - pub preopen_path: Option, -} - -impl FdEntry { - pub fn from_file(file: File) -> FdEntry { - unsafe { FdEntry::from_raw_fd(file.into_raw_fd()) } - } -} - -impl FromRawFd for FdEntry { - // TODO: make this a different function with error handling, rather than using the trait method - unsafe fn from_raw_fd(rawfd: RawFd) -> FdEntry { - let (ty, mut rights_base, rights_inheriting) = - determine_type_rights(rawfd).expect("can determine file rights"); - - use nix::fcntl::{fcntl, OFlag, F_GETFL}; - let flags_bits = fcntl(rawfd, F_GETFL).expect("fcntl succeeds"); - let flags = OFlag::from_bits_truncate(flags_bits); - let accmode = flags & OFlag::O_ACCMODE; - if accmode == OFlag::O_RDONLY { - rights_base &= !host::__WASI_RIGHT_FD_WRITE as host::__wasi_rights_t; - } else if accmode == OFlag::O_WRONLY { - rights_base &= !host::__WASI_RIGHT_FD_READ as host::__wasi_rights_t; - } - - FdEntry { - fd_object: FdObject { - ty: ty as u8, - rawfd, - needs_close: true, - }, - rights_base, - rights_inheriting, - preopen_path: None, - } - } -} - -// TODO: can probably make this safe by using fcntl directly rather than going through `File` -pub unsafe fn determine_type_rights( - rawfd: RawFd, -) -> Result< - ( - host::__wasi_filetype_t, - host::__wasi_rights_t, - host::__wasi_rights_t, - ), - host::__wasi_errno_t, -> { - let (ty, rights_base, rights_inheriting) = { - let file = File::from_raw_fd(rawfd); - let ft = file.metadata().unwrap().file_type(); - // we just make a `File` here for convenience; we don't want it to close when it drops - std::mem::forget(file); - if ft.is_block_device() { - ( - host::__WASI_FILETYPE_BLOCK_DEVICE, - host::RIGHTS_BLOCK_DEVICE_BASE, - host::RIGHTS_BLOCK_DEVICE_INHERITING, - ) - } else if ft.is_char_device() { - if nix::unistd::isatty(rawfd).unwrap() { - ( - host::__WASI_FILETYPE_CHARACTER_DEVICE, - host::RIGHTS_TTY_BASE, - host::RIGHTS_TTY_BASE, - ) - } else { - ( - host::__WASI_FILETYPE_CHARACTER_DEVICE, - host::RIGHTS_CHARACTER_DEVICE_BASE, - host::RIGHTS_CHARACTER_DEVICE_INHERITING, - ) - } - } else if ft.is_dir() { - ( - host::__WASI_FILETYPE_DIRECTORY, - host::RIGHTS_DIRECTORY_BASE, - host::RIGHTS_DIRECTORY_INHERITING, - ) - } else if ft.is_file() { - ( - host::__WASI_FILETYPE_REGULAR_FILE, - host::RIGHTS_REGULAR_FILE_BASE, - host::RIGHTS_REGULAR_FILE_INHERITING, - ) - } else if ft.is_socket() { - use nix::sys::socket; - match socket::getsockopt(rawfd, socket::sockopt::SockType).unwrap() { - socket::SockType::Datagram => ( - host::__WASI_FILETYPE_SOCKET_DGRAM, - host::RIGHTS_SOCKET_BASE, - host::RIGHTS_SOCKET_INHERITING, - ), - socket::SockType::Stream => ( - host::__WASI_FILETYPE_SOCKET_STREAM, - host::RIGHTS_SOCKET_BASE, - host::RIGHTS_SOCKET_INHERITING, - ), - _ => return Err(host::__WASI_EINVAL as host::__wasi_errno_t), - } - } else if ft.is_fifo() { - ( - host::__WASI_FILETYPE_SOCKET_STREAM, - host::RIGHTS_SOCKET_BASE, - host::RIGHTS_SOCKET_INHERITING, - ) - } else { - return Err(host::__WASI_EINVAL as host::__wasi_errno_t); - } - }; - Ok(( - ty as host::__wasi_filetype_t, - rights_base, - rights_inheriting, - )) -} - -#[derive(Debug)] -pub struct FdObject { - pub ty: host::__wasi_filetype_t, - pub rawfd: RawFd, - pub needs_close: bool, - // TODO: directories -} - -impl Drop for FdObject { - fn drop(&mut self) { - if self.needs_close { - nix::unistd::close(self.rawfd).unwrap_or_else(|e| eprintln!("FdObject::drop(): {}", e)); - } - } -} diff --git a/lucet-wasi/src/host.rs b/lucet-wasi/src/host.rs deleted file mode 100644 index 09ac169c1..000000000 --- a/lucet-wasi/src/host.rs +++ /dev/null @@ -1,271 +0,0 @@ -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] - -include!(concat!(env!("OUT_DIR"), "/wasi_host.rs")); - -pub type void = ::std::os::raw::c_void; - -pub unsafe fn ciovec_to_nix<'a>(ciovec: &'a __wasi_ciovec_t) -> nix::sys::uio::IoVec<&'a [u8]> { - let slice = std::slice::from_raw_parts(ciovec.buf as *const u8, ciovec.buf_len); - nix::sys::uio::IoVec::from_slice(slice) -} - -pub unsafe fn ciovec_to_nix_mut<'a>( - ciovec: &'a mut __wasi_ciovec_t, -) -> nix::sys::uio::IoVec<&'a mut [u8]> { - let slice = std::slice::from_raw_parts_mut(ciovec.buf as *mut u8, ciovec.buf_len); - nix::sys::uio::IoVec::from_mut_slice(slice) -} - -pub fn errno_from_nix(errno: nix::errno::Errno) -> __wasi_errno_t { - let e = match errno { - nix::errno::Errno::EPERM => __WASI_EPERM, - nix::errno::Errno::ENOENT => __WASI_ENOENT, - nix::errno::Errno::ESRCH => __WASI_ESRCH, - nix::errno::Errno::EINTR => __WASI_EINTR, - nix::errno::Errno::EIO => __WASI_EIO, - nix::errno::Errno::ENXIO => __WASI_ENXIO, - nix::errno::Errno::E2BIG => __WASI_E2BIG, - nix::errno::Errno::ENOEXEC => __WASI_ENOEXEC, - nix::errno::Errno::EBADF => __WASI_EBADF, - nix::errno::Errno::ECHILD => __WASI_ECHILD, - nix::errno::Errno::EAGAIN => __WASI_EAGAIN, - nix::errno::Errno::ENOMEM => __WASI_ENOMEM, - nix::errno::Errno::EACCES => __WASI_EACCES, - nix::errno::Errno::EFAULT => __WASI_EFAULT, - nix::errno::Errno::EBUSY => __WASI_EBUSY, - nix::errno::Errno::EEXIST => __WASI_EEXIST, - nix::errno::Errno::EXDEV => __WASI_EXDEV, - nix::errno::Errno::ENODEV => __WASI_ENODEV, - nix::errno::Errno::ENOTDIR => __WASI_ENOTDIR, - nix::errno::Errno::EISDIR => __WASI_EISDIR, - nix::errno::Errno::EINVAL => __WASI_EINVAL, - nix::errno::Errno::ENFILE => __WASI_ENFILE, - nix::errno::Errno::EMFILE => __WASI_EMFILE, - nix::errno::Errno::ENOTTY => __WASI_ENOTTY, - nix::errno::Errno::ETXTBSY => __WASI_ETXTBSY, - nix::errno::Errno::EFBIG => __WASI_EFBIG, - nix::errno::Errno::ENOSPC => __WASI_ENOSPC, - nix::errno::Errno::ESPIPE => __WASI_ESPIPE, - nix::errno::Errno::EROFS => __WASI_EROFS, - nix::errno::Errno::EMLINK => __WASI_EMLINK, - nix::errno::Errno::EPIPE => __WASI_EPIPE, - nix::errno::Errno::EDOM => __WASI_EDOM, - nix::errno::Errno::ERANGE => __WASI_ERANGE, - nix::errno::Errno::EDEADLK => __WASI_EDEADLK, - nix::errno::Errno::ENAMETOOLONG => __WASI_ENAMETOOLONG, - nix::errno::Errno::ENOLCK => __WASI_ENOLCK, - nix::errno::Errno::ENOSYS => __WASI_ENOSYS, - nix::errno::Errno::ENOTEMPTY => __WASI_ENOTEMPTY, - nix::errno::Errno::ELOOP => __WASI_ELOOP, - nix::errno::Errno::ENOMSG => __WASI_ENOMSG, - nix::errno::Errno::EIDRM => __WASI_EIDRM, - nix::errno::Errno::ENOLINK => __WASI_ENOLINK, - nix::errno::Errno::EPROTO => __WASI_EPROTO, - nix::errno::Errno::EMULTIHOP => __WASI_EMULTIHOP, - nix::errno::Errno::EBADMSG => __WASI_EBADMSG, - nix::errno::Errno::EOVERFLOW => __WASI_EOVERFLOW, - nix::errno::Errno::EILSEQ => __WASI_EILSEQ, - nix::errno::Errno::ENOTSOCK => __WASI_ENOTSOCK, - nix::errno::Errno::EDESTADDRREQ => __WASI_EDESTADDRREQ, - nix::errno::Errno::EMSGSIZE => __WASI_EMSGSIZE, - nix::errno::Errno::EPROTOTYPE => __WASI_EPROTOTYPE, - nix::errno::Errno::ENOPROTOOPT => __WASI_ENOPROTOOPT, - nix::errno::Errno::EPROTONOSUPPORT => __WASI_EPROTONOSUPPORT, - nix::errno::Errno::EAFNOSUPPORT => __WASI_EAFNOSUPPORT, - nix::errno::Errno::EADDRINUSE => __WASI_EADDRINUSE, - nix::errno::Errno::EADDRNOTAVAIL => __WASI_EADDRNOTAVAIL, - nix::errno::Errno::ENETDOWN => __WASI_ENETDOWN, - nix::errno::Errno::ENETUNREACH => __WASI_ENETUNREACH, - nix::errno::Errno::ENETRESET => __WASI_ENETRESET, - nix::errno::Errno::ECONNABORTED => __WASI_ECONNABORTED, - nix::errno::Errno::ECONNRESET => __WASI_ECONNRESET, - nix::errno::Errno::ENOBUFS => __WASI_ENOBUFS, - nix::errno::Errno::EISCONN => __WASI_EISCONN, - nix::errno::Errno::ENOTCONN => __WASI_ENOTCONN, - nix::errno::Errno::ETIMEDOUT => __WASI_ETIMEDOUT, - nix::errno::Errno::ECONNREFUSED => __WASI_ECONNREFUSED, - nix::errno::Errno::EHOSTUNREACH => __WASI_EHOSTUNREACH, - nix::errno::Errno::EALREADY => __WASI_EALREADY, - nix::errno::Errno::EINPROGRESS => __WASI_EINPROGRESS, - nix::errno::Errno::ESTALE => __WASI_ESTALE, - nix::errno::Errno::EDQUOT => __WASI_EDQUOT, - nix::errno::Errno::ECANCELED => __WASI_ECANCELED, - nix::errno::Errno::EOWNERDEAD => __WASI_EOWNERDEAD, - nix::errno::Errno::ENOTRECOVERABLE => __WASI_ENOTRECOVERABLE, - _ => __WASI_ENOSYS, - }; - e as __wasi_errno_t -} - -pub fn nix_from_fdflags(fdflags: __wasi_fdflags_t) -> nix::fcntl::OFlag { - use nix::fcntl::OFlag; - let mut nix_flags = OFlag::empty(); - if fdflags & (__WASI_FDFLAG_APPEND as __wasi_fdflags_t) != 0 { - nix_flags.insert(OFlag::O_APPEND); - } - if fdflags & (__WASI_FDFLAG_DSYNC as __wasi_fdflags_t) != 0 { - nix_flags.insert(OFlag::O_DSYNC); - } - if fdflags & (__WASI_FDFLAG_NONBLOCK as __wasi_fdflags_t) != 0 { - nix_flags.insert(OFlag::O_NONBLOCK); - } - if fdflags & (__WASI_FDFLAG_RSYNC as __wasi_fdflags_t) != 0 { - nix_flags.insert(OFlag::O_RSYNC); - } - if fdflags & (__WASI_FDFLAG_SYNC as __wasi_fdflags_t) != 0 { - nix_flags.insert(OFlag::O_SYNC); - } - nix_flags -} - -pub fn fdflags_from_nix(oflags: nix::fcntl::OFlag) -> __wasi_fdflags_t { - use nix::fcntl::OFlag; - let mut fdflags = 0; - if oflags.contains(OFlag::O_APPEND) { - fdflags |= __WASI_FDFLAG_APPEND; - } - if oflags.contains(OFlag::O_DSYNC) { - fdflags |= __WASI_FDFLAG_DSYNC; - } - if oflags.contains(OFlag::O_NONBLOCK) { - fdflags |= __WASI_FDFLAG_NONBLOCK; - } - if oflags.contains(OFlag::O_RSYNC) { - fdflags |= __WASI_FDFLAG_RSYNC; - } - if oflags.contains(OFlag::O_SYNC) { - fdflags |= __WASI_FDFLAG_SYNC; - } - fdflags as __wasi_fdflags_t -} - -pub fn nix_from_oflags(oflags: __wasi_oflags_t) -> nix::fcntl::OFlag { - use nix::fcntl::OFlag; - let mut nix_flags = OFlag::empty(); - if oflags & (__WASI_O_CREAT as __wasi_oflags_t) != 0 { - nix_flags.insert(OFlag::O_CREAT); - } - if oflags & (__WASI_O_DIRECTORY as __wasi_oflags_t) != 0 { - nix_flags.insert(OFlag::O_DIRECTORY); - } - if oflags & (__WASI_O_EXCL as __wasi_oflags_t) != 0 { - nix_flags.insert(OFlag::O_EXCL); - } - if oflags & (__WASI_O_TRUNC as __wasi_oflags_t) != 0 { - nix_flags.insert(OFlag::O_TRUNC); - } - nix_flags -} - -// Rights sets from wasmtime-wasi sandboxed system primitives. Transcribed because bindgen can't -// parse the #defines. - -pub const RIGHTS_ALL: __wasi_rights_t = (__WASI_RIGHT_FD_DATASYNC - | __WASI_RIGHT_FD_READ - | __WASI_RIGHT_FD_SEEK - | __WASI_RIGHT_FD_FDSTAT_SET_FLAGS - | __WASI_RIGHT_FD_SYNC - | __WASI_RIGHT_FD_TELL - | __WASI_RIGHT_FD_WRITE - | __WASI_RIGHT_FD_ADVISE - | __WASI_RIGHT_FD_ALLOCATE - | __WASI_RIGHT_PATH_CREATE_DIRECTORY - | __WASI_RIGHT_PATH_CREATE_FILE - | __WASI_RIGHT_PATH_LINK_SOURCE - | __WASI_RIGHT_PATH_LINK_TARGET - | __WASI_RIGHT_PATH_OPEN - | __WASI_RIGHT_FD_READDIR - | __WASI_RIGHT_PATH_READLINK - | __WASI_RIGHT_PATH_RENAME_SOURCE - | __WASI_RIGHT_PATH_RENAME_TARGET - | __WASI_RIGHT_PATH_FILESTAT_GET - | __WASI_RIGHT_PATH_FILESTAT_SET_SIZE - | __WASI_RIGHT_PATH_FILESTAT_SET_TIMES - | __WASI_RIGHT_FD_FILESTAT_GET - | __WASI_RIGHT_FD_FILESTAT_SET_SIZE - | __WASI_RIGHT_FD_FILESTAT_SET_TIMES - | __WASI_RIGHT_PATH_SYMLINK - | __WASI_RIGHT_PATH_UNLINK_FILE - | __WASI_RIGHT_PATH_REMOVE_DIRECTORY - | __WASI_RIGHT_POLL_FD_READWRITE - | __WASI_RIGHT_SOCK_SHUTDOWN) as __wasi_rights_t; - -// Block and character device interaction is outside the scope of -// CloudABI. Simply allow everything. -pub const RIGHTS_BLOCK_DEVICE_BASE: __wasi_rights_t = RIGHTS_ALL; -pub const RIGHTS_BLOCK_DEVICE_INHERITING: __wasi_rights_t = RIGHTS_ALL; -pub const RIGHTS_CHARACTER_DEVICE_BASE: __wasi_rights_t = RIGHTS_ALL; -pub const RIGHTS_CHARACTER_DEVICE_INHERITING: __wasi_rights_t = RIGHTS_ALL; - -// Only allow directory operations on directories. Directories can only -// yield file descriptors to other directories and files. -pub const RIGHTS_DIRECTORY_BASE: __wasi_rights_t = (__WASI_RIGHT_FD_FDSTAT_SET_FLAGS - | __WASI_RIGHT_FD_SYNC - | __WASI_RIGHT_FD_ADVISE - | __WASI_RIGHT_PATH_CREATE_DIRECTORY - | __WASI_RIGHT_PATH_CREATE_FILE - | __WASI_RIGHT_PATH_LINK_SOURCE - | __WASI_RIGHT_PATH_LINK_TARGET - | __WASI_RIGHT_PATH_OPEN - | __WASI_RIGHT_FD_READDIR - | __WASI_RIGHT_PATH_READLINK - | __WASI_RIGHT_PATH_RENAME_SOURCE - | __WASI_RIGHT_PATH_RENAME_TARGET - | __WASI_RIGHT_PATH_FILESTAT_GET - | __WASI_RIGHT_PATH_FILESTAT_SET_SIZE - | __WASI_RIGHT_PATH_FILESTAT_SET_TIMES - | __WASI_RIGHT_FD_FILESTAT_GET - | __WASI_RIGHT_FD_FILESTAT_SET_SIZE - | __WASI_RIGHT_FD_FILESTAT_SET_TIMES - | __WASI_RIGHT_PATH_SYMLINK - | __WASI_RIGHT_PATH_UNLINK_FILE - | __WASI_RIGHT_PATH_REMOVE_DIRECTORY - | __WASI_RIGHT_POLL_FD_READWRITE) - as __wasi_rights_t; -pub const RIGHTS_DIRECTORY_INHERITING: __wasi_rights_t = - (RIGHTS_DIRECTORY_BASE | RIGHTS_REGULAR_FILE_BASE); - -// Operations that apply to regular files. -pub const RIGHTS_REGULAR_FILE_BASE: __wasi_rights_t = (__WASI_RIGHT_FD_DATASYNC - | __WASI_RIGHT_FD_READ - | __WASI_RIGHT_FD_SEEK - | __WASI_RIGHT_FD_FDSTAT_SET_FLAGS - | __WASI_RIGHT_FD_SYNC - | __WASI_RIGHT_FD_TELL - | __WASI_RIGHT_FD_WRITE - | __WASI_RIGHT_FD_ADVISE - | __WASI_RIGHT_FD_ALLOCATE - | __WASI_RIGHT_FD_FILESTAT_GET - | __WASI_RIGHT_FD_FILESTAT_SET_SIZE - | __WASI_RIGHT_FD_FILESTAT_SET_TIMES - | __WASI_RIGHT_POLL_FD_READWRITE) - as __wasi_rights_t; -pub const RIGHTS_REGULAR_FILE_INHERITING: __wasi_rights_t = 0; - -// Operations that apply to shared memory objects. -pub const RIGHTS_SHARED_MEMORY_BASE: __wasi_rights_t = (__WASI_RIGHT_FD_READ - | __WASI_RIGHT_FD_WRITE - | __WASI_RIGHT_FD_FILESTAT_GET - | __WASI_RIGHT_FD_FILESTAT_SET_SIZE) - as __wasi_rights_t; -pub const RIGHTS_SHARED_MEMORY_INHERITING: __wasi_rights_t = 0; - -// Operations that apply to sockets and socket pairs. -pub const RIGHTS_SOCKET_BASE: __wasi_rights_t = (__WASI_RIGHT_FD_READ - | __WASI_RIGHT_FD_FDSTAT_SET_FLAGS - | __WASI_RIGHT_FD_WRITE - | __WASI_RIGHT_FD_FILESTAT_GET - | __WASI_RIGHT_POLL_FD_READWRITE - | __WASI_RIGHT_SOCK_SHUTDOWN) - as __wasi_rights_t; -pub const RIGHTS_SOCKET_INHERITING: __wasi_rights_t = RIGHTS_ALL; - -// Operations that apply to TTYs. -pub const RIGHTS_TTY_BASE: __wasi_rights_t = (__WASI_RIGHT_FD_READ - | __WASI_RIGHT_FD_FDSTAT_SET_FLAGS - | __WASI_RIGHT_FD_WRITE - | __WASI_RIGHT_FD_FILESTAT_GET - | __WASI_RIGHT_POLL_FD_READWRITE) - as __wasi_rights_t; -pub const RIGHTS_TTY_INHERITING: __wasi_rights_t = 0; diff --git a/lucet-wasi/src/hostcalls.rs b/lucet-wasi/src/hostcalls.rs deleted file mode 100644 index 3eff88d6d..000000000 --- a/lucet-wasi/src/hostcalls.rs +++ /dev/null @@ -1,988 +0,0 @@ -//! Hostcalls that implement -//! [WASI](https://github.com/CraneStation/wasmtime-wasi/blob/wasi/docs/WASI-overview.md). -//! -//! This code borrows heavily from [wasmtime-wasi](https://github.com/CraneStation/wasmtime-wasi), -//! which in turn borrows from cloudabi-utils. See `LICENSE.wasmtime-wasi` for license information. -//! -//! This is currently a very incomplete prototype, only supporting the hostcalls required to run -//! `/examples/hello.c`, and using a bare-bones translation of the capabilities system rather than -//! something nice. - -#![allow(non_camel_case_types)] -use crate::ctx::WasiCtx; -use crate::fdentry::{determine_type_rights, FdEntry}; -use crate::memory::*; -use crate::{host, wasm32}; -use cast::From as _0; -use lucet_runtime::vmctx::{lucet_vmctx, Vmctx}; -use std::ffi::{OsStr, OsString}; -use std::os::unix::prelude::{FromRawFd, OsStrExt, OsStringExt, RawFd}; - -#[no_mangle] -pub extern "C" fn __wasi_proc_exit(vmctx: *mut lucet_vmctx, rval: wasm32::__wasi_exitcode_t) -> ! { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - vmctx.terminate(dec_exitcode(rval)) -} - -#[no_mangle] -pub extern "C" fn __wasi_args_get( - vmctx_raw: *mut lucet_vmctx, - argv_ptr: wasm32::uintptr_t, - argv_buf: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx_raw) }; - let ctx: &WasiCtx = vmctx.get_embed_ctx(); - - let mut argv_buf_offset = 0; - let mut argv = vec![]; - - for arg in ctx.args.iter() { - let arg_bytes = arg.as_bytes_with_nul(); - let arg_ptr = argv_buf + argv_buf_offset; - - // nasty aliasing here, but we aren't interfering with the borrow for `ctx` - // TODO: rework vmctx interface to avoid this - let mut vmctx = unsafe { Vmctx::from_raw(vmctx_raw) }; - if let Err(e) = unsafe { enc_slice_of(&mut vmctx, arg_bytes, arg_ptr) } { - return enc_errno(e); - } - - argv.push(arg_ptr); - - argv_buf_offset = if let Some(new_offset) = argv_buf_offset.checked_add( - wasm32::uintptr_t::cast(arg_bytes.len()) - .expect("cast overflow would have been caught by `enc_slice_of` above"), - ) { - new_offset - } else { - return wasm32::__WASI_EOVERFLOW; - } - } - - unsafe { - enc_slice_of(&mut vmctx, argv.as_slice(), argv_ptr) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) - } -} - -#[no_mangle] -pub extern "C" fn __wasi_args_sizes_get( - vmctx: *mut lucet_vmctx, - argc_ptr: wasm32::uintptr_t, - argv_buf_size_ptr: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - - let ctx: &WasiCtx = vmctx.get_embed_ctx(); - - let argc = ctx.args.len(); - let argv_size = ctx - .args - .iter() - .map(|arg| arg.as_bytes_with_nul().len()) - .sum(); - - unsafe { - if let Err(e) = enc_usize_byref(&mut vmctx, argc_ptr, argc) { - return enc_errno(e); - } - if let Err(e) = enc_usize_byref(&mut vmctx, argv_buf_size_ptr, argv_size) { - return enc_errno(e); - } - } - wasm32::__WASI_ESUCCESS -} - -#[no_mangle] -pub extern "C" fn __wasi_clock_res_get( - vmctx: *mut lucet_vmctx, - clock_id: wasm32::__wasi_clockid_t, - resolution_ptr: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - - // convert the supported clocks to the libc types, or return EINVAL - let clock_id = match dec_clockid(clock_id) { - host::__WASI_CLOCK_REALTIME => libc::CLOCK_REALTIME, - host::__WASI_CLOCK_MONOTONIC => libc::CLOCK_MONOTONIC, - host::__WASI_CLOCK_PROCESS_CPUTIME_ID => libc::CLOCK_PROCESS_CPUTIME_ID, - host::__WASI_CLOCK_THREAD_CPUTIME_ID => libc::CLOCK_THREAD_CPUTIME_ID, - _ => return wasm32::__WASI_EINVAL, - }; - - // no `nix` wrapper for clock_getres, so we do it ourselves - let mut timespec = unsafe { std::mem::uninitialized::() }; - let res = unsafe { libc::clock_getres(clock_id, &mut timespec as *mut libc::timespec) }; - if res != 0 { - return wasm32::errno_from_nix(nix::errno::Errno::last()); - } - - // convert to nanoseconds, returning EOVERFLOW in case of overflow; this is freelancing a bit - // from the spec but seems like it'll be an unusual situation to hit - (timespec.tv_sec as host::__wasi_timestamp_t) - .checked_mul(1_000_000_000) - .and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as host::__wasi_timestamp_t)) - .map(|resolution| { - // a supported clock can never return zero; this case will probably never get hit, but - // make sure we follow the spec - if resolution == 0 { - wasm32::__WASI_EINVAL - } else { - unsafe { - enc_timestamp_byref(&mut vmctx, resolution_ptr, resolution) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) - } - } - }) - .unwrap_or(wasm32::__WASI_EOVERFLOW) -} - -#[no_mangle] -pub extern "C" fn __wasi_clock_time_get( - vmctx: *mut lucet_vmctx, - clock_id: wasm32::__wasi_clockid_t, - // ignored for now, but will be useful once we put optional limits on precision to reduce side - // channels - _precision: wasm32::__wasi_timestamp_t, - time_ptr: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - - // convert the supported clocks to the libc types, or return EINVAL - let clock_id = match dec_clockid(clock_id) { - host::__WASI_CLOCK_REALTIME => libc::CLOCK_REALTIME, - host::__WASI_CLOCK_MONOTONIC => libc::CLOCK_MONOTONIC, - host::__WASI_CLOCK_PROCESS_CPUTIME_ID => libc::CLOCK_PROCESS_CPUTIME_ID, - host::__WASI_CLOCK_THREAD_CPUTIME_ID => libc::CLOCK_THREAD_CPUTIME_ID, - _ => return wasm32::__WASI_EINVAL, - }; - - // no `nix` wrapper for clock_getres, so we do it ourselves - let mut timespec = unsafe { std::mem::uninitialized::() }; - let res = unsafe { libc::clock_gettime(clock_id, &mut timespec as *mut libc::timespec) }; - if res != 0 { - return wasm32::errno_from_nix(nix::errno::Errno::last()); - } - - // convert to nanoseconds, returning EOVERFLOW in case of overflow; this is freelancing a bit - // from the spec but seems like it'll be an unusual situation to hit - (timespec.tv_sec as host::__wasi_timestamp_t) - .checked_mul(1_000_000_000) - .and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as host::__wasi_timestamp_t)) - .map(|time| unsafe { - enc_timestamp_byref(&mut vmctx, time_ptr, time) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) - }) - .unwrap_or(wasm32::__WASI_EOVERFLOW) -} - -#[no_mangle] -pub extern "C" fn __wasi_environ_get( - vmctx_raw: *mut lucet_vmctx, - environ_ptr: wasm32::uintptr_t, - environ_buf: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx_raw) }; - let ctx: &WasiCtx = vmctx.get_embed_ctx(); - - let mut environ_buf_offset = 0; - let mut environ = vec![]; - - for pair in ctx.env.iter() { - let env_bytes = pair.as_bytes_with_nul(); - let env_ptr = environ_buf + environ_buf_offset; - - // nasty aliasing here, but we aren't interfering with the borrow for `ctx` - // TODO: rework vmctx interface to avoid this - let mut vmctx = unsafe { Vmctx::from_raw(vmctx_raw) }; - if let Err(e) = unsafe { enc_slice_of(&mut vmctx, env_bytes, env_ptr) } { - return enc_errno(e); - } - - environ.push(env_ptr); - - environ_buf_offset = if let Some(new_offset) = environ_buf_offset.checked_add( - wasm32::uintptr_t::cast(env_bytes.len()) - .expect("cast overflow would have been caught by `enc_slice_of` above"), - ) { - new_offset - } else { - return wasm32::__WASI_EOVERFLOW; - } - } - - unsafe { - enc_slice_of(&mut vmctx, environ.as_slice(), environ_ptr) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) - } -} - -#[no_mangle] -pub extern "C" fn __wasi_environ_sizes_get( - vmctx: *mut lucet_vmctx, - environ_count_ptr: wasm32::uintptr_t, - environ_size_ptr: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - - let ctx: &WasiCtx = vmctx.get_embed_ctx(); - - let environ_count = ctx.env.len(); - if let Some(environ_size) = ctx.env.iter().try_fold(0, |acc: u32, pair| { - acc.checked_add(pair.as_bytes_with_nul().len() as u32) - }) { - unsafe { - if let Err(e) = enc_usize_byref(&mut vmctx, environ_count_ptr, environ_count) { - return enc_errno(e); - } - if let Err(e) = enc_usize_byref(&mut vmctx, environ_size_ptr, environ_size as usize) { - return enc_errno(e); - } - } - wasm32::__WASI_ESUCCESS - } else { - wasm32::__WASI_EOVERFLOW - } -} - -#[no_mangle] -pub extern "C" fn __wasi_fd_close( - vmctx: *mut lucet_vmctx, - fd: wasm32::__wasi_fd_t, -) -> wasm32::__wasi_errno_t { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); - let fd = dec_fd(fd); - if let Some(fdent) = ctx.fds.get(&fd) { - // can't close preopened files - if fdent.preopen_path.is_some() { - return wasm32::__WASI_ENOTSUP; - } - } - if let Some(mut fdent) = ctx.fds.remove(&fd) { - fdent.fd_object.needs_close = false; - match nix::unistd::close(fdent.fd_object.rawfd) { - Ok(_) => wasm32::__WASI_ESUCCESS, - Err(e) => wasm32::errno_from_nix(e.as_errno().unwrap()), - } - } else { - wasm32::__WASI_EBADF - } -} - -#[no_mangle] -pub extern "C" fn __wasi_fd_fdstat_get( - vmctx: *mut lucet_vmctx, - fd: wasm32::__wasi_fd_t, - fdstat_ptr: wasm32::uintptr_t, // *mut wasm32::__wasi_fdstat_t -) -> wasm32::__wasi_errno_t { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - - let host_fd = dec_fd(fd); - let mut host_fdstat = match unsafe { dec_fdstat_byref(&mut vmctx, fdstat_ptr) } { - Ok(host_fdstat) => host_fdstat, - Err(e) => return enc_errno(e), - }; - - let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); - let errno = if let Some(fe) = ctx.fds.get(&host_fd) { - host_fdstat.fs_filetype = fe.fd_object.ty; - host_fdstat.fs_rights_base = fe.rights_base; - host_fdstat.fs_rights_inheriting = fe.rights_inheriting; - use nix::fcntl::{fcntl, OFlag, F_GETFL}; - match fcntl(fe.fd_object.rawfd, F_GETFL).map(OFlag::from_bits_truncate) { - Ok(flags) => { - host_fdstat.fs_flags = host::fdflags_from_nix(flags); - wasm32::__WASI_ESUCCESS - } - Err(e) => wasm32::errno_from_nix(e.as_errno().unwrap()), - } - } else { - wasm32::__WASI_EBADF - }; - - unsafe { - enc_fdstat_byref(&mut vmctx, fdstat_ptr, host_fdstat) - .expect("can write back into the pointer we read from"); - } - - errno -} - -#[no_mangle] -pub extern "C" fn __wasi_fd_fdstat_set_flags( - vmctx: *mut lucet_vmctx, - fd: wasm32::__wasi_fd_t, - fdflags: wasm32::__wasi_fdflags_t, -) -> wasm32::__wasi_errno_t { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - - let host_fd = dec_fd(fd); - let host_fdflags = dec_fdflags(fdflags); - let nix_flags = host::nix_from_fdflags(host_fdflags); - - let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); - - if let Some(fe) = ctx.fds.get(&host_fd) { - match nix::fcntl::fcntl(fe.fd_object.rawfd, nix::fcntl::F_SETFL(nix_flags)) { - Ok(_) => wasm32::__WASI_ESUCCESS, - Err(e) => wasm32::errno_from_nix(e.as_errno().unwrap()), - } - } else { - wasm32::__WASI_EBADF - } -} - -#[no_mangle] -pub extern "C" fn __wasi_fd_seek( - vmctx: *mut lucet_vmctx, - fd: wasm32::__wasi_fd_t, - offset: wasm32::__wasi_filedelta_t, - whence: wasm32::__wasi_whence_t, - newoffset: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); - let fd = dec_fd(fd); - let offset = dec_filedelta(offset); - let whence = dec_whence(whence); - - let host_newoffset = { - use nix::unistd::{lseek, Whence}; - let nwhence = match whence as u32 { - host::__WASI_WHENCE_CUR => Whence::SeekCur, - host::__WASI_WHENCE_END => Whence::SeekEnd, - host::__WASI_WHENCE_SET => Whence::SeekSet, - _ => return wasm32::__WASI_EINVAL, - }; - - let rights = if offset == 0 && whence as u32 == host::__WASI_WHENCE_CUR { - host::__WASI_RIGHT_FD_TELL - } else { - host::__WASI_RIGHT_FD_SEEK | host::__WASI_RIGHT_FD_TELL - }; - match ctx.get_fd_entry(fd, rights.into(), 0) { - Ok(fe) => match lseek(fe.fd_object.rawfd, offset, nwhence) { - Ok(newoffset) => newoffset, - Err(e) => return wasm32::errno_from_nix(e.as_errno().unwrap()), - }, - Err(e) => return enc_errno(e), - } - }; - - unsafe { - enc_filesize_byref(&mut vmctx, newoffset, host_newoffset as u64) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) - } -} - -#[no_mangle] -pub extern "C" fn __wasi_fd_prestat_get( - vmctx_raw: *mut lucet_vmctx, - fd: wasm32::__wasi_fd_t, - prestat_ptr: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - let vmctx = unsafe { Vmctx::from_raw(vmctx_raw) }; - let ctx: &WasiCtx = vmctx.get_embed_ctx(); - let fd = dec_fd(fd); - // TODO: is this the correct right for this? - match ctx.get_fd_entry(fd, host::__WASI_RIGHT_PATH_OPEN.into(), 0) { - Ok(fe) => { - if let Some(po_path) = &fe.preopen_path { - if fe.fd_object.ty != host::__WASI_FILETYPE_DIRECTORY as host::__wasi_filetype_t { - return wasm32::__WASI_ENOTDIR; - } - // nasty aliasing here, but we aren't interfering with the borrow for `ctx` - // TODO: rework vmctx interface to avoid this - unsafe { - enc_prestat_byref( - &mut Vmctx::from_raw(vmctx_raw), - prestat_ptr, - host::__wasi_prestat_t { - pr_type: host::__WASI_PREOPENTYPE_DIR as host::__wasi_preopentype_t, - u: host::__wasi_prestat_t___wasi_prestat_u { - dir: - host::__wasi_prestat_t___wasi_prestat_u___wasi_prestat_u_dir_t { - pr_name_len: po_path.as_os_str().as_bytes().len(), - }, - }, - }, - ) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) - } - } else { - wasm32::__WASI_ENOTSUP - } - } - Err(e) => enc_errno(e), - } -} - -#[no_mangle] -pub extern "C" fn __wasi_fd_prestat_dir_name( - vmctx_raw: *mut lucet_vmctx, - fd: wasm32::__wasi_fd_t, - path_ptr: wasm32::uintptr_t, - path_len: wasm32::size_t, -) -> wasm32::__wasi_errno_t { - let vmctx = unsafe { Vmctx::from_raw(vmctx_raw) }; - let ctx: &WasiCtx = vmctx.get_embed_ctx(); - let fd = dec_fd(fd); - match ctx.get_fd_entry(fd, host::__WASI_RIGHT_PATH_OPEN.into(), 0) { - Ok(fe) => { - if let Some(po_path) = &fe.preopen_path { - if fe.fd_object.ty != host::__WASI_FILETYPE_DIRECTORY as host::__wasi_filetype_t { - return wasm32::__WASI_ENOTDIR; - } - let path_bytes = po_path.as_os_str().as_bytes(); - if path_bytes.len() > dec_usize(path_len) { - return wasm32::__WASI_ENAMETOOLONG; - } - // nasty aliasing here, but we aren't interfering with the borrow for `ctx` - // TODO: rework vmctx interface to avoid this - unsafe { - enc_slice_of(&mut Vmctx::from_raw(vmctx_raw), path_bytes, path_ptr) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) - } - } else { - wasm32::__WASI_ENOTSUP - } - } - Err(e) => enc_errno(e), - } -} - -#[no_mangle] -pub extern "C" fn __wasi_fd_read( - vmctx: *mut lucet_vmctx, - fd: wasm32::__wasi_fd_t, - iovs_ptr: wasm32::uintptr_t, - iovs_len: wasm32::size_t, - nread: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - use nix::sys::uio::{readv, IoVec}; - - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - let fd = dec_fd(fd); - let mut iovs = match unsafe { dec_ciovec_slice(&mut vmctx, iovs_ptr, iovs_len) } { - Ok(iovs) => iovs, - Err(e) => return enc_errno(e), - }; - - let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); - let fe = match ctx.get_fd_entry(fd, host::__WASI_RIGHT_FD_READ.into(), 0) { - Ok(fe) => fe, - Err(e) => return enc_errno(e), - }; - - let mut iovs: Vec> = iovs - .iter_mut() - .map(|iov| unsafe { host::ciovec_to_nix_mut(iov) }) - .collect(); - - let full_nread = iovs.iter().map(|iov| iov.as_slice().len()).sum(); - - let host_nread = match readv(fe.fd_object.rawfd, &mut iovs) { - Ok(len) => len, - Err(e) => return wasm32::errno_from_nix(e.as_errno().unwrap()), - }; - - if host_nread < full_nread { - // we hit eof, so remove the fdentry from the context - let mut fe = ctx.fds.remove(&fd).expect("file entry is still there"); - fe.fd_object.needs_close = false; - } - - unsafe { - enc_usize_byref(&mut vmctx, nread, host_nread) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) - } -} - -#[no_mangle] -pub extern "C" fn __wasi_fd_write( - vmctx: *mut lucet_vmctx, - fd: wasm32::__wasi_fd_t, - iovs_ptr: wasm32::uintptr_t, - iovs_len: wasm32::size_t, - nwritten: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - use nix::sys::uio::{writev, IoVec}; - - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - let fd = dec_fd(fd); - let iovs = match unsafe { dec_ciovec_slice(&mut vmctx, iovs_ptr, iovs_len) } { - Ok(iovs) => iovs, - Err(e) => return enc_errno(e), - }; - - let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); - let fe = match ctx.get_fd_entry(fd, host::__WASI_RIGHT_FD_WRITE.into(), 0) { - Ok(fe) => fe, - Err(e) => return enc_errno(e), - }; - - let iovs: Vec> = iovs - .iter() - .map(|iov| unsafe { host::ciovec_to_nix(iov) }) - .collect(); - - let host_nwritten = match writev(fe.fd_object.rawfd, &iovs) { - Ok(len) => len, - Err(e) => return wasm32::errno_from_nix(e.as_errno().unwrap()), - }; - - unsafe { - enc_usize_byref(&mut vmctx, nwritten, host_nwritten) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) - } -} - -#[no_mangle] -pub extern "C" fn __wasi_path_open( - vmctx: *mut lucet_vmctx, - dirfd: wasm32::__wasi_fd_t, - dirflags: wasm32::__wasi_lookupflags_t, - path_ptr: wasm32::uintptr_t, - path_len: wasm32::size_t, - oflags: wasm32::__wasi_oflags_t, - fs_rights_base: wasm32::__wasi_rights_t, - fs_rights_inheriting: wasm32::__wasi_rights_t, - fs_flags: wasm32::__wasi_fdflags_t, - fd_out_ptr: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - use nix::errno::Errno; - use nix::fcntl::{openat, AtFlags, OFlag}; - use nix::sys::stat::{fstatat, Mode, SFlag}; - - let dirfd = dec_fd(dirfd); - let dirflags = dec_lookupflags(dirflags); - let oflags = dec_oflags(oflags); - let fs_rights_base = dec_rights(fs_rights_base); - let fs_rights_inheriting = dec_rights(fs_rights_inheriting); - let fs_flags = dec_fdflags(fs_flags); - - // which open mode do we need? - let read = fs_rights_base - & ((host::__WASI_RIGHT_FD_READ | host::__WASI_RIGHT_FD_READDIR) as host::__wasi_rights_t) - != 0; - let write = fs_rights_base - & ((host::__WASI_RIGHT_FD_DATASYNC - | host::__WASI_RIGHT_FD_WRITE - | host::__WASI_RIGHT_FD_ALLOCATE - | host::__WASI_RIGHT_PATH_FILESTAT_SET_SIZE) as host::__wasi_rights_t) - != 0; - - let mut nix_all_oflags = if read && write { - OFlag::O_RDWR - } else if read { - OFlag::O_RDONLY - } else { - OFlag::O_WRONLY - }; - - // on non-Capsicum systems, we always want nofollow - nix_all_oflags.insert(OFlag::O_NOFOLLOW); - - // which rights are needed on the dirfd? - let mut needed_base = host::__WASI_RIGHT_PATH_OPEN as host::__wasi_rights_t; - let mut needed_inheriting = fs_rights_base | fs_rights_inheriting; - - // convert open flags - let nix_oflags = host::nix_from_oflags(oflags); - nix_all_oflags.insert(nix_oflags); - if nix_all_oflags.contains(OFlag::O_CREAT) { - needed_base |= host::__WASI_RIGHT_PATH_CREATE_FILE as host::__wasi_rights_t; - } - if nix_all_oflags.contains(OFlag::O_TRUNC) { - needed_inheriting |= host::__WASI_RIGHT_PATH_FILESTAT_SET_SIZE as host::__wasi_rights_t; - } - - // convert file descriptor flags - nix_all_oflags.insert(host::nix_from_fdflags(fs_flags)); - if nix_all_oflags.contains(OFlag::O_DSYNC) { - needed_inheriting |= host::__WASI_RIGHT_FD_DATASYNC as host::__wasi_rights_t; - } - if nix_all_oflags.intersects(OFlag::O_RSYNC | OFlag::O_SYNC) { - needed_inheriting |= host::__WASI_RIGHT_FD_SYNC as host::__wasi_rights_t; - } - - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - - let path = match unsafe { dec_slice_of::(&mut vmctx, path_ptr, path_len) } { - Ok((ptr, len)) => OsStr::from_bytes(unsafe { std::slice::from_raw_parts(ptr, len) }), - Err(e) => return enc_errno(e), - }; - - let (dir, path) = match path_get( - &vmctx, - dirfd, - dirflags, - path, - needed_base, - needed_inheriting, - nix_oflags.contains(OFlag::O_CREAT), - ) { - Ok((dir, path)) => (dir, path), - Err(e) => return enc_errno(e), - }; - - let new_fd = match openat( - dir, - path.as_os_str(), - nix_all_oflags, - Mode::from_bits_truncate(0o777), - ) { - Ok(fd) => fd, - Err(e) => { - match e.as_errno() { - // Linux returns ENXIO instead of EOPNOTSUPP when opening a socket - Some(Errno::ENXIO) => { - if let Ok(stat) = fstatat(dir, path.as_os_str(), AtFlags::AT_SYMLINK_NOFOLLOW) { - if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFSOCK) { - return wasm32::__WASI_ENOTSUP; - } else { - return wasm32::__WASI_ENXIO; - } - } else { - return wasm32::__WASI_ENXIO; - } - } - Some(e) => return wasm32::errno_from_nix(e), - None => return wasm32::__WASI_ENOSYS, - } - } - }; - - // Determine the type of the new file descriptor and which rights contradict with this type - let guest_fd = match unsafe { determine_type_rights(new_fd) } { - Err(e) => { - // if `close` fails, note it but do not override the underlying errno - nix::unistd::close(new_fd).unwrap_or_else(|e| { - dbg!(e); - }); - return enc_errno(e); - } - Ok((_ty, max_base, max_inheriting)) => { - let mut fe = unsafe { FdEntry::from_raw_fd(new_fd) }; - fe.rights_base &= max_base; - fe.rights_inheriting &= max_inheriting; - match vmctx.get_embed_ctx_mut::().insert_fd_entry(fe) { - Ok(fd) => fd, - Err(e) => return enc_errno(e), - } - } - }; - - unsafe { - enc_fd_byref(&mut vmctx, fd_out_ptr, guest_fd) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) - } -} - -/// Normalizes a path to ensure that the target path is located under the directory provided. -/// -/// This is a workaround for not having Capsicum support in the OS. -pub fn path_get>( - vmctx: &Vmctx, - dirfd: host::__wasi_fd_t, - dirflags: host::__wasi_lookupflags_t, - path: P, - needed_base: host::__wasi_rights_t, - needed_inheriting: host::__wasi_rights_t, - needs_final_component: bool, -) -> Result<(RawFd, OsString), host::__wasi_errno_t> { - use nix::errno::Errno; - use nix::fcntl::{openat, readlinkat, OFlag}; - use nix::sys::stat::Mode; - - const MAX_SYMLINK_EXPANSIONS: usize = 128; - - /// close all the intermediate file descriptors, but make sure not to drop either the original - /// dirfd or the one we return (which may be the same dirfd) - fn ret_dir_success(dir_stack: &mut Vec) -> RawFd { - let ret_dir = dir_stack.pop().expect("there is always a dirfd to return"); - if let Some(dirfds) = dir_stack.get(1..) { - for dirfd in dirfds { - nix::unistd::close(*dirfd).unwrap_or_else(|e| { - dbg!(e); - }); - } - } - ret_dir - } - - /// close all file descriptors other than the base directory, and return the errno for - /// convenience with `return` - fn ret_error( - dir_stack: &mut Vec, - errno: host::__wasi_errno_t, - ) -> Result<(RawFd, OsString), host::__wasi_errno_t> { - if let Some(dirfds) = dir_stack.get(1..) { - for dirfd in dirfds { - nix::unistd::close(*dirfd).unwrap_or_else(|e| { - dbg!(e); - }); - } - } - Err(errno) - } - - let ctx: &WasiCtx = vmctx.get_embed_ctx(); - - let dirfe = ctx.get_fd_entry(dirfd, needed_base, needed_inheriting)?; - - // Stack of directory file descriptors. Index 0 always corresponds with the directory provided - // to this function. Entering a directory causes a file descriptor to be pushed, while handling - // ".." entries causes an entry to be popped. Index 0 cannot be popped, as this would imply - // escaping the base directory. - let mut dir_stack = vec![dirfe.fd_object.rawfd]; - - // Stack of paths left to process. This is initially the `path` argument to this function, but - // any symlinks we encounter are processed by pushing them on the stack. - let mut path_stack = vec![path.as_ref().to_owned().into_vec()]; - - // Track the number of symlinks we've expanded, so we can return `ELOOP` after too many. - let mut symlink_expansions = 0; - - // Buffer to read links into; defined outside of the loop so we don't reallocate it constantly. - let mut readlink_buf = vec![0u8; libc::PATH_MAX as usize + 1]; - - // TODO: rewrite this using a custom posix path type, with a component iterator that respects - // trailing slashes. This version does way too much allocation, and is way too fiddly. - loop { - let component = if let Some(cur_path) = path_stack.pop() { - // eprintln!( - // "cur_path = {:?}", - // std::str::from_utf8(cur_path.as_slice()).unwrap() - // ); - let mut split = cur_path.splitn(2, |&c| c == '/' as u8); - let head = split.next(); - let tail = split.next(); - match (head, tail) { - (None, _) => { - // split always returns at least a singleton iterator with an empty slice - panic!("unreachable"); - } - // path is empty - (Some([]), None) => { - return ret_error(&mut dir_stack, host::__WASI_ENOENT as host::__wasi_errno_t); - } - // path starts with `/`, is absolute - (Some([]), Some(_)) => { - return ret_error( - &mut dir_stack, - host::__WASI_ENOTCAPABLE as host::__wasi_errno_t, - ); - } - // the final component of the path with no trailing slash - (Some(component), None) => component.to_vec(), - (Some(component), Some(rest)) => { - if rest.iter().all(|&c| c == '/' as u8) { - // the final component of the path with trailing slashes; put one trailing - // slash back on - let mut component = component.to_vec(); - component.push('/' as u8); - component - } else { - // non-final component; push the rest back on the stack - path_stack.push(rest.to_vec()); - component.to_vec() - } - } - } - } else { - // if the path stack is ever empty, we return rather than going through the loop again - panic!("unreachable"); - }; - - // eprintln!( - // "component = {:?}", - // std::str::from_utf8(component.as_slice()).unwrap() - // ); - - match component.as_slice() { - b"." => { - // skip component - } - b".." => { - // pop a directory - let dirfd = dir_stack.pop().expect("dir_stack is never empty"); - - // we're not allowed to pop past the original directory - if dir_stack.is_empty() { - return ret_error( - &mut dir_stack, - host::__WASI_ENOTCAPABLE as host::__wasi_errno_t, - ); - } else { - nix::unistd::close(dirfd).unwrap_or_else(|e| { - dbg!(e); - }); - } - } - // should the component be a directory? it should if there is more path left to process, or - // if it has a trailing slash and `needs_final_component` is not set - component - if !path_stack.is_empty() - || (component.ends_with(b"/") && !needs_final_component) => - { - match openat( - *dir_stack.first().expect("dir_stack is never empty"), - component, - OFlag::O_RDONLY | OFlag::O_DIRECTORY | OFlag::O_NOFOLLOW, - Mode::empty(), - ) { - Ok(new_dir) => { - dir_stack.push(new_dir); - continue; - } - Err(e) - if e.as_errno() == Some(Errno::ELOOP) - || e.as_errno() == Some(Errno::EMLINK) => - { - // attempt symlink expansion - match readlinkat( - *dir_stack.last().expect("dir_stack is never empty"), - component, - readlink_buf.as_mut_slice(), - ) { - Ok(link_path) => { - symlink_expansions += 1; - if symlink_expansions > MAX_SYMLINK_EXPANSIONS { - return ret_error( - &mut dir_stack, - host::__WASI_ELOOP as host::__wasi_errno_t, - ); - } - - let mut link_path = link_path.as_bytes().to_vec(); - - // append a trailing slash if the component leading to it has one, so - // that we preserve any ENOTDIR that might come from trying to open a - // non-directory - if component.ends_with(b"/") { - link_path.push('/' as u8); - } - - path_stack.push(link_path); - continue; - } - Err(e) => { - return ret_error( - &mut dir_stack, - host::errno_from_nix(e.as_errno().unwrap()), - ); - } - } - } - Err(e) => { - return ret_error( - &mut dir_stack, - host::errno_from_nix(e.as_errno().unwrap()), - ); - } - } - } - // the final component - component => { - // if there's a trailing slash, or if `LOOKUP_SYMLINK_FOLLOW` is set, attempt - // symlink expansion - if component.ends_with(b"/") || (dirflags & host::__WASI_LOOKUP_SYMLINK_FOLLOW) != 0 - { - match readlinkat( - *dir_stack.last().expect("dir_stack is never empty"), - component, - readlink_buf.as_mut_slice(), - ) { - Ok(link_path) => { - symlink_expansions += 1; - if symlink_expansions > MAX_SYMLINK_EXPANSIONS { - return ret_error( - &mut dir_stack, - host::__WASI_ELOOP as host::__wasi_errno_t, - ); - } - - let mut link_path = link_path.as_bytes().to_vec(); - - // append a trailing slash if the component leading to it has one, so - // that we preserve any ENOTDIR that might come from trying to open a - // non-directory - if component.ends_with(b"/") { - link_path.push('/' as u8); - } - - path_stack.push(link_path); - continue; - } - Err(e) => { - let errno = e.as_errno().unwrap(); - if errno != Errno::EINVAL && errno != Errno::ENOENT { - // only return an error if this path is not actually a symlink - return ret_error(&mut dir_stack, host::errno_from_nix(errno)); - } - } - } - } - - // not a symlink, so we're done; - return Ok(( - ret_dir_success(&mut dir_stack), - OsStr::from_bytes(component).to_os_string(), - )); - } - } - - if path_stack.is_empty() { - // no further components to process. means we've hit a case like "." or "a/..", or if the - // input path has trailing slashes and `needs_final_component` is not set - return Ok(( - ret_dir_success(&mut dir_stack), - OsStr::new(".").to_os_string(), - )); - } else { - continue; - } - } -} - -#[no_mangle] -pub extern "C" fn __wasi_random_get( - vmctx: *mut lucet_vmctx, - buf_ptr: wasm32::uintptr_t, - buf_len: wasm32::size_t, -) -> wasm32::__wasi_errno_t { - use rand::{thread_rng, RngCore}; - - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - - let buf_len = dec_usize(buf_len); - let buf_ptr = match unsafe { dec_ptr(&mut vmctx, buf_ptr, buf_len) } { - Ok(ptr) => ptr, - Err(e) => return enc_errno(e), - }; - - let buf = unsafe { std::slice::from_raw_parts_mut(buf_ptr, buf_len) }; - - thread_rng().fill_bytes(buf); - - return wasm32::__WASI_ESUCCESS; -} - -#[doc(hidden)] -pub fn ensure_linked() { - unsafe { - std::ptr::read_volatile(__wasi_proc_exit as *const extern "C" fn()); - } -} diff --git a/lucet-wasi/src/lib.rs b/lucet-wasi/src/lib.rs index 5dccecd80..4fc3e1cc2 100644 --- a/lucet-wasi/src/lib.rs +++ b/lucet-wasi/src/lib.rs @@ -1,10 +1,15 @@ +#![deny(bare_trait_objects)] + +mod bindings; pub mod c_api; -pub mod ctx; -pub mod fdentry; -pub mod host; -pub mod hostcalls; -pub mod memory; -pub mod wasm32; +pub mod wasi; + +// Lucet-specific wrappers of wasi-common: +pub use bindings::bindings; +pub use wasi::export_wasi_funcs; -pub use ctx::{WasiCtx, WasiCtxBuilder}; +// Wasi-common re-exports: +pub use wasi_common::{wasi::__wasi_exitcode_t, Error, WasiCtx, WasiCtxBuilder}; +// Wasi executables export the following symbol for the entry point: +pub const START_SYMBOL: &'static str = "_start"; diff --git a/lucet-wasi/src/main.rs b/lucet-wasi/src/main.rs index eb9837c6b..953aa53ec 100644 --- a/lucet-wasi/src/main.rs +++ b/lucet-wasi/src/main.rs @@ -1,13 +1,17 @@ +#![deny(bare_trait_objects)] + #[macro_use] extern crate clap; +use anyhow::{format_err, Error}; use clap::Arg; -use human_size::{Byte, Size}; -use lucet_runtime::{self, DlModule, Limits, MmapRegion, Module, Region}; -use lucet_runtime_internals::module::ModuleInternal; -use lucet_wasi::{hostcalls, WasiCtxBuilder}; +use lucet_runtime::{self, DlModule, Limits, MmapRegion, Module, PublicKey, Region, RunResult}; +use lucet_wasi::{self, WasiCtxBuilder, __wasi_exitcode_t}; use std::fs::File; +use std::path::PathBuf; use std::sync::Arc; +use std::thread; +use std::time::Duration; struct Config<'a> { lucet_module: &'a str, @@ -15,13 +19,28 @@ struct Config<'a> { entrypoint: &'a str, preopen_dirs: Vec<(File, &'a str)>, limits: Limits, + timeout: Option, + verify: bool, + pk_path: Option, +} + +fn parse_humansized(desc: &str) -> Result { + use human_size::{Byte, ParsingError, Size, SpecificSize}; + match desc.parse::() { + Ok(s) => { + let bytes: SpecificSize = s.into(); + Ok(bytes.value() as u64) + } + Err(ParsingError::MissingMultiple) => Ok(desc.parse::()?), + Err(e) => Err(e)?, + } } fn main() { // No-ops, but makes sure the linker doesn't throw away parts // of the runtime: lucet_runtime::lucet_internal_ensure_linked(); - hostcalls::ensure_linked(); + lucet_wasi::export_wasi_funcs(); let matches = app_from_crate!() .arg( @@ -80,12 +99,27 @@ fn main() { .default_value("8 MiB") .help("Maximum stack size (must be a multiple of 4 KiB)"), ) + .arg( + Arg::with_name("timeout").long("timeout").takes_value(true).help("Number of milliseconds the instance will be allowed to run") + ) .arg( Arg::with_name("guest_args") .required(false) .multiple(true) .help("Arguments to the WASI `main` function"), ) + .arg( + Arg::with_name("verify") + .long("--signature-verify") + .takes_value(false) + .help("Verify the signature of the source file") + ) + .arg( + Arg::with_name("pk_path") + .long("--signature-pk") + .takes_value(true) + .help("Path to the public key to verify the source code signature") + ) .get_matches(); let entrypoint = matches.value_of("entrypoint").unwrap(); @@ -111,14 +145,17 @@ fn main() { }) .unwrap_or(vec![]); - let heap_memory_size = value_t!(matches, "heap_memory_size", Size) - .unwrap_or_else(|e| e.exit()) - .into::() - .value() as usize; - let heap_address_space_size = value_t!(matches, "heap_address_space_size", Size) - .unwrap_or_else(|e| e.exit()) - .into::() - .value() as usize; + let heap_memory_size = matches + .value_of("heap_memory_size") + .ok_or_else(|| format_err!("missing heap memory size")) + .and_then(|v| parse_humansized(v)) + .unwrap() as usize; + + let heap_address_space_size = matches + .value_of("heap_address_space_size") + .ok_or_else(|| format_err!("missing heap address space size")) + .and_then(|v| parse_humansized(v)) + .unwrap() as usize; if heap_memory_size > heap_address_space_size { println!("`heap-address-space` must be at least as large as `max-heap-size`"); @@ -126,10 +163,15 @@ fn main() { std::process::exit(1); } - let stack_size = value_t!(matches, "stack_size", Size) - .unwrap_or_else(|e| e.exit()) - .into::() - .value() as usize; + let stack_size = matches + .value_of("stack_size") + .ok_or_else(|| format_err!("missing stack size")) + .and_then(|v| parse_humansized(v)) + .unwrap() as usize; + + let timeout = matches + .value_of("timeout") + .map(|t| Duration::from_millis(t.parse::().unwrap())); let limits = Limits { heap_memory_size, @@ -143,23 +185,40 @@ fn main() { .map(|vals| vals.collect()) .unwrap_or(vec![]); + let verify = matches.is_present("verify"); + let pk_path = matches.value_of("pk_path").map(PathBuf::from); + let config = Config { lucet_module, guest_args, entrypoint, preopen_dirs, limits, + timeout, + verify, + pk_path, }; run(config) } -fn run(config: Config) { - lucet_wasi::hostcalls::ensure_linked(); +fn run(config: Config<'_>) { let exitcode = { // doing all of this in a block makes sure everything gets dropped before exiting - let module = DlModule::load(&config.lucet_module).expect("module can be loaded"); - let min_globals_size = module.globals().len() * std::mem::size_of::(); + let pk = match (config.verify, config.pk_path) { + (false, _) => None, + (true, Some(pk_path)) => { + Some(PublicKey::from_file(pk_path).expect("public key can be loaded")) + } + (true, None) => panic!("signature verification requires a public key"), + }; + let module = if let Some(pk) = pk { + DlModule::load_and_verify(&config.lucet_module, pk) + .expect("signed module can be loaded") + } else { + DlModule::load(&config.lucet_module).expect("module can be loaded") + }; + let min_globals_size = module.initial_globals_size(); let globals_size = ((min_globals_size + 4096 - 1) / 4096) * 4096; let region = MmapRegion::create( @@ -175,7 +234,10 @@ fn run(config: Config) { let args = std::iter::once(config.lucet_module) .chain(config.guest_args.into_iter()) .collect::>(); - let mut ctx = WasiCtxBuilder::new().args(&args).inherit_env(); + let mut ctx = WasiCtxBuilder::new() + .args(args.iter()) + .inherit_stdio() + .inherit_env(); for (dir, guest_path) in config.preopen_dirs { ctx = ctx.preopened_dir(dir, guest_path); } @@ -185,14 +247,32 @@ fn run(config: Config) { .build() .expect("instance can be created"); - match inst.run(config.entrypoint.as_bytes(), &[]) { + if let Some(timeout) = config.timeout { + let kill_switch = inst.kill_switch(); + thread::spawn(move || { + thread::sleep(timeout); + // We may hit this line exactly when the guest exits, so sometimes `terminate` can + // fail. That's still acceptable, so just ignore the result. + kill_switch.terminate().ok(); + }); + } + + match inst.run(config.entrypoint, &[]) { // normal termination implies 0 exit code - Ok(_) => 0, + Ok(RunResult::Returned(_)) => 0, + // none of the WASI hostcalls use yield yet, so this shouldn't happen + Ok(RunResult::Yielded(_)) => panic!("lucet-wasi unexpectedly yielded"), Err(lucet_runtime::Error::RuntimeTerminated( lucet_runtime::TerminationDetails::Provided(any), )) => *any - .downcast_ref::() + .downcast_ref::<__wasi_exitcode_t>() .expect("termination yields an exitcode"), + Err(lucet_runtime::Error::RuntimeTerminated( + lucet_runtime::TerminationDetails::Remote, + )) => { + println!("Terminated via remote kill switch (likely a timeout)"); + std::u32::MAX + } Err(e) => panic!("lucet-wasi runtime error: {}", e), } }; diff --git a/lucet-wasi/src/memory.rs b/lucet-wasi/src/memory.rs deleted file mode 100644 index c521ca729..000000000 --- a/lucet-wasi/src/memory.rs +++ /dev/null @@ -1,359 +0,0 @@ -//! Functions to go back and forth between WASI types in host and wasm32 representations. -//! -//! This module is an adaptation of the `wasmtime-wasi` module -//! [`translate.rs`](https://github.com/CraneStation/wasmtime-wasi/blob/1a6ecf3a0378d71f3fc1ba25ce76a2b43e4166b8/lib/wasi/src/translate.rs); -//! its license file `LICENSE.wasmtime-wasi` is included in this project. -//! -//! Any of these functions that take a `Vmctx` argument are only meant to be called from within a -//! hostcall. -//! -//! This sort of manual encoding will hopefully be obsolete once the IDL is developed. - -use crate::{host, wasm32}; -use cast; -use cast::From as _0; -use lucet_runtime::vmctx::Vmctx; -use std::mem::{align_of, size_of}; -use std::slice; - -macro_rules! bail_errno { - ( $errno:ident ) => { - return Err(host::$errno as host::__wasi_errno_t); - }; -} - -pub unsafe fn dec_ptr( - vmctx: &mut Vmctx, - ptr: wasm32::uintptr_t, - len: usize, -) -> Result<*mut u8, host::__wasi_errno_t> { - let heap = vmctx.heap_mut(); - - // check that `len` fits in the wasm32 address space - if len > wasm32::UINTPTR_MAX as usize { - bail_errno!(__WASI_EOVERFLOW); - } - - // check that `ptr` and `ptr + len` are both within the guest heap - if ptr as usize > heap.len() || ptr as usize + len > heap.len() { - bail_errno!(__WASI_EFAULT); - } - - // translate the pointer - Ok(heap.as_mut_ptr().offset(ptr as isize)) -} - -pub unsafe fn dec_ptr_to( - vmctx: &mut Vmctx, - ptr: wasm32::uintptr_t, -) -> Result<*mut T, host::__wasi_errno_t> { - // check that the ptr is aligned - if ptr as usize % align_of::() != 0 { - bail_errno!(__WASI_EINVAL); - } - dec_ptr(vmctx, ptr, size_of::()).map(|p| p as *mut T) -} - -pub unsafe fn dec_pointee( - vmctx: &mut Vmctx, - ptr: wasm32::uintptr_t, -) -> Result { - dec_ptr_to::(vmctx, ptr).map(|p| p.read()) -} - -pub unsafe fn enc_pointee( - vmctx: &mut Vmctx, - ptr: wasm32::uintptr_t, - t: T, -) -> Result<(), host::__wasi_errno_t> { - dec_ptr_to::(vmctx, ptr).map(|p| p.write(t)) -} - -pub unsafe fn dec_slice_of( - vmctx: &mut Vmctx, - ptr: wasm32::uintptr_t, - len: wasm32::size_t, -) -> Result<(*mut T, usize), host::__wasi_errno_t> { - // check alignment, and that length doesn't overflow - if ptr as usize % align_of::() != 0 { - return Err(host::__WASI_EINVAL as host::__wasi_errno_t); - } - let len = dec_usize(len); - let len_bytes = if let Some(len) = size_of::().checked_mul(len) { - len - } else { - return Err(host::__WASI_EOVERFLOW as host::__wasi_errno_t); - }; - - let ptr = dec_ptr(vmctx, ptr, len_bytes)? as *mut T; - - Ok((ptr, len)) -} - -pub unsafe fn enc_slice_of( - vmctx: &mut Vmctx, - slice: &[T], - ptr: wasm32::uintptr_t, -) -> Result<(), host::__wasi_errno_t> { - // check alignment - if ptr as usize % align_of::() != 0 { - return Err(host::__WASI_EINVAL as host::__wasi_errno_t); - } - // check that length doesn't overflow - let len_bytes = if let Some(len) = size_of::().checked_mul(slice.len()) { - len - } else { - return Err(host::__WASI_EOVERFLOW as host::__wasi_errno_t); - }; - - // get the pointer into guest memory, and copy the bytes - let ptr = dec_ptr(vmctx, ptr, len_bytes)? as *mut libc::c_void; - libc::memcpy(ptr, slice.as_ptr() as *const libc::c_void, len_bytes); - - Ok(()) -} - -macro_rules! dec_enc_scalar { - ( $ty:ident, $dec:ident, $dec_byref:ident, $enc:ident, $enc_byref:ident) => { - pub fn $dec(x: wasm32::$ty) -> host::$ty { - host::$ty::from_le(x) - } - - pub unsafe fn $dec_byref( - vmctx: &mut Vmctx, - ptr: wasm32::uintptr_t, - ) -> Result { - dec_pointee::(vmctx, ptr).map($dec) - } - - pub fn $enc(x: host::$ty) -> wasm32::$ty { - x.to_le() - } - - pub unsafe fn $enc_byref( - vmctx: &mut Vmctx, - ptr: wasm32::uintptr_t, - x: host::$ty, - ) -> Result<(), host::__wasi_errno_t> { - enc_pointee::(vmctx, ptr, $enc(x)) - } - }; -} - -pub unsafe fn dec_ciovec( - vmctx: &mut Vmctx, - ciovec: &wasm32::__wasi_ciovec_t, -) -> Result { - let len = dec_usize(ciovec.buf_len); - Ok(host::__wasi_ciovec_t { - buf: dec_ptr(vmctx, ciovec.buf, len)? as *const host::void, - buf_len: len, - }) -} - -pub unsafe fn dec_ciovec_slice( - vmctx: &mut Vmctx, - ptr: wasm32::uintptr_t, - len: wasm32::size_t, -) -> Result, host::__wasi_errno_t> { - let slice = dec_slice_of::(vmctx, ptr, len)?; - let slice = slice::from_raw_parts(slice.0, slice.1); - slice.iter().map(|iov| dec_ciovec(vmctx, iov)).collect() -} - -dec_enc_scalar!( - __wasi_clockid_t, - dec_clockid, - dec_clockid_byref, - enc_clockid, - enc_clockid_byref -); -dec_enc_scalar!( - __wasi_errno_t, - dec_errno, - dec_errno_byref, - enc_errno, - enc_errno_byref -); -dec_enc_scalar!( - __wasi_exitcode_t, - dec_exitcode, - dec_exitcode_byref, - enc_exitcode, - enc_exitcode_byref -); -dec_enc_scalar!(__wasi_fd_t, dec_fd, dec_fd_byref, enc_fd, enc_fd_byref); -dec_enc_scalar!( - __wasi_fdflags_t, - dec_fdflags, - dec_fdflags_byref, - enc_fdflags, - enc_fdflags_byref -); - -pub fn dec_fdstat(fdstat: wasm32::__wasi_fdstat_t) -> host::__wasi_fdstat_t { - host::__wasi_fdstat_t { - fs_filetype: dec_filetype(fdstat.fs_filetype), - fs_flags: dec_fdflags(fdstat.fs_flags), - fs_rights_base: dec_rights(fdstat.fs_rights_base), - fs_rights_inheriting: dec_rights(fdstat.fs_rights_inheriting), - } -} - -pub unsafe fn dec_fdstat_byref( - vmctx: &mut Vmctx, - fdstat_ptr: wasm32::uintptr_t, -) -> Result { - dec_pointee::(vmctx, fdstat_ptr).map(dec_fdstat) -} - -pub fn enc_fdstat(fdstat: host::__wasi_fdstat_t) -> wasm32::__wasi_fdstat_t { - wasm32::__wasi_fdstat_t { - fs_filetype: enc_filetype(fdstat.fs_filetype), - fs_flags: enc_fdflags(fdstat.fs_flags), - __bindgen_padding_0: 0, - fs_rights_base: enc_rights(fdstat.fs_rights_base), - fs_rights_inheriting: enc_rights(fdstat.fs_rights_inheriting), - } -} - -pub unsafe fn enc_fdstat_byref( - vmctx: &mut Vmctx, - fdstat_ptr: wasm32::uintptr_t, - host_fdstat: host::__wasi_fdstat_t, -) -> Result<(), host::__wasi_errno_t> { - let fdstat = enc_fdstat(host_fdstat); - enc_pointee::(vmctx, fdstat_ptr, fdstat) -} - -dec_enc_scalar!( - __wasi_filedelta_t, - dec_filedelta, - dec_filedelta_byref, - enc_filedelta, - enc_filedelta_byref -); -dec_enc_scalar!( - __wasi_filesize_t, - dec_filesize, - dec_filesize_byref, - enc_filesize, - enc_filesize_byref -); - -dec_enc_scalar!( - __wasi_filetype_t, - dec_filetype, - dec_filetype_byref, - enc_filetype, - enc_filetype_byref -); - -dec_enc_scalar!( - __wasi_lookupflags_t, - dec_lookupflags, - dec_lookupflags_byref, - enc_lookupflags, - enc_lookupflags_byref -); - -dec_enc_scalar!( - __wasi_oflags_t, - dec_oflags, - dec_oflags_byref, - enc_oflags, - enc_oflags_byref -); - -pub fn dec_prestat( - prestat: wasm32::__wasi_prestat_t, -) -> Result { - match prestat.pr_type { - wasm32::__WASI_PREOPENTYPE_DIR => { - let u = host::__wasi_prestat_t___wasi_prestat_u { - dir: host::__wasi_prestat_t___wasi_prestat_u___wasi_prestat_u_dir_t { - pr_name_len: dec_usize(unsafe { prestat.u.dir.pr_name_len }), - }, - }; - Ok(host::__wasi_prestat_t { - pr_type: host::__WASI_PREOPENTYPE_DIR as host::__wasi_preopentype_t, - u, - }) - } - _ => Err(host::__WASI_EINVAL as host::__wasi_errno_t), - } -} - -pub unsafe fn dec_prestat_byref( - vmctx: &mut Vmctx, - prestat_ptr: wasm32::uintptr_t, -) -> Result { - dec_pointee::(vmctx, prestat_ptr).and_then(dec_prestat) -} - -pub fn enc_prestat( - prestat: host::__wasi_prestat_t, -) -> Result { - match prestat.pr_type as u32 { - host::__WASI_PREOPENTYPE_DIR => { - let u = wasm32::__wasi_prestat_t___wasi_prestat_u { - dir: wasm32::__wasi_prestat_t___wasi_prestat_u___wasi_prestat_u_dir_t { - pr_name_len: enc_usize(unsafe { prestat.u.dir.pr_name_len }), - }, - }; - Ok(wasm32::__wasi_prestat_t { - pr_type: wasm32::__WASI_PREOPENTYPE_DIR as wasm32::__wasi_preopentype_t, - u, - }) - } - _ => Err(host::__WASI_EINVAL as host::__wasi_errno_t), - } -} - -pub unsafe fn enc_prestat_byref( - vmctx: &mut Vmctx, - prestat_ptr: wasm32::uintptr_t, - host_prestat: host::__wasi_prestat_t, -) -> Result<(), host::__wasi_errno_t> { - let prestat = enc_prestat(host_prestat)?; - enc_pointee::(vmctx, prestat_ptr, prestat) -} - -dec_enc_scalar!( - __wasi_rights_t, - dec_rights, - dec_rights_byref, - enc_rights, - enc_rights_byref -); -dec_enc_scalar!( - __wasi_timestamp_t, - dec_timestamp, - dec_timestamp_byref, - enc_timestamp, - enc_timestamp_byref -); - -pub fn dec_usize(size: wasm32::size_t) -> usize { - cast::usize(u32::from_le(size)) -} - -pub fn enc_usize(size: usize) -> wasm32::size_t { - wasm32::size_t::cast(size).unwrap() -} - -pub unsafe fn enc_usize_byref( - vmctx: &mut Vmctx, - usize_ptr: wasm32::uintptr_t, - host_usize: usize, -) -> Result<(), host::__wasi_errno_t> { - enc_pointee::(vmctx, usize_ptr, enc_usize(host_usize)) -} - -dec_enc_scalar!( - __wasi_whence_t, - dec_whence, - dec_whence_byref, - enc_whence, - enc_whence_byref -); diff --git a/lucet-wasi/src/wasi.rs b/lucet-wasi/src/wasi.rs new file mode 100644 index 000000000..c0b9b7d8a --- /dev/null +++ b/lucet-wasi/src/wasi.rs @@ -0,0 +1,627 @@ +#![allow(clippy::too_many_arguments)] + +use lucet_runtime::vmctx::Vmctx; +use lucet_runtime::{lucet_hostcall, lucet_hostcall_terminate}; +use std::mem; +use std::rc::Rc; +use wasi_common::hostcalls::*; +use wasi_common::{wasi, wasi32, WasiCtx}; + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_proc_exit(_lucet_ctx: &mut Vmctx, rval: wasi::__wasi_exitcode_t) -> ! { + export_wasi_funcs(); + lucet_hostcall_terminate!(rval); +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_args_get( + lucet_ctx: &mut Vmctx, + argv_ptr: wasi32::uintptr_t, + argv_buf: wasi32::uintptr_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + let heap = &mut lucet_ctx.heap_mut(); + args_get(wasi_ctx, heap, argv_ptr, argv_buf) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_args_sizes_get( + lucet_ctx: &mut Vmctx, + argc_ptr: wasi32::uintptr_t, + argv_buf_size_ptr: wasi32::uintptr_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + let heap = &mut lucet_ctx.heap_mut(); + args_sizes_get(wasi_ctx, heap, argc_ptr, argv_buf_size_ptr) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_sched_yield(_lucet_ctx: &mut Vmctx) -> wasi::__wasi_errno_t { + sched_yield() +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_clock_res_get( + lucet_ctx: &mut Vmctx, + clock_id: wasi::__wasi_clockid_t, + resolution_ptr: wasi32::uintptr_t, +) -> wasi::__wasi_errno_t { + let heap = &mut lucet_ctx.heap_mut(); + clock_res_get(heap, clock_id, resolution_ptr) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_clock_time_get( + lucet_ctx: &mut Vmctx, + clock_id: wasi::__wasi_clockid_t, + precision: wasi::__wasi_timestamp_t, + time_ptr: wasi32::uintptr_t, +) -> wasi::__wasi_errno_t { + let heap = &mut lucet_ctx.heap_mut(); + clock_time_get(heap, clock_id, precision, time_ptr) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_environ_get( + lucet_ctx: &mut Vmctx, + environ_ptr: wasi32::uintptr_t, + environ_buf: wasi32::uintptr_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + let heap = &mut lucet_ctx.heap_mut(); + environ_get(wasi_ctx, heap, environ_ptr, environ_buf) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_environ_sizes_get( + lucet_ctx: &mut Vmctx, + environ_count_ptr: wasi32::uintptr_t, + environ_size_ptr: wasi32::uintptr_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + let heap = &mut lucet_ctx.heap_mut(); + environ_sizes_get(wasi_ctx, heap, environ_count_ptr, environ_size_ptr) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_fd_close( + lucet_ctx: &mut Vmctx, + fd: wasi::__wasi_fd_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &mut lucet_ctx.get_embed_ctx_mut::(); + fd_close(wasi_ctx, fd) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_fd_fdstat_get( + lucet_ctx: &mut Vmctx, + fd: wasi::__wasi_fd_t, + fdstat_ptr: wasi32::uintptr_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + let heap = &mut lucet_ctx.heap_mut(); + fd_fdstat_get(wasi_ctx, heap, fd, fdstat_ptr) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_fd_fdstat_set_flags( + lucet_ctx: &mut Vmctx, + fd: wasi::__wasi_fd_t, + fdflags: wasi::__wasi_fdflags_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + fd_fdstat_set_flags(wasi_ctx, fd, fdflags) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_fd_tell( + lucet_ctx: &mut Vmctx, + fd: wasi::__wasi_fd_t, + offset: wasi32::uintptr_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &mut lucet_ctx.get_embed_ctx_mut::(); + let heap = &mut lucet_ctx.heap_mut(); + fd_tell(wasi_ctx, heap, fd, offset) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_fd_seek( + lucet_ctx: &mut Vmctx, + fd: wasi::__wasi_fd_t, + offset: wasi::__wasi_filedelta_t, + whence: wasi::__wasi_whence_t, + newoffset: wasi32::uintptr_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &mut lucet_ctx.get_embed_ctx_mut::(); + let heap = &mut lucet_ctx.heap_mut(); + fd_seek(wasi_ctx, heap, fd, offset, whence, newoffset) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_fd_prestat_get( + lucet_ctx: &mut Vmctx, + fd: wasi::__wasi_fd_t, + prestat_ptr: wasi32::uintptr_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + let heap = &mut lucet_ctx.heap_mut(); + fd_prestat_get(wasi_ctx, heap, fd, prestat_ptr) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_fd_prestat_dir_name( + lucet_ctx: &mut Vmctx, + fd: wasi::__wasi_fd_t, + path_ptr: wasi32::uintptr_t, + path_len: wasi32::size_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + let heap = &mut lucet_ctx.heap_mut(); + fd_prestat_dir_name(wasi_ctx, heap, fd, path_ptr, path_len) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_fd_read( + lucet_ctx: &mut Vmctx, + fd: wasi::__wasi_fd_t, + iovs_ptr: wasi32::uintptr_t, + iovs_len: wasi32::size_t, + nread: wasi32::uintptr_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &mut lucet_ctx.get_embed_ctx_mut::(); + let heap = &mut lucet_ctx.heap_mut(); + fd_read(wasi_ctx, heap, fd, iovs_ptr, iovs_len, nread) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_fd_write( + lucet_ctx: &mut Vmctx, + fd: wasi::__wasi_fd_t, + iovs_ptr: wasi32::uintptr_t, + iovs_len: wasi32::size_t, + nwritten: wasi32::uintptr_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &mut lucet_ctx.get_embed_ctx_mut::(); + let heap = &mut lucet_ctx.heap_mut(); + fd_write(wasi_ctx, heap, fd, iovs_ptr, iovs_len, nwritten) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_path_open( + lucet_ctx: &mut Vmctx, + dirfd: wasi::__wasi_fd_t, + dirflags: wasi::__wasi_lookupflags_t, + path_ptr: wasi32::uintptr_t, + path_len: wasi32::size_t, + oflags: wasi::__wasi_oflags_t, + fs_rights_base: wasi::__wasi_rights_t, + fs_rights_inheriting: wasi::__wasi_rights_t, + fs_flags: wasi::__wasi_fdflags_t, + fd_out_ptr: wasi32::uintptr_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &mut lucet_ctx.get_embed_ctx_mut::(); + let heap = &mut lucet_ctx.heap_mut(); + path_open( + wasi_ctx, + heap, + dirfd, + dirflags, + path_ptr, + path_len, + oflags, + fs_rights_base, + fs_rights_inheriting, + fs_flags, + fd_out_ptr, + ) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_random_get( + lucet_ctx: &mut Vmctx, + buf_ptr: wasi32::uintptr_t, + buf_len: wasi32::size_t, +) -> wasi::__wasi_errno_t { + let heap = &mut lucet_ctx.heap_mut(); + random_get(heap, buf_ptr, buf_len) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_poll_oneoff( + lucet_ctx: &mut Vmctx, + input: wasi32::uintptr_t, + output: wasi32::uintptr_t, + nsubscriptions: wasi32::size_t, + nevents: wasi32::uintptr_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + let heap = &mut lucet_ctx.heap_mut(); + poll_oneoff(wasi_ctx, heap, input, output, nsubscriptions, nevents) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_fd_filestat_get( + lucet_ctx: &mut Vmctx, + fd: wasi::__wasi_fd_t, + filestat_ptr: wasi32::uintptr_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + let heap = &mut lucet_ctx.heap_mut(); + fd_filestat_get(wasi_ctx, heap, fd, filestat_ptr) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_path_filestat_get( + lucet_ctx: &mut Vmctx, + dirfd: wasi::__wasi_fd_t, + dirflags: wasi::__wasi_lookupflags_t, + path_ptr: wasi32::uintptr_t, + path_len: wasi32::size_t, + filestat_ptr: wasi32::uintptr_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + let heap = &mut lucet_ctx.heap_mut(); + path_filestat_get( + wasi_ctx, + heap, + dirfd, + dirflags, + path_ptr, + path_len, + filestat_ptr, + ) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_path_create_directory( + lucet_ctx: &mut Vmctx, + dirfd: wasi::__wasi_fd_t, + path_ptr: wasi32::uintptr_t, + path_len: wasi32::size_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + let heap = &mut lucet_ctx.heap_mut(); + path_create_directory(wasi_ctx, heap, dirfd, path_ptr, path_len) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_path_unlink_file( + lucet_ctx: &mut Vmctx, + dirfd: wasi::__wasi_fd_t, + path_ptr: wasi32::uintptr_t, + path_len: wasi32::size_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + let heap = &mut lucet_ctx.heap_mut(); + path_unlink_file(wasi_ctx, heap, dirfd, path_ptr, path_len) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_fd_allocate( + lucet_ctx: &mut Vmctx, + fd: wasi::__wasi_fd_t, + offset: wasi::__wasi_filesize_t, + len: wasi::__wasi_filesize_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + fd_allocate(wasi_ctx, fd, offset, len) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_fd_advise( + lucet_ctx: &mut Vmctx, + fd: wasi::__wasi_fd_t, + offset: wasi::__wasi_filesize_t, + len: wasi::__wasi_filesize_t, + advice: wasi::__wasi_advice_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + fd_advise(wasi_ctx, fd, offset, len, advice) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_fd_datasync( + lucet_ctx: &mut Vmctx, + fd: wasi::__wasi_fd_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + fd_datasync(wasi_ctx, fd) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_fd_sync(lucet_ctx: &mut Vmctx, fd: wasi::__wasi_fd_t) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + fd_sync(wasi_ctx, fd) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_fd_fdstat_set_rights( + lucet_ctx: &mut Vmctx, + fd: wasi::__wasi_fd_t, + fs_rights_base: wasi::__wasi_rights_t, + fs_rights_inheriting: wasi::__wasi_rights_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &mut lucet_ctx.get_embed_ctx_mut::(); + fd_fdstat_set_rights(wasi_ctx, fd, fs_rights_base, fs_rights_inheriting) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_fd_filestat_set_size( + lucet_ctx: &mut Vmctx, + fd: wasi::__wasi_fd_t, + st_size: wasi::__wasi_filesize_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + fd_filestat_set_size(wasi_ctx, fd, st_size) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_fd_filestat_set_times( + lucet_ctx: &mut Vmctx, + fd: wasi::__wasi_fd_t, + st_atim: wasi::__wasi_timestamp_t, + st_mtim: wasi::__wasi_timestamp_t, + fst_flags: wasi::__wasi_fstflags_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + fd_filestat_set_times(wasi_ctx, fd, st_atim, st_mtim, fst_flags) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_fd_pread( + lucet_ctx: &mut Vmctx, + fd: wasi::__wasi_fd_t, + iovs_ptr: wasi32::uintptr_t, + iovs_len: wasi32::size_t, + offset: wasi::__wasi_filesize_t, + nread: wasi32::uintptr_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + let heap = &mut lucet_ctx.heap_mut(); + fd_pread(wasi_ctx, heap, fd, iovs_ptr, iovs_len, offset, nread) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_fd_pwrite( + lucet_ctx: &mut Vmctx, + fd: wasi::__wasi_fd_t, + iovs_ptr: wasi32::uintptr_t, + iovs_len: wasi32::size_t, + offset: wasi::__wasi_filesize_t, + nwritten: wasi32::uintptr_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + let heap = &mut lucet_ctx.heap_mut(); + fd_pwrite(wasi_ctx, heap, fd, iovs_ptr, iovs_len, offset, nwritten) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_fd_readdir( + lucet_ctx: &mut Vmctx, + fd: wasi::__wasi_fd_t, + buf: wasi32::uintptr_t, + buf_len: wasi32::size_t, + cookie: wasi::__wasi_dircookie_t, + bufused: wasi32::uintptr_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &mut lucet_ctx.get_embed_ctx_mut::(); + let heap = &mut lucet_ctx.heap_mut(); + fd_readdir(wasi_ctx, heap, fd, buf, buf_len, cookie, bufused) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_fd_renumber( + lucet_ctx: &mut Vmctx, + from: wasi::__wasi_fd_t, + to: wasi::__wasi_fd_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &mut lucet_ctx.get_embed_ctx_mut::(); + fd_renumber(wasi_ctx, from, to) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_path_filestat_set_times( + lucet_ctx: &mut Vmctx, + dirfd: wasi::__wasi_fd_t, + dirflags: wasi::__wasi_lookupflags_t, + path_ptr: wasi32::uintptr_t, + path_len: wasi32::size_t, + st_atim: wasi::__wasi_timestamp_t, + st_mtim: wasi::__wasi_timestamp_t, + fst_flags: wasi::__wasi_fstflags_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + let heap = &mut lucet_ctx.heap_mut(); + path_filestat_set_times( + wasi_ctx, heap, dirfd, dirflags, path_ptr, path_len, st_atim, st_mtim, fst_flags, + ) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_path_link( + lucet_ctx: &mut Vmctx, + old_fd: wasi::__wasi_fd_t, + old_flags: wasi::__wasi_lookupflags_t, + old_path_ptr: wasi32::uintptr_t, + old_path_len: wasi32::size_t, + new_fd: wasi::__wasi_fd_t, + new_path_ptr: wasi32::uintptr_t, + new_path_len: wasi32::size_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + let heap = &mut lucet_ctx.heap_mut(); + path_link( + wasi_ctx, + heap, + old_fd, + old_flags, + old_path_ptr, + old_path_len, + new_fd, + new_path_ptr, + new_path_len, + ) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_path_readlink( + lucet_ctx: &mut Vmctx, + dirfd: wasi::__wasi_fd_t, + path_ptr: wasi32::uintptr_t, + path_len: wasi32::size_t, + buf_ptr: wasi32::uintptr_t, + buf_len: wasi32::size_t, + bufused: wasi32::uintptr_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + let heap = &mut lucet_ctx.heap_mut(); + path_readlink( + wasi_ctx, heap, dirfd, path_ptr, path_len, buf_ptr, buf_len, bufused, + ) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_path_remove_directory( + lucet_ctx: &mut Vmctx, + dirfd: wasi::__wasi_fd_t, + path_ptr: wasi32::uintptr_t, + path_len: wasi32::size_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + let heap = &mut lucet_ctx.heap_mut(); + path_remove_directory(wasi_ctx, heap, dirfd, path_ptr, path_len) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_path_rename( + lucet_ctx: &mut Vmctx, + old_dirfd: wasi::__wasi_fd_t, + old_path_ptr: wasi32::uintptr_t, + old_path_len: wasi32::size_t, + new_dirfd: wasi::__wasi_fd_t, + new_path_ptr: wasi32::uintptr_t, + new_path_len: wasi32::size_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + let heap = &mut lucet_ctx.heap_mut(); + path_rename( + wasi_ctx, + heap, + old_dirfd, + old_path_ptr, + old_path_len, + new_dirfd, + new_path_ptr, + new_path_len, + ) +} + +#[lucet_hostcall] +#[no_mangle] +pub unsafe fn __wasi_path_symlink( + lucet_ctx: &mut Vmctx, + old_path_ptr: wasi32::uintptr_t, + old_path_len: wasi32::size_t, + dir_fd: wasi::__wasi_fd_t, + new_path_ptr: wasi32::uintptr_t, + new_path_len: wasi32::size_t, +) -> wasi::__wasi_errno_t { + let wasi_ctx = &lucet_ctx.get_embed_ctx::(); + let heap = &mut lucet_ctx.heap_mut(); + path_symlink( + wasi_ctx, + heap, + old_path_ptr, + old_path_len, + dir_fd, + new_path_ptr, + new_path_len, + ) +} + +pub fn export_wasi_funcs() { + let funcs: &[*const extern "C" fn()] = &[ + __wasi_args_get as _, + __wasi_args_sizes_get as _, + __wasi_sched_yield as _, + __wasi_clock_res_get as _, + __wasi_clock_time_get as _, + __wasi_environ_get as _, + __wasi_environ_sizes_get as _, + __wasi_fd_close as _, + __wasi_fd_fdstat_get as _, + __wasi_fd_fdstat_set_flags as _, + __wasi_fd_tell as _, + __wasi_fd_seek as _, + __wasi_fd_prestat_get as _, + __wasi_fd_prestat_dir_name as _, + __wasi_fd_read as _, + __wasi_fd_write as _, + __wasi_path_open as _, + __wasi_random_get as _, + __wasi_poll_oneoff as _, + __wasi_fd_filestat_get as _, + __wasi_path_filestat_get as _, + __wasi_path_create_directory as _, + __wasi_path_unlink_file as _, + __wasi_fd_allocate as _, + __wasi_fd_advise as _, + __wasi_fd_datasync as _, + __wasi_fd_sync as _, + __wasi_fd_fdstat_set_rights as _, + __wasi_fd_filestat_set_size as _, + __wasi_fd_filestat_set_times as _, + __wasi_fd_pread as _, + __wasi_fd_pwrite as _, + __wasi_fd_readdir as _, + __wasi_fd_renumber as _, + __wasi_path_filestat_set_times as _, + __wasi_path_link as _, + __wasi_path_readlink as _, + __wasi_path_remove_directory as _, + __wasi_path_rename as _, + __wasi_path_symlink as _, + __wasi_proc_exit as _, + ]; + mem::forget(Rc::new(funcs)); +} diff --git a/lucet-wasi/src/wasm32.rs b/lucet-wasi/src/wasm32.rs deleted file mode 100644 index 9983b3790..000000000 --- a/lucet-wasi/src/wasm32.rs +++ /dev/null @@ -1,1367 +0,0 @@ -//! WASI types as defined in wasm32. This file was originally generated -//! by running bindgen over wasi/core.h with a wasm32 target, and the content -//! still largely reflects that, however it's been heavily modified, to -//! be host-independent, to avoid exposing libc implementation details, -//! to clean up cases where the headers use complex preprocessor macros, -//! and to - -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] -#![allow(dead_code)] - -// C types -pub type char = i8; -pub type schar = i8; -pub type uchar = u8; -pub type short = i16; -pub type ushort = u16; -pub type int = i32; -pub type uint = u32; -pub type long = i32; -pub type ulong = u32; -pub type longlong = i64; -pub type ulonglong = u64; - -// libc stdint types -pub type int8_t = i8; -pub type uint8_t = u8; -pub type int16_t = i16; -pub type uint16_t = u16; -pub type int32_t = i32; -pub type uint32_t = u32; -pub type int64_t = i64; -pub type uint64_t = u64; -pub type intmax_t = i64; -pub type uintmax_t = u64; -pub type int_least8_t = i8; -pub type int_least16_t = i16; -pub type int_least32_t = i32; -pub type int_least64_t = i64; -pub type uint_least8_t = u8; -pub type uint_least16_t = u16; -pub type uint_least32_t = u32; -pub type uint_least64_t = u64; -pub type int_fast8_t = i8; -pub type int_fast16_t = i32; -pub type int_fast32_t = i32; -pub type int_fast64_t = i64; -pub type uint_fast8_t = u8; -pub type uint_fast16_t = u32; -pub type uint_fast32_t = u32; -pub type uint_fast64_t = u64; -pub type size_t = ulong; -pub type intptr_t = long; -pub type uintptr_t = ulong; -pub type wchar_t = i32; - -// libc types -pub type dev_t = u64; -pub type uid_t = u32; -pub type gid_t = u32; -pub type ino_t = u64; -pub type ino64_t = u64; -pub type mode_t = u32; -pub type nlink_t = u64; -pub type off_t = i64; -pub type off64_t = i64; -pub type pid_t = i32; -pub type clock_t = i64; -pub type rlim_t = u64; -pub type rlim64_t = u64; -pub type id_t = u32; -pub type time_t = i64; -pub type useconds_t = u32; -pub type suseconds_t = i64; -pub type daddr_t = i32; -pub type key_t = i32; -pub type clockid_t = i32; -pub type timer_t = uintptr_t; // *mut ::std::os::raw::c_void -pub type blksize_t = i64; -pub type blkcnt_t = i64; -pub type blkcnt64_t = i64; -pub type fsblkcnt_t = u64; -pub type fsblkcnt64_t = u64; -pub type fsfilcnt_t = u64; -pub type fsfilcnt64_t = u64; -pub type fsword_t = i64; -pub type ssize_t = i32; -pub type loff_t = off64_t; -pub type caddr_t = uintptr_t; // *mut i8 -pub type socklen_t = u32; -pub type sig_atomic_t = i32; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct fsid_t { - pub __val: [i32; 2usize], -} -#[allow(non_snake_case)] -#[test] -fn bindgen_test_layout_fsid_t() { - assert_eq!( - ::std::mem::size_of::(), - 8usize, - concat!("Size of: ", stringify!(fsid_t)) - ); - assert_eq!( - ::std::mem::align_of::(), - 4usize, - concat!("Alignment of ", stringify!(fsid_t)) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).__val as *const _ as usize }, - 0usize, - concat!( - "Offset of field: ", - stringify!(fsid_t), - "::", - stringify!(__val) - ) - ); -} - -// WASI types -pub type __wasi_advice_t = u8; -pub type __wasi_clockid_t = u32; -pub type __wasi_device_t = u64; -pub type __wasi_dircookie_t = u64; -pub type __wasi_errno_t = u16; -pub type __wasi_eventrwflags_t = u16; -pub type __wasi_eventtype_t = u8; -pub type __wasi_exitcode_t = u32; -pub type __wasi_fd_t = u32; -pub type __wasi_fdflags_t = u16; -pub type __wasi_fdsflags_t = u16; -pub type __wasi_filedelta_t = i64; -pub type __wasi_filesize_t = u64; -pub type __wasi_filetype_t = u8; -pub type __wasi_preopentype_t = u8; -pub type __wasi_fstflags_t = u16; -pub type __wasi_inode_t = u64; -pub type __wasi_linkcount_t = u32; -pub type __wasi_lookupflags_t = u32; -pub type __wasi_oflags_t = u16; -pub type __wasi_riflags_t = u16; -pub type __wasi_rights_t = u64; -pub type __wasi_roflags_t = u16; -pub type __wasi_sdflags_t = u8; -pub type __wasi_siflags_t = u16; -pub type __wasi_signal_t = u8; -pub type __wasi_subclockflags_t = u16; -pub type __wasi_timestamp_t = u64; -pub type __wasi_userdata_t = u64; -pub type __wasi_whence_t = u8; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __wasi_dirent_t { - pub d_next: __wasi_dircookie_t, - pub d_ino: __wasi_inode_t, - pub d_namlen: u32, - pub d_type: __wasi_filetype_t, - pub __bindgen_padding_0: [u8; 3usize], -} -#[test] -fn bindgen_test_layout_wasi_dirent_t() { - assert_eq!( - ::std::mem::size_of::<__wasi_dirent_t>(), - 24usize, - concat!("Size of: ", stringify!(__wasi_dirent_t)) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_dirent_t>())).d_next as *const _ as usize }, - 0usize, - concat!( - "Offset of field: ", - stringify!(__wasi_dirent_t), - "::", - stringify!(d_next) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_dirent_t>())).d_ino as *const _ as usize }, - 8usize, - concat!( - "Offset of field: ", - stringify!(__wasi_dirent_t), - "::", - stringify!(d_ino) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_dirent_t>())).d_namlen as *const _ as usize }, - 16usize, - concat!( - "Offset of field: ", - stringify!(__wasi_dirent_t), - "::", - stringify!(d_namlen) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_dirent_t>())).d_type as *const _ as usize }, - 20usize, - concat!( - "Offset of field: ", - stringify!(__wasi_dirent_t), - "::", - stringify!(d_type) - ) - ); -} -#[repr(C)] -#[derive(Copy, Clone)] -pub struct __wasi_event_t { - pub userdata: __wasi_userdata_t, - pub error: __wasi_errno_t, - pub type_: __wasi_eventtype_t, - pub __bindgen_padding_0: u32, - pub __bindgen_anon_1: __wasi_event_t__bindgen_ty_1, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub struct __wasi_prestat_t { - pub pr_type: __wasi_preopentype_t, - pub u: __wasi_prestat_t___wasi_prestat_u, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub union __wasi_prestat_t___wasi_prestat_u { - pub dir: __wasi_prestat_t___wasi_prestat_u___wasi_prestat_u_dir_t, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __wasi_prestat_t___wasi_prestat_u___wasi_prestat_u_dir_t { - pub pr_name_len: size_t, -} -#[test] -fn bindgen_test_layout___wasi_prestat_t___wasi_prestat_u___wasi_prestat_u_dir_t() { - assert_eq!( - ::std::mem::size_of::<__wasi_prestat_t___wasi_prestat_u___wasi_prestat_u_dir_t>(), - 4usize, - concat!( - "Size of: ", - stringify!(__wasi_prestat_t___wasi_prestat_u___wasi_prestat_u_dir_t) - ) - ); - assert_eq!( - ::std::mem::align_of::<__wasi_prestat_t___wasi_prestat_u___wasi_prestat_u_dir_t>(), - 4usize, - concat!( - "Alignment of ", - stringify!(__wasi_prestat_t___wasi_prestat_u___wasi_prestat_u_dir_t) - ) - ); - assert_eq!( - unsafe { - &(*(::std::ptr::null::<__wasi_prestat_t___wasi_prestat_u___wasi_prestat_u_dir_t>())) - .pr_name_len as *const _ as usize - }, - 0usize, - concat!( - "Offset of field: ", - stringify!(__wasi_prestat_t___wasi_prestat_u___wasi_prestat_u_dir_t), - "::", - stringify!(pr_name_len) - ) - ); -} -#[test] -fn bindgen_test_layout___wasi_prestat_t___wasi_prestat_u() { - assert_eq!( - ::std::mem::size_of::<__wasi_prestat_t___wasi_prestat_u>(), - 4usize, - concat!("Size of: ", stringify!(__wasi_prestat_t___wasi_prestat_u)) - ); - assert_eq!( - ::std::mem::align_of::<__wasi_prestat_t___wasi_prestat_u>(), - 4usize, - concat!( - "Alignment of ", - stringify!(__wasi_prestat_t___wasi_prestat_u) - ) - ); - assert_eq!( - unsafe { - &(*(::std::ptr::null::<__wasi_prestat_t___wasi_prestat_u>())).dir as *const _ as usize - }, - 0usize, - concat!( - "Offset of field: ", - stringify!(__wasi_prestat_t___wasi_prestat_u), - "::", - stringify!(dir) - ) - ); -} -#[test] -fn bindgen_test_layout___wasi_prestat_t() { - assert_eq!( - ::std::mem::size_of::<__wasi_prestat_t>(), - 8usize, - concat!("Size of: ", stringify!(__wasi_prestat_t)) - ); - assert_eq!( - ::std::mem::align_of::<__wasi_prestat_t>(), - 4usize, - concat!("Alignment of ", stringify!(__wasi_prestat_t)) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_prestat_t>())).pr_type as *const _ as usize }, - 0usize, - concat!( - "Offset of field: ", - stringify!(__wasi_prestat_t), - "::", - stringify!(pr_type) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_prestat_t>())).u as *const _ as usize }, - 4usize, - concat!( - "Offset of field: ", - stringify!(__wasi_prestat_t), - "::", - stringify!(u) - ) - ); -} -#[allow(non_snake_case)] -#[repr(C)] -#[derive(Copy, Clone)] -pub union __wasi_event_t__bindgen_ty_1 { - pub fd_readwrite: __wasi_event_t__bindgen_ty_1__bindgen_ty_1, - _bindgen_union_align: [u64; 2usize], -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __wasi_event_t__bindgen_ty_1__bindgen_ty_1 { - pub nbytes: __wasi_filesize_t, - pub flags: __wasi_eventrwflags_t, - pub __bindgen_padding_0: [u16; 3usize], -} -#[allow(non_snake_case)] -#[test] -fn bindgen_test_layout_wasi_event_t__bindgen_ty_1__bindgen_ty_1() { - assert_eq!( - ::std::mem::size_of::<__wasi_event_t__bindgen_ty_1__bindgen_ty_1>(), - 16usize, - concat!( - "Size of: ", - stringify!(__wasi_event_t__bindgen_ty_1__bindgen_ty_1) - ) - ); - assert_eq!( - unsafe { - &(*(::std::ptr::null::<__wasi_event_t__bindgen_ty_1__bindgen_ty_1>())).nbytes - as *const _ as usize - }, - 0usize, - concat!( - "Offset of field: ", - stringify!(__wasi_event_t__bindgen_ty_1__bindgen_ty_1), - "::", - stringify!(nbytes) - ) - ); - assert_eq!( - unsafe { - &(*(::std::ptr::null::<__wasi_event_t__bindgen_ty_1__bindgen_ty_1>())).flags as *const _ - as usize - }, - 8usize, - concat!( - "Offset of field: ", - stringify!(__wasi_event_t__bindgen_ty_1__bindgen_ty_1), - "::", - stringify!(flags) - ) - ); -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __wasi_event_t__bindgen_ty_1__bindgen_ty_2 { - pub signal: __wasi_signal_t, - pub exitcode: __wasi_exitcode_t, -} -#[allow(non_snake_case)] -#[test] -fn bindgen_test_layout_wasi_event_t__bindgen_ty_1__bindgen_ty_2() { - assert_eq!( - ::std::mem::size_of::<__wasi_event_t__bindgen_ty_1__bindgen_ty_2>(), - 8usize, - concat!( - "Size of: ", - stringify!(__wasi_event_t__bindgen_ty_1__bindgen_ty_2) - ) - ); - assert_eq!( - ::std::mem::align_of::<__wasi_event_t__bindgen_ty_1__bindgen_ty_2>(), - 4usize, - concat!( - "Alignment of ", - stringify!(__wasi_event_t__bindgen_ty_1__bindgen_ty_2) - ) - ); - assert_eq!( - unsafe { - &(*(::std::ptr::null::<__wasi_event_t__bindgen_ty_1__bindgen_ty_2>())).signal - as *const _ as usize - }, - 0usize, - concat!( - "Offset of field: ", - stringify!(__wasi_event_t__bindgen_ty_1__bindgen_ty_2), - "::", - stringify!(signal) - ) - ); - assert_eq!( - unsafe { - &(*(::std::ptr::null::<__wasi_event_t__bindgen_ty_1__bindgen_ty_2>())).exitcode - as *const _ as usize - }, - 4usize, - concat!( - "Offset of field: ", - stringify!(__wasi_event_t__bindgen_ty_1__bindgen_ty_2), - "::", - stringify!(exitcode) - ) - ); -} -#[allow(non_snake_case)] -#[test] -fn bindgen_test_layout_wasi_event_t__bindgen_ty_1() { - assert_eq!( - ::std::mem::size_of::<__wasi_event_t__bindgen_ty_1>(), - 16usize, - concat!("Size of: ", stringify!(__wasi_event_t__bindgen_ty_1)) - ); - assert_eq!( - unsafe { - &(*(::std::ptr::null::<__wasi_event_t__bindgen_ty_1>())).fd_readwrite as *const _ - as usize - }, - 0usize, - concat!( - "Offset of field: ", - stringify!(__wasi_event_t__bindgen_ty_1), - "::", - stringify!(fd_readwrite) - ) - ); -} -#[test] -fn bindgen_test_layout_wasi_event_t() { - assert_eq!( - ::std::mem::size_of::<__wasi_event_t>(), - 32usize, - concat!("Size of: ", stringify!(__wasi_event_t)) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_event_t>())).userdata as *const _ as usize }, - 0usize, - concat!( - "Offset of field: ", - stringify!(__wasi_event_t), - "::", - stringify!(userdata) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_event_t>())).error as *const _ as usize }, - 8usize, - concat!( - "Offset of field: ", - stringify!(__wasi_event_t), - "::", - stringify!(error) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_event_t>())).type_ as *const _ as usize }, - 10usize, - concat!( - "Offset of field: ", - stringify!(__wasi_event_t), - "::", - stringify!(type_) - ) - ); -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __wasi_fdstat_t { - pub fs_filetype: __wasi_filetype_t, - pub fs_flags: __wasi_fdflags_t, - pub __bindgen_padding_0: u32, - pub fs_rights_base: __wasi_rights_t, - pub fs_rights_inheriting: __wasi_rights_t, -} -#[test] -fn bindgen_test_layout_wasi_fdstat_t() { - assert_eq!( - ::std::mem::size_of::<__wasi_fdstat_t>(), - 24usize, - concat!("Size of: ", stringify!(__wasi_fdstat_t)) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_fdstat_t>())).fs_filetype as *const _ as usize }, - 0usize, - concat!( - "Offset of field: ", - stringify!(__wasi_fdstat_t), - "::", - stringify!(fs_filetype) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_fdstat_t>())).fs_flags as *const _ as usize }, - 2usize, - concat!( - "Offset of field: ", - stringify!(__wasi_fdstat_t), - "::", - stringify!(fs_flags) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_fdstat_t>())).fs_rights_base as *const _ as usize }, - 8usize, - concat!( - "Offset of field: ", - stringify!(__wasi_fdstat_t), - "::", - stringify!(fs_rights_base) - ) - ); - assert_eq!( - unsafe { - &(*(::std::ptr::null::<__wasi_fdstat_t>())).fs_rights_inheriting as *const _ as usize - }, - 16usize, - concat!( - "Offset of field: ", - stringify!(__wasi_fdstat_t), - "::", - stringify!(fs_rights_inheriting) - ) - ); -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __wasi_filestat_t { - pub st_dev: __wasi_device_t, - pub st_ino: __wasi_inode_t, - pub st_filetype: __wasi_filetype_t, - pub st_nlink: __wasi_linkcount_t, - pub st_size: __wasi_filesize_t, - pub st_atim: __wasi_timestamp_t, - pub st_mtim: __wasi_timestamp_t, - pub st_ctim: __wasi_timestamp_t, -} -#[test] -fn bindgen_test_layout_wasi_filestat_t() { - assert_eq!( - ::std::mem::size_of::<__wasi_filestat_t>(), - 56usize, - concat!("Size of: ", stringify!(__wasi_filestat_t)) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_dev as *const _ as usize }, - 0usize, - concat!( - "Offset of field: ", - stringify!(__wasi_filestat_t), - "::", - stringify!(st_dev) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_ino as *const _ as usize }, - 8usize, - concat!( - "Offset of field: ", - stringify!(__wasi_filestat_t), - "::", - stringify!(st_ino) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_filetype as *const _ as usize }, - 16usize, - concat!( - "Offset of field: ", - stringify!(__wasi_filestat_t), - "::", - stringify!(st_filetype) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_nlink as *const _ as usize }, - 20usize, - concat!( - "Offset of field: ", - stringify!(__wasi_filestat_t), - "::", - stringify!(st_nlink) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_size as *const _ as usize }, - 24usize, - concat!( - "Offset of field: ", - stringify!(__wasi_filestat_t), - "::", - stringify!(st_size) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_atim as *const _ as usize }, - 32usize, - concat!( - "Offset of field: ", - stringify!(__wasi_filestat_t), - "::", - stringify!(st_atim) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_mtim as *const _ as usize }, - 40usize, - concat!( - "Offset of field: ", - stringify!(__wasi_filestat_t), - "::", - stringify!(st_mtim) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_ctim as *const _ as usize }, - 48usize, - concat!( - "Offset of field: ", - stringify!(__wasi_filestat_t), - "::", - stringify!(st_ctim) - ) - ); -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __wasi_ciovec_t { - pub buf: uintptr_t, // *const ::std::os::raw::c_void - pub buf_len: size_t, -} -#[test] -fn bindgen_test_layout_wasi_ciovec_t() { - assert_eq!( - ::std::mem::size_of::<__wasi_ciovec_t>(), - 8usize, - concat!("Size of: ", stringify!(__wasi_ciovec_t)) - ); - assert_eq!( - ::std::mem::align_of::<__wasi_ciovec_t>(), - 4usize, - concat!("Alignment of ", stringify!(__wasi_ciovec_t)) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_ciovec_t>())).buf as *const _ as usize }, - 0usize, - concat!( - "Offset of field: ", - stringify!(__wasi_ciovec_t), - "::", - stringify!(buf) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_ciovec_t>())).buf_len as *const _ as usize }, - 4usize, - concat!( - "Offset of field: ", - stringify!(__wasi_ciovec_t), - "::", - stringify!(buf_len) - ) - ); -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __wasi_iovec_t { - pub buf: uintptr_t, // *mut ::std::os::raw::c_void - pub buf_len: size_t, -} -#[test] -fn bindgen_test_layout_wasi_iovec_t() { - assert_eq!( - ::std::mem::size_of::<__wasi_iovec_t>(), - 8usize, - concat!("Size of: ", stringify!(__wasi_iovec_t)) - ); - assert_eq!( - ::std::mem::align_of::<__wasi_iovec_t>(), - 4usize, - concat!("Alignment of ", stringify!(__wasi_iovec_t)) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_iovec_t>())).buf as *const _ as usize }, - 0usize, - concat!( - "Offset of field: ", - stringify!(__wasi_iovec_t), - "::", - stringify!(buf) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_iovec_t>())).buf_len as *const _ as usize }, - 4usize, - concat!( - "Offset of field: ", - stringify!(__wasi_iovec_t), - "::", - stringify!(buf_len) - ) - ); -} -#[repr(C)] -#[derive(Copy, Clone)] -pub struct __wasi_subscription_t { - pub userdata: __wasi_userdata_t, - pub type_: __wasi_eventtype_t, - pub __bindgen_padding_0: u32, - pub __bindgen_anon_1: __wasi_subscription_t__bindgen_ty_1, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub union __wasi_subscription_t__bindgen_ty_1 { - pub clock: __wasi_subscription_t__bindgen_ty_1__bindgen_ty_1, - pub fd_readwrite: __wasi_subscription_t__bindgen_ty_1__bindgen_ty_3, - _bindgen_union_align: [u64; 5usize], -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __wasi_subscription_t__bindgen_ty_1__bindgen_ty_1 { - pub identifier: __wasi_userdata_t, - pub clock_id: __wasi_clockid_t, - pub __bindgen_padding_0: u32, - pub timeout: __wasi_timestamp_t, - pub precision: __wasi_timestamp_t, - pub flags: __wasi_subclockflags_t, - pub __bindgen_padding_1: [u16; 3usize], -} -#[allow(non_snake_case)] -#[test] -fn bindgen_test_layout_wasi_subscription_t__bindgen_ty_1__bindgen_ty_1() { - assert_eq!( - ::std::mem::size_of::<__wasi_subscription_t__bindgen_ty_1__bindgen_ty_1>(), - 40usize, - concat!( - "Size of: ", - stringify!(__wasi_subscription_t__bindgen_ty_1__bindgen_ty_1) - ) - ); - assert_eq!( - unsafe { - &(*(::std::ptr::null::<__wasi_subscription_t__bindgen_ty_1__bindgen_ty_1>())).identifier - as *const _ as usize - }, - 0usize, - concat!( - "Offset of field: ", - stringify!(__wasi_subscription_t__bindgen_ty_1__bindgen_ty_1), - "::", - stringify!(identifier) - ) - ); - assert_eq!( - unsafe { - &(*(::std::ptr::null::<__wasi_subscription_t__bindgen_ty_1__bindgen_ty_1>())).clock_id - as *const _ as usize - }, - 8usize, - concat!( - "Offset of field: ", - stringify!(__wasi_subscription_t__bindgen_ty_1__bindgen_ty_1), - "::", - stringify!(clock_id) - ) - ); - assert_eq!( - unsafe { - &(*(::std::ptr::null::<__wasi_subscription_t__bindgen_ty_1__bindgen_ty_1>())).timeout - as *const _ as usize - }, - 16usize, - concat!( - "Offset of field: ", - stringify!(__wasi_subscription_t__bindgen_ty_1__bindgen_ty_1), - "::", - stringify!(timeout) - ) - ); - assert_eq!( - unsafe { - &(*(::std::ptr::null::<__wasi_subscription_t__bindgen_ty_1__bindgen_ty_1>())).precision - as *const _ as usize - }, - 24usize, - concat!( - "Offset of field: ", - stringify!(__wasi_subscription_t__bindgen_ty_1__bindgen_ty_1), - "::", - stringify!(precision) - ) - ); - assert_eq!( - unsafe { - &(*(::std::ptr::null::<__wasi_subscription_t__bindgen_ty_1__bindgen_ty_1>())).flags - as *const _ as usize - }, - 32usize, - concat!( - "Offset of field: ", - stringify!(__wasi_subscription_t__bindgen_ty_1__bindgen_ty_1), - "::", - stringify!(flags) - ) - ); -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __wasi_subscription_t__bindgen_ty_1__bindgen_ty_3 { - pub fd: __wasi_fd_t, -} -#[allow(non_snake_case)] -#[test] -fn bindgen_test_layout_wasi_subscription_t__bindgen_ty_1__bindgen_ty_3() { - assert_eq!( - ::std::mem::size_of::<__wasi_subscription_t__bindgen_ty_1__bindgen_ty_3>(), - 4usize, - concat!( - "Size of: ", - stringify!(__wasi_subscription_t__bindgen_ty_1__bindgen_ty_3) - ) - ); - assert_eq!( - ::std::mem::align_of::<__wasi_subscription_t__bindgen_ty_1__bindgen_ty_3>(), - 4usize, - concat!( - "Alignment of ", - stringify!(__wasi_subscription_t__bindgen_ty_1__bindgen_ty_3) - ) - ); - assert_eq!( - unsafe { - &(*(::std::ptr::null::<__wasi_subscription_t__bindgen_ty_1__bindgen_ty_3>())).fd - as *const _ as usize - }, - 0usize, - concat!( - "Offset of field: ", - stringify!(__wasi_subscription_t__bindgen_ty_1__bindgen_ty_3), - "::", - stringify!(fd) - ) - ); -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __wasi_subscription_t__bindgen_ty_1__bindgen_ty_5 { - pub fd: __wasi_fd_t, -} -#[allow(non_snake_case)] -#[test] -fn bindgen_test_layout_wasi_subscription_t__bindgen_ty_1__bindgen_ty_5() { - assert_eq!( - ::std::mem::size_of::<__wasi_subscription_t__bindgen_ty_1__bindgen_ty_5>(), - 4usize, - concat!( - "Size of: ", - stringify!(__wasi_subscription_t__bindgen_ty_1__bindgen_ty_5) - ) - ); - assert_eq!( - ::std::mem::align_of::<__wasi_subscription_t__bindgen_ty_1__bindgen_ty_5>(), - 4usize, - concat!( - "Alignment of ", - stringify!(__wasi_subscription_t__bindgen_ty_1__bindgen_ty_5) - ) - ); - assert_eq!( - unsafe { - &(*(::std::ptr::null::<__wasi_subscription_t__bindgen_ty_1__bindgen_ty_5>())).fd - as *const _ as usize - }, - 0usize, - concat!( - "Offset of field: ", - stringify!(__wasi_subscription_t__bindgen_ty_1__bindgen_ty_5), - "::", - stringify!(fd) - ) - ); -} -#[allow(non_snake_case)] -#[test] -fn bindgen_test_layout_wasi_subscription_t__bindgen_ty_1() { - assert_eq!( - ::std::mem::size_of::<__wasi_subscription_t__bindgen_ty_1>(), - 40usize, - concat!("Size of: ", stringify!(__wasi_subscription_t__bindgen_ty_1)) - ); - assert_eq!( - unsafe { - &(*(::std::ptr::null::<__wasi_subscription_t__bindgen_ty_1>())).clock as *const _ - as usize - }, - 0usize, - concat!( - "Offset of field: ", - stringify!(__wasi_subscription_t__bindgen_ty_1), - "::", - stringify!(clock) - ) - ); - assert_eq!( - unsafe { - &(*(::std::ptr::null::<__wasi_subscription_t__bindgen_ty_1>())).fd_readwrite as *const _ - as usize - }, - 0usize, - concat!( - "Offset of field: ", - stringify!(__wasi_subscription_t__bindgen_ty_1), - "::", - stringify!(fd_readwrite) - ) - ); -} -#[allow(non_snake_case)] -#[test] -fn bindgen_test_layout_wasi_subscription_t() { - assert_eq!( - ::std::mem::size_of::<__wasi_subscription_t>(), - 56usize, - concat!("Size of: ", stringify!(__wasi_subscription_t)) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_subscription_t>())).userdata as *const _ as usize }, - 0usize, - concat!( - "Offset of field: ", - stringify!(__wasi_subscription_t), - "::", - stringify!(userdata) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::<__wasi_subscription_t>())).type_ as *const _ as usize }, - 8usize, - concat!( - "Offset of field: ", - stringify!(__wasi_subscription_t), - "::", - stringify!(type_) - ) - ); -} - -pub fn strerror(errno: __wasi_errno_t) -> &'static str { - match errno { - __WASI_ESUCCESS => "__WASI_ESUCCESS", - __WASI_E2BIG => "__WASI_E2BIG", - __WASI_EACCES => "__WASI_EACCES", - __WASI_EADDRINUSE => "__WASI_EADDRINUSE", - __WASI_EADDRNOTAVAIL => "__WASI_EADDRNOTAVAIL", - __WASI_EAFNOSUPPORT => "__WASI_EAFNOSUPPORT", - __WASI_EAGAIN => "__WASI_EAGAIN", - __WASI_EALREADY => "__WASI_EALREADY", - __WASI_EBADF => "__WASI_EBADF", - __WASI_EBADMSG => "__WASI_EBADMSG", - __WASI_EBUSY => "__WASI_EBUSY", - __WASI_ECANCELED => "__WASI_ECANCELED", - __WASI_ECHILD => "__WASI_ECHILD", - __WASI_ECONNABORTED => "__WASI_ECONNABORTED", - __WASI_ECONNREFUSED => "__WASI_ECONNREFUSED", - __WASI_ECONNRESET => "__WASI_ECONNRESET", - __WASI_EDEADLK => "__WASI_EDEADLK", - __WASI_EDESTADDRREQ => "__WASI_EDESTADDRREQ", - __WASI_EDOM => "__WASI_EDOM", - __WASI_EDQUOT => "__WASI_EDQUOT", - __WASI_EEXIST => "__WASI_EEXIST", - __WASI_EFAULT => "__WASI_EFAULT", - __WASI_EFBIG => "__WASI_EFBIG", - __WASI_EHOSTUNREACH => "__WASI_EHOSTUNREACH", - __WASI_EIDRM => "__WASI_EIDRM", - __WASI_EILSEQ => "__WASI_EILSEQ", - __WASI_EINPROGRESS => "__WASI_EINPROGRESS", - __WASI_EINTR => "__WASI_EINTR", - __WASI_EINVAL => "__WASI_EINVAL", - __WASI_EIO => "__WASI_EIO", - __WASI_EISCONN => "__WASI_EISCONN", - __WASI_EISDIR => "__WASI_EISDIR", - __WASI_ELOOP => "__WASI_ELOOP", - __WASI_EMFILE => "__WASI_EMFILE", - __WASI_EMLINK => "__WASI_EMLINK", - __WASI_EMSGSIZE => "__WASI_EMSGSIZE", - __WASI_EMULTIHOP => "__WASI_EMULTIHOP", - __WASI_ENAMETOOLONG => "__WASI_ENAMETOOLONG", - __WASI_ENETDOWN => "__WASI_ENETDOWN", - __WASI_ENETRESET => "__WASI_ENETRESET", - __WASI_ENETUNREACH => "__WASI_ENETUNREACH", - __WASI_ENFILE => "__WASI_ENFILE", - __WASI_ENOBUFS => "__WASI_ENOBUFS", - __WASI_ENODEV => "__WASI_ENODEV", - __WASI_ENOENT => "__WASI_ENOENT", - __WASI_ENOEXEC => "__WASI_ENOEXEC", - __WASI_ENOLCK => "__WASI_ENOLCK", - __WASI_ENOLINK => "__WASI_ENOLINK", - __WASI_ENOMEM => "__WASI_ENOMEM", - __WASI_ENOMSG => "__WASI_ENOMSG", - __WASI_ENOPROTOOPT => "__WASI_ENOPROTOOPT", - __WASI_ENOSPC => "__WASI_ENOSPC", - __WASI_ENOSYS => "__WASI_ENOSYS", - __WASI_ENOTCONN => "__WASI_ENOTCONN", - __WASI_ENOTDIR => "__WASI_ENOTDIR", - __WASI_ENOTEMPTY => "__WASI_ENOTEMPTY", - __WASI_ENOTRECOVERABLE => "__WASI_ENOTRECOVERABLE", - __WASI_ENOTSOCK => "__WASI_ENOTSOCK", - __WASI_ENOTSUP => "__WASI_ENOTSUP", - __WASI_ENOTTY => "__WASI_ENOTTY", - __WASI_ENXIO => "__WASI_ENXIO", - __WASI_EOVERFLOW => "__WASI_EOVERFLOW", - __WASI_EOWNERDEAD => "__WASI_EOWNERDEAD", - __WASI_EPERM => "__WASI_EPERM", - __WASI_EPIPE => "__WASI_EPIPE", - __WASI_EPROTO => "__WASI_EPROTO", - __WASI_EPROTONOSUPPORT => "__WASI_EPROTONOSUPPORT", - __WASI_EPROTOTYPE => "__WASI_EPROTOTYPE", - __WASI_ERANGE => "__WASI_ERANGE", - __WASI_EROFS => "__WASI_EROFS", - __WASI_ESPIPE => "__WASI_ESPIPE", - __WASI_ESRCH => "__WASI_ESRCH", - __WASI_ESTALE => "__WASI_ESTALE", - __WASI_ETIMEDOUT => "__WASI_ETIMEDOUT", - __WASI_ETXTBSY => "__WASI_ETXTBSY", - __WASI_EXDEV => "__WASI_EXDEV", - __WASI_ENOTCAPABLE => "__WASI_ENOTCAPABLE", - other => panic!("Undefined errno value {:?}", other), - } -} - -pub fn whence_to_str(whence: __wasi_whence_t) -> &'static str { - match whence { - __WASI_WHENCE_CUR => "__WASI_WHENCE_CUR", - __WASI_WHENCE_END => "__WASI_WHENCE_END", - __WASI_WHENCE_SET => "__WASI_WHENCE_SET", - other => panic!("Undefined whence value {:?}", other), - } -} - -// libc constants -pub const INT8_MIN: i32 = -128; -pub const INT16_MIN: i32 = -32768; -pub const INT32_MIN: i32 = -2147483648; -pub const INT8_MAX: u32 = 127; -pub const INT16_MAX: u32 = 32767; -pub const INT32_MAX: u32 = 2147483647; -pub const UINT8_MAX: u32 = 255; -pub const UINT16_MAX: u32 = 65535; -pub const UINT32_MAX: u32 = 4294967295; -pub const INT_LEAST8_MIN: i32 = -128; -pub const INT_LEAST16_MIN: i32 = -32768; -pub const INT_LEAST32_MIN: i32 = -2147483648; -pub const INT_LEAST8_MAX: u32 = 127; -pub const INT_LEAST16_MAX: u32 = 32767; -pub const INT_LEAST32_MAX: u32 = 2147483647; -pub const UINT_LEAST8_MAX: u32 = 255; -pub const UINT_LEAST16_MAX: u32 = 65535; -pub const UINT_LEAST32_MAX: u32 = 4294967295; -pub const INT_FAST8_MIN: i32 = -128; -pub const INT_FAST16_MIN: i32 = -2147483648; -pub const INT_FAST32_MIN: i32 = -2147483648; -pub const INT_FAST8_MAX: u32 = 127; -pub const INT_FAST16_MAX: u32 = 2147483647; -pub const INT_FAST32_MAX: u32 = 2147483647; -pub const UINT_FAST8_MAX: u32 = 255; -pub const UINT_FAST16_MAX: u32 = 4294967295; -pub const UINT_FAST32_MAX: u32 = 4294967295; -pub const INTPTR_MIN: i32 = -2147483648; -pub const INTPTR_MAX: u32 = 2147483647; -pub const UINTPTR_MAX: u32 = 4294967295; -pub const PTRDIFF_MIN: i32 = -2147483648; -pub const PTRDIFF_MAX: u32 = 2147483647; -pub const SIG_ATOMIC_MIN: i32 = -2147483648; -pub const SIG_ATOMIC_MAX: u32 = 2147483647; -pub const SIZE_MAX: u32 = 4294967295; -pub const WINT_MIN: i32 = -2147483648; -pub const WINT_MAX: i32 = 2147483647; - -// WASI constants -pub const __WASI_ADVICE_NORMAL: __wasi_advice_t = 0; -pub const __WASI_ADVICE_SEQUENTIAL: __wasi_advice_t = 1; -pub const __WASI_ADVICE_RANDOM: __wasi_advice_t = 2; -pub const __WASI_ADVICE_WILLNEED: __wasi_advice_t = 3; -pub const __WASI_ADVICE_DONTNEED: __wasi_advice_t = 4; -pub const __WASI_ADVICE_NOREUSE: __wasi_advice_t = 5; -pub const __WASI_CLOCK_REALTIME: __wasi_clockid_t = 0; -pub const __WASI_CLOCK_MONOTONIC: __wasi_clockid_t = 1; -pub const __WASI_CLOCK_PROCESS_CPUTIME_ID: __wasi_clockid_t = 2; -pub const __WASI_CLOCK_THREAD_CPUTIME_ID: __wasi_clockid_t = 3; -pub const __WASI_DIRCOOKIE_START: __wasi_dircookie_t = 0; -pub const __WASI_ESUCCESS: __wasi_errno_t = 0; -pub const __WASI_E2BIG: __wasi_errno_t = 1; -pub const __WASI_EACCES: __wasi_errno_t = 2; -pub const __WASI_EADDRINUSE: __wasi_errno_t = 3; -pub const __WASI_EADDRNOTAVAIL: __wasi_errno_t = 4; -pub const __WASI_EAFNOSUPPORT: __wasi_errno_t = 5; -pub const __WASI_EAGAIN: __wasi_errno_t = 6; -pub const __WASI_EALREADY: __wasi_errno_t = 7; -pub const __WASI_EBADF: __wasi_errno_t = 8; -pub const __WASI_EBADMSG: __wasi_errno_t = 9; -pub const __WASI_EBUSY: __wasi_errno_t = 10; -pub const __WASI_ECANCELED: __wasi_errno_t = 11; -pub const __WASI_ECHILD: __wasi_errno_t = 12; -pub const __WASI_ECONNABORTED: __wasi_errno_t = 13; -pub const __WASI_ECONNREFUSED: __wasi_errno_t = 14; -pub const __WASI_ECONNRESET: __wasi_errno_t = 15; -pub const __WASI_EDEADLK: __wasi_errno_t = 16; -pub const __WASI_EDESTADDRREQ: __wasi_errno_t = 17; -pub const __WASI_EDOM: __wasi_errno_t = 18; -pub const __WASI_EDQUOT: __wasi_errno_t = 19; -pub const __WASI_EEXIST: __wasi_errno_t = 20; -pub const __WASI_EFAULT: __wasi_errno_t = 21; -pub const __WASI_EFBIG: __wasi_errno_t = 22; -pub const __WASI_EHOSTUNREACH: __wasi_errno_t = 23; -pub const __WASI_EIDRM: __wasi_errno_t = 24; -pub const __WASI_EILSEQ: __wasi_errno_t = 25; -pub const __WASI_EINPROGRESS: __wasi_errno_t = 26; -pub const __WASI_EINTR: __wasi_errno_t = 27; -pub const __WASI_EINVAL: __wasi_errno_t = 28; -pub const __WASI_EIO: __wasi_errno_t = 29; -pub const __WASI_EISCONN: __wasi_errno_t = 30; -pub const __WASI_EISDIR: __wasi_errno_t = 31; -pub const __WASI_ELOOP: __wasi_errno_t = 32; -pub const __WASI_EMFILE: __wasi_errno_t = 33; -pub const __WASI_EMLINK: __wasi_errno_t = 34; -pub const __WASI_EMSGSIZE: __wasi_errno_t = 35; -pub const __WASI_EMULTIHOP: __wasi_errno_t = 36; -pub const __WASI_ENAMETOOLONG: __wasi_errno_t = 37; -pub const __WASI_ENETDOWN: __wasi_errno_t = 38; -pub const __WASI_ENETRESET: __wasi_errno_t = 39; -pub const __WASI_ENETUNREACH: __wasi_errno_t = 40; -pub const __WASI_ENFILE: __wasi_errno_t = 41; -pub const __WASI_ENOBUFS: __wasi_errno_t = 42; -pub const __WASI_ENODEV: __wasi_errno_t = 43; -pub const __WASI_ENOENT: __wasi_errno_t = 44; -pub const __WASI_ENOEXEC: __wasi_errno_t = 45; -pub const __WASI_ENOLCK: __wasi_errno_t = 46; -pub const __WASI_ENOLINK: __wasi_errno_t = 47; -pub const __WASI_ENOMEM: __wasi_errno_t = 48; -pub const __WASI_ENOMSG: __wasi_errno_t = 49; -pub const __WASI_ENOPROTOOPT: __wasi_errno_t = 50; -pub const __WASI_ENOSPC: __wasi_errno_t = 51; -pub const __WASI_ENOSYS: __wasi_errno_t = 52; -pub const __WASI_ENOTCONN: __wasi_errno_t = 53; -pub const __WASI_ENOTDIR: __wasi_errno_t = 54; -pub const __WASI_ENOTEMPTY: __wasi_errno_t = 55; -pub const __WASI_ENOTRECOVERABLE: __wasi_errno_t = 56; -pub const __WASI_ENOTSOCK: __wasi_errno_t = 57; -pub const __WASI_ENOTSUP: __wasi_errno_t = 58; -pub const __WASI_ENOTTY: __wasi_errno_t = 59; -pub const __WASI_ENXIO: __wasi_errno_t = 60; -pub const __WASI_EOVERFLOW: __wasi_errno_t = 61; -pub const __WASI_EOWNERDEAD: __wasi_errno_t = 62; -pub const __WASI_EPERM: __wasi_errno_t = 63; -pub const __WASI_EPIPE: __wasi_errno_t = 64; -pub const __WASI_EPROTO: __wasi_errno_t = 65; -pub const __WASI_EPROTONOSUPPORT: __wasi_errno_t = 66; -pub const __WASI_EPROTOTYPE: __wasi_errno_t = 67; -pub const __WASI_ERANGE: __wasi_errno_t = 68; -pub const __WASI_EROFS: __wasi_errno_t = 69; -pub const __WASI_ESPIPE: __wasi_errno_t = 70; -pub const __WASI_ESRCH: __wasi_errno_t = 71; -pub const __WASI_ESTALE: __wasi_errno_t = 72; -pub const __WASI_ETIMEDOUT: __wasi_errno_t = 73; -pub const __WASI_ETXTBSY: __wasi_errno_t = 74; -pub const __WASI_EXDEV: __wasi_errno_t = 75; -pub const __WASI_ENOTCAPABLE: __wasi_errno_t = 76; -pub const __WASI_EVENT_FD_READWRITE_HANGUP: __wasi_eventrwflags_t = 1; -pub const __WASI_EVENTTYPE_CLOCK: __wasi_eventtype_t = 0; -pub const __WASI_EVENTTYPE_FD_READ: __wasi_eventtype_t = 1; -pub const __WASI_EVENTTYPE_FD_WRITE: __wasi_eventtype_t = 2; -pub const __WASI_FDFLAG_APPEND: __wasi_fdflags_t = 1; -pub const __WASI_FDFLAG_DSYNC: __wasi_fdflags_t = 2; -pub const __WASI_FDFLAG_NONBLOCK: __wasi_fdflags_t = 4; -pub const __WASI_FDFLAG_RSYNC: __wasi_fdflags_t = 8; -pub const __WASI_FDFLAG_SYNC: __wasi_fdflags_t = 16; -pub const __WASI_PREOPENTYPE_DIR: __wasi_preopentype_t = 0; -pub const __WASI_FILETYPE_UNKNOWN: __wasi_filetype_t = 0; -pub const __WASI_FILETYPE_BLOCK_DEVICE: __wasi_filetype_t = 1; -pub const __WASI_FILETYPE_CHARACTER_DEVICE: __wasi_filetype_t = 2; -pub const __WASI_FILETYPE_DIRECTORY: __wasi_filetype_t = 3; -pub const __WASI_FILETYPE_REGULAR_FILE: __wasi_filetype_t = 4; -pub const __WASI_FILETYPE_SOCKET_DGRAM: __wasi_filetype_t = 5; -pub const __WASI_FILETYPE_SOCKET_STREAM: __wasi_filetype_t = 6; -pub const __WASI_FILETYPE_SYMBOLIC_LINK: __wasi_filetype_t = 7; -pub const __WASI_FILESTAT_SET_ATIM: __wasi_fstflags_t = 1; -pub const __WASI_FILESTAT_SET_ATIM_NOW: __wasi_fstflags_t = 2; -pub const __WASI_FILESTAT_SET_MTIM: __wasi_fstflags_t = 4; -pub const __WASI_FILESTAT_SET_MTIM_NOW: __wasi_fstflags_t = 8; -pub const __WASI_LOOKUP_SYMLINK_FOLLOW: __wasi_lookupflags_t = 1; -pub const __WASI_O_CREAT: __wasi_oflags_t = 1; -pub const __WASI_O_DIRECTORY: __wasi_oflags_t = 2; -pub const __WASI_O_EXCL: __wasi_oflags_t = 4; -pub const __WASI_O_TRUNC: __wasi_oflags_t = 8; -pub const __WASI_SOCK_RECV_PEEK: __wasi_riflags_t = 1; -pub const __WASI_SOCK_RECV_WAITALL: __wasi_riflags_t = 2; -pub const __WASI_RIGHT_FD_DATASYNC: __wasi_rights_t = 1; -pub const __WASI_RIGHT_FD_READ: __wasi_rights_t = 2; -pub const __WASI_RIGHT_FD_SEEK: __wasi_rights_t = 4; -pub const __WASI_RIGHT_FD_FDSTAT_SET_FLAGS: __wasi_rights_t = 8; -pub const __WASI_RIGHT_FD_SYNC: __wasi_rights_t = 16; -pub const __WASI_RIGHT_FD_TELL: __wasi_rights_t = 32; -pub const __WASI_RIGHT_FD_WRITE: __wasi_rights_t = 64; -pub const __WASI_RIGHT_FD_ADVISE: __wasi_rights_t = 128; -pub const __WASI_RIGHT_FD_ALLOCATE: __wasi_rights_t = 256; -pub const __WASI_RIGHT_PATH_CREATE_DIRECTORY: __wasi_rights_t = 512; -pub const __WASI_RIGHT_PATH_CREATE_FILE: __wasi_rights_t = 1024; -pub const __WASI_RIGHT_PATH_LINK_SOURCE: __wasi_rights_t = 2048; -pub const __WASI_RIGHT_PATH_LINK_TARGET: __wasi_rights_t = 4096; -pub const __WASI_RIGHT_PATH_OPEN: __wasi_rights_t = 8192; -pub const __WASI_RIGHT_FD_READDIR: __wasi_rights_t = 16384; -pub const __WASI_RIGHT_PATH_READLINK: __wasi_rights_t = 32768; -pub const __WASI_RIGHT_PATH_RENAME_SOURCE: __wasi_rights_t = 65536; -pub const __WASI_RIGHT_PATH_RENAME_TARGET: __wasi_rights_t = 131072; -pub const __WASI_RIGHT_PATH_FILESTAT_GET: __wasi_rights_t = 262144; -pub const __WASI_RIGHT_PATH_FILESTAT_SET_SIZE: __wasi_rights_t = 524288; -pub const __WASI_RIGHT_PATH_FILESTAT_SET_TIMES: __wasi_rights_t = 1048576; -pub const __WASI_RIGHT_FD_FILESTAT_GET: __wasi_rights_t = 2097152; -pub const __WASI_RIGHT_FD_FILESTAT_SET_SIZE: __wasi_rights_t = 4194304; -pub const __WASI_RIGHT_FD_FILESTAT_SET_TIMES: __wasi_rights_t = 8388608; -pub const __WASI_RIGHT_PATH_SYMLINK: __wasi_rights_t = 16777216; -pub const __WASI_RIGHT_PATH_REMOVE_DIRECTORY: __wasi_rights_t = 33554432; -pub const __WASI_RIGHT_PATH_UNLINK_FILE: __wasi_rights_t = 67108864; -pub const __WASI_RIGHT_POLL_FD_READWRITE: __wasi_rights_t = 134217728; -pub const __WASI_RIGHT_SOCK_SHUTDOWN: __wasi_rights_t = 268435456; -pub const __WASI_SOCK_RECV_DATA_TRUNCATED: __wasi_roflags_t = 1; -pub const __WASI_SHUT_RD: __wasi_sdflags_t = 1; -pub const __WASI_SHUT_WR: __wasi_sdflags_t = 2; -pub const __WASI_SIGHUP: __wasi_signal_t = 1; -pub const __WASI_SIGINT: __wasi_signal_t = 2; -pub const __WASI_SIGQUIT: __wasi_signal_t = 3; -pub const __WASI_SIGILL: __wasi_signal_t = 4; -pub const __WASI_SIGTRAP: __wasi_signal_t = 5; -pub const __WASI_SIGABRT: __wasi_signal_t = 6; -pub const __WASI_SIGBUS: __wasi_signal_t = 7; -pub const __WASI_SIGFPE: __wasi_signal_t = 8; -pub const __WASI_SIGKILL: __wasi_signal_t = 9; -pub const __WASI_SIGUSR1: __wasi_signal_t = 10; -pub const __WASI_SIGSEGV: __wasi_signal_t = 11; -pub const __WASI_SIGUSR2: __wasi_signal_t = 12; -pub const __WASI_SIGPIPE: __wasi_signal_t = 13; -pub const __WASI_SIGALRM: __wasi_signal_t = 14; -pub const __WASI_SIGTERM: __wasi_signal_t = 15; -pub const __WASI_SIGCHLD: __wasi_signal_t = 16; -pub const __WASI_SIGCONT: __wasi_signal_t = 17; -pub const __WASI_SIGSTOP: __wasi_signal_t = 18; -pub const __WASI_SIGTSTP: __wasi_signal_t = 19; -pub const __WASI_SIGTTIN: __wasi_signal_t = 20; -pub const __WASI_SIGTTOU: __wasi_signal_t = 21; -pub const __WASI_SIGURG: __wasi_signal_t = 22; -pub const __WASI_SIGXCPU: __wasi_signal_t = 23; -pub const __WASI_SIGXFSZ: __wasi_signal_t = 24; -pub const __WASI_SIGVTALRM: __wasi_signal_t = 25; -pub const __WASI_SIGPROF: __wasi_signal_t = 26; -pub const __WASI_SIGWINCH: __wasi_signal_t = 27; -pub const __WASI_SIGPOLL: __wasi_signal_t = 28; -pub const __WASI_SIGPWR: __wasi_signal_t = 29; -pub const __WASI_SIGSYS: __wasi_signal_t = 30; -pub const __WASI_SUBSCRIPTION_CLOCK_ABSTIME: __wasi_subclockflags_t = 1; -pub const __WASI_WHENCE_CUR: __wasi_whence_t = 0; -pub const __WASI_WHENCE_END: __wasi_whence_t = 1; -pub const __WASI_WHENCE_SET: __wasi_whence_t = 2; - -pub fn errno_from_nix(errno: nix::errno::Errno) -> __wasi_errno_t { - match errno { - nix::errno::Errno::EPERM => __WASI_EPERM, - nix::errno::Errno::ENOENT => __WASI_ENOENT, - nix::errno::Errno::ESRCH => __WASI_ESRCH, - nix::errno::Errno::EINTR => __WASI_EINTR, - nix::errno::Errno::EIO => __WASI_EIO, - nix::errno::Errno::ENXIO => __WASI_ENXIO, - nix::errno::Errno::E2BIG => __WASI_E2BIG, - nix::errno::Errno::ENOEXEC => __WASI_ENOEXEC, - nix::errno::Errno::EBADF => __WASI_EBADF, - nix::errno::Errno::ECHILD => __WASI_ECHILD, - nix::errno::Errno::EAGAIN => __WASI_EAGAIN, - nix::errno::Errno::ENOMEM => __WASI_ENOMEM, - nix::errno::Errno::EACCES => __WASI_EACCES, - nix::errno::Errno::EFAULT => __WASI_EFAULT, - nix::errno::Errno::EBUSY => __WASI_EBUSY, - nix::errno::Errno::EEXIST => __WASI_EEXIST, - nix::errno::Errno::EXDEV => __WASI_EXDEV, - nix::errno::Errno::ENODEV => __WASI_ENODEV, - nix::errno::Errno::ENOTDIR => __WASI_ENOTDIR, - nix::errno::Errno::EISDIR => __WASI_EISDIR, - nix::errno::Errno::EINVAL => __WASI_EINVAL, - nix::errno::Errno::ENFILE => __WASI_ENFILE, - nix::errno::Errno::EMFILE => __WASI_EMFILE, - nix::errno::Errno::ENOTTY => __WASI_ENOTTY, - nix::errno::Errno::ETXTBSY => __WASI_ETXTBSY, - nix::errno::Errno::EFBIG => __WASI_EFBIG, - nix::errno::Errno::ENOSPC => __WASI_ENOSPC, - nix::errno::Errno::ESPIPE => __WASI_ESPIPE, - nix::errno::Errno::EROFS => __WASI_EROFS, - nix::errno::Errno::EMLINK => __WASI_EMLINK, - nix::errno::Errno::EPIPE => __WASI_EPIPE, - nix::errno::Errno::EDOM => __WASI_EDOM, - nix::errno::Errno::ERANGE => __WASI_ERANGE, - nix::errno::Errno::EDEADLK => __WASI_EDEADLK, - nix::errno::Errno::ENAMETOOLONG => __WASI_ENAMETOOLONG, - nix::errno::Errno::ENOLCK => __WASI_ENOLCK, - nix::errno::Errno::ENOSYS => __WASI_ENOSYS, - nix::errno::Errno::ENOTEMPTY => __WASI_ENOTEMPTY, - nix::errno::Errno::ELOOP => __WASI_ELOOP, - nix::errno::Errno::ENOMSG => __WASI_ENOMSG, - nix::errno::Errno::EIDRM => __WASI_EIDRM, - nix::errno::Errno::ENOLINK => __WASI_ENOLINK, - nix::errno::Errno::EPROTO => __WASI_EPROTO, - nix::errno::Errno::EMULTIHOP => __WASI_EMULTIHOP, - nix::errno::Errno::EBADMSG => __WASI_EBADMSG, - nix::errno::Errno::EOVERFLOW => __WASI_EOVERFLOW, - nix::errno::Errno::EILSEQ => __WASI_EILSEQ, - nix::errno::Errno::ENOTSOCK => __WASI_ENOTSOCK, - nix::errno::Errno::EDESTADDRREQ => __WASI_EDESTADDRREQ, - nix::errno::Errno::EMSGSIZE => __WASI_EMSGSIZE, - nix::errno::Errno::EPROTOTYPE => __WASI_EPROTOTYPE, - nix::errno::Errno::ENOPROTOOPT => __WASI_ENOPROTOOPT, - nix::errno::Errno::EPROTONOSUPPORT => __WASI_EPROTONOSUPPORT, - nix::errno::Errno::EAFNOSUPPORT => __WASI_EAFNOSUPPORT, - nix::errno::Errno::EADDRINUSE => __WASI_EADDRINUSE, - nix::errno::Errno::EADDRNOTAVAIL => __WASI_EADDRNOTAVAIL, - nix::errno::Errno::ENETDOWN => __WASI_ENETDOWN, - nix::errno::Errno::ENETUNREACH => __WASI_ENETUNREACH, - nix::errno::Errno::ENETRESET => __WASI_ENETRESET, - nix::errno::Errno::ECONNABORTED => __WASI_ECONNABORTED, - nix::errno::Errno::ECONNRESET => __WASI_ECONNRESET, - nix::errno::Errno::ENOBUFS => __WASI_ENOBUFS, - nix::errno::Errno::EISCONN => __WASI_EISCONN, - nix::errno::Errno::ENOTCONN => __WASI_ENOTCONN, - nix::errno::Errno::ETIMEDOUT => __WASI_ETIMEDOUT, - nix::errno::Errno::ECONNREFUSED => __WASI_ECONNREFUSED, - nix::errno::Errno::EHOSTUNREACH => __WASI_EHOSTUNREACH, - nix::errno::Errno::EALREADY => __WASI_EALREADY, - nix::errno::Errno::EINPROGRESS => __WASI_EINPROGRESS, - nix::errno::Errno::ESTALE => __WASI_ESTALE, - nix::errno::Errno::EDQUOT => __WASI_EDQUOT, - nix::errno::Errno::ECANCELED => __WASI_ECANCELED, - nix::errno::Errno::EOWNERDEAD => __WASI_EOWNERDEAD, - nix::errno::Errno::ENOTRECOVERABLE => __WASI_ENOTRECOVERABLE, - _ => __WASI_ENOSYS, - } -} diff --git a/lucet-wasi/tests/guests/duplicate_import.wat b/lucet-wasi/tests/guests/duplicate_import.wat new file mode 100644 index 000000000..987286f33 --- /dev/null +++ b/lucet-wasi/tests/guests/duplicate_import.wat @@ -0,0 +1,38 @@ +(module + (type (func (param i32 i32 i32 i32) (result i32))) + (type (func)) + + ;; import fd_read, this is fine. + (func $read (import "wasi_unstable" "fd_read") (type 0)) + + ;; import fd_write, this is also fine. + (func $write (import "wasi_unstable" "fd_write") (type 0)) + + ;; import fd_read, again, under a different name! + ;; this is to test that we join together the imports. + ;; the .wat would be invalid if their types disagree, so there + ;; is no observable difference between $read and $read_2 + (func $read_2 (import "wasi_unstable" "fd_read") (type 0)) + + ;; import fd_write again for grins. + (import "wasi_unstable" "fd_write" (func (type 0))) + (memory 1) + (data (i32.const 0) "duplicate import works!\0a") + (data (i32.const 64) "\00\00\00\00\18\00\00\00") + + (func $_setup (type 1) + (drop (call $write (i32.const 1) (i32.const 64) (i32.const 1) (i32.const 0)))) + + ;; declare that, actually, one of the imported functions is exported + (export "read_2" (func $read_2)) + ;; and declare that the *other* read function is also exported, by a + ;; different name. This lets us check that when we merge the functions, + ;; we also merge their export names properly. + (export "read" (func $read)) + + ;; and check that other exported functions still work, and are not affected + (export "write" (func $write)) + + ;; and that we can export local functions without issue + (export "_start" (func $_setup)) +) diff --git a/lucet-wasi/tests/guests/fs.c b/lucet-wasi/tests/guests/fs.c new file mode 100644 index 000000000..6f4bfa78e --- /dev/null +++ b/lucet-wasi/tests/guests/fs.c @@ -0,0 +1,158 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(void) +{ + struct timespec times[2]; + struct dirent * entry; + char buf[4]; + DIR * dir; + FILE * fp; + struct stat st; + off_t offset; + int fd; + int res; + + fd = open("/sandbox/testfile", O_CREAT | O_RDWR, 0644); + assert(fd != -1); + + res = posix_fallocate(fd, 0, 10000); + assert(res == 0); + + res = fstat(fd, &st); + assert(res == 0); + assert(st.st_size == 10000); + + res = ftruncate(fd, 1000); + res = fstat(fd, &st); + assert(res == 0); + assert(st.st_size == 1000); + assert(st.st_nlink == 1); + res = posix_fadvise(fd, 0, 1000, POSIX_FADV_RANDOM); + assert(res == 0); + + res = (int) write(fd, "test", 4); + assert(res == 4); + + offset = lseek(fd, 0, SEEK_CUR); + assert(offset == 4); + offset = lseek(fd, 0, SEEK_END); + assert(offset == 1000); + offset = lseek(fd, 0, SEEK_SET); + assert(offset == 0); + + res = fdatasync(fd); + assert(res == 0); + + res = fsync(fd); + assert(res == 0); + + times[0] = (struct timespec){ .tv_sec = 1557403800, .tv_nsec = 0 }; + times[1] = (struct timespec){ .tv_sec = 1557403800, .tv_nsec = 0 }; + + res = futimens(fd, times); + assert(res == 0); + + res = pread(fd, buf, sizeof buf, 2); + assert(res == 4); + assert(buf[1] == 't'); + + res = pwrite(fd, "T", 1, 3); + assert(res == 1); + + res = pread(fd, buf, sizeof buf, 2); + assert(res == 4); + assert(buf[1] == 'T'); + + res = close(fd); + assert(res == 0); + + dir = opendir("/nonexistent"); + assert(dir == NULL); + + res = mkdir("/sandbox/test", 0755); + assert(res == 0); + + res = mkdir("/sandbox/test", 0755); + assert(res == -1); + assert(errno == EEXIST); + + res = rmdir("/sandbox/test"); + assert(res == 0); + + res = rmdir("/sandbox/test"); + assert(res == -1); + + res = rename("/sandbox/testfile", "/sandbox/testfile2"); + assert(res == 0); + + res = unlink("/sandbox/testfile"); + assert(res == -1); + + res = access("/sandbox/testfile2", R_OK); + assert(res == 0); + + res = link("/sandbox/testfile2", "/sandbox/testfile-link"); + assert(res == 0); + + res = access("/sandbox/testfile-link", R_OK); + assert(res == 0); + + res = symlink("/sandbox/testfile-link", "/sandbox/testfile-symlink"); + assert(res == 0); + + res = symlink("/sandbox/testfile2", "/sandbox/testfile-symlink"); + assert(res == -1); + + res = sched_yield(); + assert(res == 0); + + fd = open("/sandbox/testfile2", O_RDONLY); + assert(fd != -1); + + fp = fdopen(fd, "r"); + assert(fp != NULL); + + res = fgetc(fp); + assert(res == 't'); + + res = fclose(fp); + assert(res == 0); + + dir = opendir("/sandbox"); + assert(dir != NULL); + + res = 0; + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') { + res += 1000; + } else { + res++; + } + } + assert(res == 2003); + + res = closedir(dir); + assert(res == 0); + + res = mkdir("/sandbox/a", 0755); + assert(res == 0); + res = mkdir("/sandbox/a/b", 0755); + assert(res == 0); + res = mkdir("/sandbox/a/b/c", 0755); + assert(res == 0); + res = access("/sandbox/a/b/c", R_OK); + assert(res == 0); + + return 0; +} diff --git a/lucet-wasi/tests/guests/getrusage.c b/lucet-wasi/tests/guests/getrusage.c index 84e2bb7ce..7ff12fe12 100644 --- a/lucet-wasi/tests/guests/getrusage.c +++ b/lucet-wasi/tests/guests/getrusage.c @@ -1,6 +1,11 @@ #include #include +// Temporary fix until +// https://github.com/CraneStation/wasi-sysroot/pull/24/ +// is available in wasi-sdk package +extern int getrusage(int who, struct rusage *usage); + int main() { struct rusage ru1; diff --git a/lucet-wasi/tests/guests/poll.c b/lucet-wasi/tests/guests/poll.c new file mode 100644 index 000000000..bec0688e8 --- /dev/null +++ b/lucet-wasi/tests/guests/poll.c @@ -0,0 +1,28 @@ +#include +#include +#include +#include + +int main(void) +{ + struct pollfd fds[2]; + time_t before, now; + int ret; + + fds[0] = (struct pollfd){ .fd = 1, .events = POLLOUT, .revents = 0 }; + fds[1] = (struct pollfd){ .fd = 2, .events = POLLOUT, .revents = 0 }; + + ret = poll(fds, 2, -1); + assert(ret == 2); + assert(fds[0].revents == POLLOUT); + assert(fds[1].revents == POLLOUT); + + fds[0] = (struct pollfd){ .fd = 0, .events = POLLIN, .revents = 0 }; + time(&before); + ret = poll(fds, 1, 2000); + time(&now); + assert(ret == 0); + assert(now - before >= 1); + + return 0; +} diff --git a/lucet-wasi/tests/guests/stat.c b/lucet-wasi/tests/guests/stat.c new file mode 100644 index 000000000..7974c31d8 --- /dev/null +++ b/lucet-wasi/tests/guests/stat.c @@ -0,0 +1,54 @@ +#include + +#include +#include +#include +#include + +#define BASE_DIR "/sandbox" +#define OUTPUT_DIR BASE_DIR "/testdir" +#define PATH OUTPUT_DIR "/output.txt" +#define SIZE 500 + +int main(void) +{ + struct stat st; + int fd; + int ret; + off_t pos; + + (void) st; + ret = mkdir(OUTPUT_DIR, 0755); + assert(ret == 0); + + fd = open(PATH, O_CREAT | O_WRONLY, 0666); + assert(fd != -1); + + pos = lseek(fd, SIZE - 1, SEEK_SET); + assert(pos == SIZE - 1); + + ret = (int) write(fd, "", 1); + assert(ret == 1); + + ret = fstat(fd, &st); + assert(ret == 0); + assert(st.st_size == SIZE); + + ret = close(fd); + assert(ret == 0); + + ret = access(PATH, R_OK); + assert(ret == 0); + + ret = stat(PATH, &st); + assert(ret == 0); + assert(st.st_size == SIZE); + + ret = unlink(PATH); + assert(ret == 0); + + ret = stat(PATH, &st); + assert(ret == -1); + + return 0; +} \ No newline at end of file diff --git a/lucet-wasi/tests/test_helpers/mod.rs b/lucet-wasi/tests/test_helpers/mod.rs index 0eac093a5..8ff4dc0e4 100644 --- a/lucet-wasi/tests/test_helpers/mod.rs +++ b/lucet-wasi/tests/test_helpers/mod.rs @@ -1,10 +1,8 @@ -use failure::{bail, Error}; -use lucet_runtime::{DlModule, Module}; -use lucet_runtime::{Limits, MmapRegion, Region}; -use lucet_wasi::host::__wasi_exitcode_t; -use lucet_wasi::{WasiCtx, WasiCtxBuilder}; -use lucet_wasi_sdk::Link; -use lucetc::{Bindings, Lucetc}; +use anyhow::{bail, Error}; +use lucet_runtime::{DlModule, Limits, MmapRegion, Module, Region}; +use lucet_wasi::{self, WasiCtx, WasiCtxBuilder, __wasi_exitcode_t}; +use lucet_wasi_sdk::{CompileOpts, Link}; +use lucetc::{Lucetc, LucetcOpts}; use std::fs::File; use std::io::Read; use std::os::unix::io::{FromRawFd, IntoRawFd}; @@ -32,21 +30,42 @@ pub fn guest_file>(path: P) -> PathBuf { p } -pub fn wasi_test>(c_file: P) -> Result, Error> { +pub fn wasi_test>(file: P) -> Result, Error> { let workdir = TempDir::new().expect("create working directory"); - let wasm_build = Link::new(&[c_file]) - .cflag("-Wall") - .cflag("-Werror") - .print_output(true); - - let wasm_file = workdir.path().join("out.wasm"); - - wasm_build.link(wasm_file.clone())?; + let wasm_path = match file.as_ref().extension().and_then(|x| x.to_str()) { + Some("c") => { + // some tests are .c, and must be compiled/linked to .wasm we can run + let wasm_build = Link::new(&[file]) + .with_cflag("-Wall") + .with_cflag("-Werror") + .with_print_output(true); + + let wasm_file = workdir.path().join("out.wasm"); + wasm_build.link(wasm_file.clone())?; + + wasm_file + } + Some("wasm") | Some("wat") => { + // others are just wasm we can run directly + file.as_ref().to_owned() + } + Some(ext) => { + panic!("unknown test file extension: .{}", ext); + } + None => { + panic!("unknown test file, has no extension"); + } + }; - let bindings = Bindings::from_file(Path::new(LUCET_WASI_ROOT).join("bindings.json"))?; + wasi_load(&workdir, wasm_path) +} - let native_build = Lucetc::new(wasm_file)?.bindings(bindings)?; +pub fn wasi_load>( + workdir: &TempDir, + wasm_file: P, +) -> Result, Error> { + let native_build = Lucetc::new(wasm_file).with_bindings(lucet_wasi::bindings()); let so_file = workdir.path().join("out.so"); @@ -66,7 +85,7 @@ pub fn run>(path: P, ctx: WasiCtx) -> Result<__wasi_exitcode_t, E .with_embed_ctx(ctx) .build()?; - match inst.run(b"_start", &[]) { + match inst.run("_start", &[]) { // normal termination implies 0 exit code Ok(_) => Ok(0), Err(lucet_runtime::Error::RuntimeTerminated( @@ -84,7 +103,7 @@ pub fn run_with_stdout>( ) -> Result<(__wasi_exitcode_t, String), Error> { let (pipe_out, pipe_in) = nix::unistd::pipe()?; - let ctx = unsafe { ctx.raw_fd(1, pipe_in) }.build()?; + let ctx = ctx.stdout(unsafe { File::from_raw_fd(pipe_in) }).build()?; let exitcode = run(path, ctx)?; @@ -96,6 +115,21 @@ pub fn run_with_stdout>( Ok((exitcode, stdout)) } +pub fn run_with_null_stdin>( + path: P, + ctx: WasiCtxBuilder, +) -> Result<__wasi_exitcode_t, Error> { + let (pipe_out, pipe_in) = nix::unistd::pipe()?; + + let ctx = ctx.stdin(unsafe { File::from_raw_fd(pipe_out) }).build()?; + + let exitcode = run(path, ctx)?; + + nix::unistd::close(pipe_in)?; + + Ok(exitcode) +} + /// Call this if you're having trouble with `__wasi_*` symbols not being exported. /// /// This is pretty hackish; we will hopefully be able to avoid this altogether once [this @@ -103,5 +137,5 @@ pub fn run_with_stdout>( #[no_mangle] #[doc(hidden)] pub extern "C" fn lucet_wasi_tests_internal_ensure_linked() { - lucet_wasi::hostcalls::ensure_linked(); + lucet_wasi::export_wasi_funcs(); } diff --git a/lucet-wasi/tests/tests.rs b/lucet-wasi/tests/tests.rs index 39ebea505..372700377 100644 --- a/lucet-wasi/tests/tests.rs +++ b/lucet-wasi/tests/tests.rs @@ -1,14 +1,24 @@ mod test_helpers; -use crate::test_helpers::{run, run_with_stdout, LUCET_WASI_ROOT}; +use crate::test_helpers::{run, run_with_null_stdin, run_with_stdout, LUCET_WASI_ROOT}; use lucet_wasi::{WasiCtx, WasiCtxBuilder}; use std::fs::File; use std::path::Path; use tempfile::TempDir; +#[test] +fn double_import() { + let ctx = WasiCtxBuilder::new(); + + let (exitcode, stdout) = run_with_stdout("duplicate_import.wat", ctx).unwrap(); + + assert_eq!(stdout, "duplicate import works!\n"); + assert_eq!(exitcode, 0); +} + #[test] fn hello() { - let ctx = WasiCtxBuilder::new().args(&["hello"]); + let ctx = WasiCtxBuilder::new().args(["hello"].into_iter()); let (exitcode, stdout) = run_with_stdout( Path::new(LUCET_WASI_ROOT).join("examples").join("hello.c"), @@ -22,7 +32,7 @@ fn hello() { #[test] fn hello_args() { - let ctx = WasiCtxBuilder::new().args(&["hello", "test suite"]); + let ctx = WasiCtxBuilder::new().args(["hello", "test suite"].into_iter()); let (exitcode, stdout) = run_with_stdout( Path::new(LUCET_WASI_ROOT).join("examples").join("hello.c"), @@ -37,7 +47,7 @@ fn hello_args() { #[test] fn hello_env() { let ctx = WasiCtxBuilder::new() - .args(&["hello", "test suite"]) + .args(["hello", "test suite"].into_iter()) .env("GREETING", "goodbye"); let (exitcode, stdout) = run_with_stdout( @@ -52,7 +62,7 @@ fn hello_env() { #[test] fn exitcode() { - let ctx = WasiCtx::new(&["exitcode"]); + let ctx = WasiCtx::new(["exitcode"].into_iter()).unwrap(); let exitcode = run("exitcode.c", ctx).unwrap(); @@ -61,7 +71,7 @@ fn exitcode() { #[test] fn clock_getres() { - let ctx = WasiCtx::new(&["clock_getres"]); + let ctx = WasiCtx::new(["clock_getres"].into_iter()).unwrap(); let exitcode = run("clock_getres.c", ctx).unwrap(); @@ -70,7 +80,7 @@ fn clock_getres() { #[test] fn getrusage() { - let ctx = WasiCtx::new(&["getrusage"]); + let ctx = WasiCtx::new(["getrusage"].into_iter()).unwrap(); let exitcode = run("getrusage.c", ctx).unwrap(); @@ -79,7 +89,7 @@ fn getrusage() { #[test] fn gettimeofday() { - let ctx = WasiCtx::new(&["gettimeofday"]); + let ctx = WasiCtx::new(["gettimeofday"].into_iter()).unwrap(); let exitcode = run("gettimeofday.c", ctx).unwrap(); @@ -88,7 +98,7 @@ fn gettimeofday() { #[test] fn getentropy() { - let ctx = WasiCtx::new(&["getentropy"]); + let ctx = WasiCtx::new(["getentropy"].into_iter()).unwrap(); let exitcode = run("getentropy.c", ctx).unwrap(); @@ -106,7 +116,9 @@ fn stdin() { write!(stdin_file, "hello from stdin!").expect("pipe write succeeds"); drop(stdin_file); - let ctx = unsafe { WasiCtxBuilder::new().args(&["stdin"]).raw_fd(0, pipe_out) }; + let ctx = WasiCtxBuilder::new() + .args(["stdin"].into_iter()) + .stdin(unsafe { File::from_raw_fd(pipe_out) }); let (exitcode, stdout) = run_with_stdout("stdin.c", ctx).unwrap(); @@ -122,7 +134,7 @@ fn preopen_populates() { let preopen_dir = File::open(preopen_host_path).unwrap(); let ctx = WasiCtxBuilder::new() - .args(&["preopen_populates"]) + .args(["preopen_populates"].into_iter()) .preopened_dir(preopen_dir, "/preopen") .build() .expect("can build WasiCtx"); @@ -142,7 +154,7 @@ fn write_file() { let preopen_dir = File::open(&preopen_host_path).unwrap(); let ctx = WasiCtxBuilder::new() - .args(&["write_file"]) + .args(["write_file"].into_iter()) .preopened_dir(preopen_dir, "/sandbox") .build() .expect("can build WasiCtx"); @@ -169,7 +181,7 @@ fn read_file() { let preopen_dir = File::open(&preopen_host_path).unwrap(); let ctx = WasiCtxBuilder::new() - .args(&["read_file"]) + .args(["read_file"].into_iter()) .preopened_dir(preopen_dir, "/sandbox"); let (exitcode, stdout) = run_with_stdout("read_file.c", ctx).unwrap(); @@ -192,7 +204,7 @@ fn read_file_twice() { let preopen_dir = File::open(&preopen_host_path).unwrap(); let ctx = WasiCtxBuilder::new() - .args(&["read_file_twice"]) + .args(["read_file_twice"].into_iter()) .preopened_dir(preopen_dir, "/sandbox"); let (exitcode, stdout) = run_with_stdout("read_file_twice.c", ctx).unwrap(); @@ -220,7 +232,7 @@ fn cant_dotdot() { let preopen_dir = File::open(&preopen_host_path).unwrap(); let ctx = WasiCtxBuilder::new() - .args(&["cant_dotdot"]) + .args(["cant_dotdot"].into_iter()) .preopened_dir(preopen_dir, "/sandbox") .build() .unwrap(); @@ -244,7 +256,7 @@ fn notdir() { let preopen_dir = File::open(&preopen_host_path).unwrap(); let ctx = WasiCtxBuilder::new() - .args(&["notdir"]) + .args(["notdir"].into_iter()) .preopened_dir(preopen_dir, "/sandbox") .build() .unwrap(); @@ -273,7 +285,7 @@ fn follow_symlink() { let preopen_dir = File::open(&preopen_host_path).unwrap(); let ctx = WasiCtxBuilder::new() - .args(&["follow_symlink"]) + .args(["follow_symlink"].into_iter()) .preopened_dir(preopen_dir, "/sandbox"); let (exitcode, stdout) = run_with_stdout("follow_symlink.c", ctx).unwrap(); @@ -298,7 +310,7 @@ fn symlink_loop() { let preopen_dir = File::open(&preopen_host_path).unwrap(); let ctx = WasiCtxBuilder::new() - .args(&["symlink_loop"]) + .args(["symlink_loop"].into_iter()) .preopened_dir(preopen_dir, "/sandbox") .build() .unwrap(); @@ -328,7 +340,7 @@ fn symlink_escape() { let preopen_dir = File::open(&preopen_host_path).unwrap(); let ctx = WasiCtxBuilder::new() - .args(&["symlink_escape"]) + .args(["symlink_escape"].into_iter()) .preopened_dir(preopen_dir, "/sandbox") .build() .unwrap(); @@ -345,7 +357,7 @@ fn pseudoquine() { let pseudoquine_c = examples_dir.join("pseudoquine.c"); let ctx = WasiCtxBuilder::new() - .args(&["pseudoquine"]) + .args(["pseudoquine"].into_iter()) .preopened_dir(File::open(examples_dir).unwrap(), "/examples"); let (exitcode, stdout) = run_with_stdout(&pseudoquine_c, ctx).unwrap(); @@ -356,3 +368,43 @@ fn pseudoquine() { assert_eq!(stdout, expected); } + +// ACF 2019-10-03: temporarily disabled until we figure out why it's behaving differently only in +// one CI environment +#[ignore] +#[test] +fn poll() { + let ctx = WasiCtxBuilder::new().args(["poll"].into_iter()); + let exitcode = run_with_null_stdin("poll.c", ctx).unwrap(); + assert_eq!(exitcode, 0); +} + +#[test] +fn stat() { + let tmpdir = TempDir::new().unwrap(); + let preopen_host_path = tmpdir.path().join("preopen"); + std::fs::create_dir(&preopen_host_path).unwrap(); + let preopen_dir = File::open(&preopen_host_path).unwrap(); + let ctx = WasiCtxBuilder::new() + .args(["stat"].into_iter()) + .preopened_dir(preopen_dir, "/sandbox") + .build() + .expect("can build WasiCtx"); + let exitcode = run("stat.c", ctx).unwrap(); + assert_eq!(exitcode, 0); +} + +#[test] +fn fs() { + let tmpdir = TempDir::new().unwrap(); + let preopen_host_path = tmpdir.path().join("preopen"); + std::fs::create_dir(&preopen_host_path).unwrap(); + let preopen_dir = File::open(&preopen_host_path).unwrap(); + let ctx = WasiCtxBuilder::new() + .args(["stat"].into_iter()) + .preopened_dir(preopen_dir, "/sandbox") + .build() + .expect("can build WasiCtx"); + let exitcode = run("fs.c", ctx).unwrap(); + assert_eq!(exitcode, 0); +} diff --git a/lucetc/Cargo.toml b/lucetc/Cargo.toml index 67bfa74af..ccd267aff 100644 --- a/lucetc/Cargo.toml +++ b/lucetc/Cargo.toml @@ -1,42 +1,69 @@ [package] name = "lucetc" -version = "0.1.0" -description = "Compile WebAssembly to native code" +version = "0.5.0" +description = "Fastly's WebAssembly to native code compiler" +homepage = "https://github.com/fastly/lucet" repository = "https://github.com/fastly/lucet" -authors = ["Pat Hickey ", "Frank Denis ", "Adam C. Foltzer ", "Tyler McMullen "] license = "Apache-2.0 WITH LLVM-exception" +categories = ["wasm"] +authors = ["Lucet team "] edition = "2018" -[lib] -crate-type=["rlib"] - [[bin]] name = "lucetc" -path = "src/main.rs" +path = "lucetc/main.rs" [dependencies] -lucet-module-data = { path = "../lucet-module-data" } +anyhow = "1" +bincode = "1.1.4" +cranelift-codegen = { path = "../cranelift/cranelift-codegen", version = "0.51.0" } +cranelift-entity = { path = "../cranelift/cranelift-entity", version = "0.51.0" } +cranelift-native = { path = "../cranelift/cranelift-native", version = "0.51.0" } +cranelift-frontend = { path = "../cranelift/cranelift-frontend", version = "0.51.0" } +cranelift-module = { path = "../cranelift/cranelift-module", version = "0.51.0" } +cranelift-faerie = { path = "../cranelift/cranelift-faerie", version = "0.51.0" } +cranelift-wasm = { path = "../cranelift/cranelift-wasm", version = "0.51.0" } +target-lexicon = "0.9" +lucet-module = { path = "../lucet-module", version = "=0.5.0" } +lucet-validate = { path = "../lucet-validate", version = "=0.5.0" } +wasmparser = "0.39.1" clap="2.32" log = "0.4" env_logger = "0.6" -parity-wasm = "0.35" -cranelift-codegen = { path = "../cranelift/cranelift-codegen" } -cranelift-native = { path = "../cranelift/cranelift-native" } -cranelift-frontend = { path = "../cranelift/cranelift-frontend" } -cranelift-module = { path = "../cranelift/cranelift-module" } -cranelift-faerie = { path = "../cranelift/cranelift-faerie" } -target-lexicon = "0.2" -faerie = "0.9.1" +faerie = "0.12.0" +goblin = "0.0.24" failure = "0.1" -serde = "1.0" -serde_json = "1.0" byteorder = "1.2" -wasmonkey = { path = "../lucet-builtins/wasmonkey" } -wabt = "0.7" +# precisely pin wasmonkey, because the shared dependency on parity-wasm is very sensitive +wasmonkey = "=0.1.9" +wabt = "0.9.2" tempfile = "3.0" -bimap = "0.1" -pwasm-validation = { path = "../pwasm-validation" } +bimap = "0.2" human-size = "0.4" +# must match wasmonkey's version specifier +parity-wasm = "0.41" +minisign = "0.5.11" +memoffset = "0.5.1" +serde = "1.0" +serde_json = "1.0" +thiserror = "1.0.4" +raw-cpuid = "6.0.0" -[dev-dependencies] -lucet-wasi-sdk = { path = "../lucet-wasi-sdk" } +[package.metadata.deb] +name = "fst-lucetc" +maintainer = "Lucet team " +depends = "$auto" +priority = "optional" +assets = [ + ["target/release/lucetc", "/opt/fst-lucetc/bin/lucetc", "755"], + ["target/release/liblucetc.rlib", "/opt/fst-lucetc/lib/", "644"], + ["LICENSE", "/opt/fst-lucetc/share/doc/lucetc/", "644"], + ["../wasi/phases/old/snapshot_0/witx/typenames.witx", + "/opt/fst-lucetc/share/wasi/snapshot_0/typenames.witx", "644"], + ["../wasi/phases/old/snapshot_0/witx/wasi_unstable.witx", + "/opt/fst-lucetc/share/wasi/snapshot_0/wasi_unstable.witx", "644"], + ["../wasi/phases/snapshot/witx/typenames.witx", + "/opt/fst-lucetc/share/wasi/snapshot_1/typenames.witx", "644"], + ["../wasi/phases/snapshot/witx/wasi_snapshot_preview1.witx", + "/opt/fst-lucetc/share/wasi/snapshot_1/wasi_snapshot_preview1.witx", "644"], +] diff --git a/lucetc/build.rs b/lucetc/build.rs new file mode 100644 index 000000000..96299ec26 --- /dev/null +++ b/lucetc/build.rs @@ -0,0 +1,29 @@ +use std::env; +use std::fs::File; +use std::path::Path; + +fn main() { + let commit_file_path = Path::new(&env::var("OUT_DIR").unwrap()).join("commit_hash"); + // in debug builds we only need the file to exist, but in release builds this will be used and + // requires mutability. + #[allow(unused_variables, unused_mut)] + let mut f = File::create(&commit_file_path).unwrap(); + + // This is about the closest not-additional-feature-flag way to detect release builds. + // In debug builds, leave the `commit_hash` file empty to allow looser version checking and + // avoid impacting development workflows too much. + #[cfg(not(debug_assertions))] + { + use std::io::Write; + use std::process::Command; + + let last_commit_hash = Command::new("git") + .args(&["log", "-n", "1", "--pretty=format:%H"]) + .output() + .ok(); + + if let Some(last_commit_hash) = last_commit_hash { + f.write_all(&last_commit_hash.stdout).unwrap(); + } + } +} diff --git a/lucetc/lucetc/main.rs b/lucetc/lucetc/main.rs new file mode 100644 index 000000000..1dee241b7 --- /dev/null +++ b/lucetc/lucetc/main.rs @@ -0,0 +1,159 @@ +mod options; + +#[macro_use] +extern crate clap; + +use crate::options::{CodegenOutput, ErrorStyle, Options}; +use anyhow::{format_err, Error}; +use log::info; +use lucet_module::bindings::Bindings; +use lucet_validate::Validator; +use lucetc::{ + signature::{self, PublicKey}, + Lucetc, LucetcOpts, +}; +use serde::Serialize; +use serde_json; +use std::path::PathBuf; +use std::process; + +#[derive(Clone, Debug, Serialize)] +pub struct SerializedLucetcError { + error: String, +} + +impl From for SerializedLucetcError { + fn from(e: Error) -> Self { + SerializedLucetcError { + error: format!("{}", e), + } + } +} + +#[derive(Debug, thiserror::Error)] +enum BindingError { + #[error("adding bindings from {1}")] + ExtendError(#[source] lucet_module::error::Error, String), + #[error("bindings file {1}")] + FileError(#[source] lucet_module::error::Error, String), +} + +fn main() { + env_logger::init(); + + let opts = Options::get().unwrap(); + + if let Err(err) = run(&opts) { + match opts.error_style { + ErrorStyle::Human => { + eprintln!("Error: {}\n", err); + } + ErrorStyle::Json => { + let errs: Vec = vec![err.into()]; + let json = serde_json::to_string(&errs).unwrap(); + eprintln!("{}", json); + } + } + process::exit(1); + } +} + +pub fn run(opts: &Options) -> Result<(), Error> { + info!("lucetc {:?}", opts); + + if opts.keygen { + keygen(opts)?; + return Ok(()); + } + + let input = &match opts.input.len() { + 0 => Err(format_err!("must provide at least one input")), + 1 => Ok(opts.input[0].clone()), + _ => Err(format_err!("provided too many inputs: {:?}", opts.input)), + }?; + + let mut bindings = Bindings::empty(); + for file in opts.binding_files.iter() { + let file_bindings = Bindings::from_file(file).map_err(|source| { + let file = format!("{:?}", file); + BindingError::FileError(source, file) + })?; + + bindings.extend(&file_bindings).map_err(|source| { + let file = format!("{:?}", file); + BindingError::ExtendError(source, file) + })?; + } + + let mut c = Lucetc::new(PathBuf::from(input)) + .with_bindings(bindings) + .with_opt_level(opts.opt_level) + .with_cpu_features(opts.cpu_features.clone()) + .with_target(opts.target.clone()); + + match opts.witx_specs.len() { + 0 => {} + 1 => { + let validator = Validator::load(&opts.witx_specs[0])?.with_wasi_exe(opts.wasi_exe); + c.validator(validator); + } + _ => Err(format_err!("multiple witx specs not yet supported"))?, + } + + if let Some(ref builtins) = opts.builtins_path { + c.builtins(builtins); + } + + if let Some(min_reserved_size) = opts.min_reserved_size { + c.min_reserved_size(min_reserved_size); + } + + if let Some(max_reserved_size) = opts.max_reserved_size { + c.max_reserved_size(max_reserved_size); + } + + // this comes after min and max, so it overrides them if present + if let Some(reserved_size) = opts.reserved_size { + c.reserved_size(reserved_size); + } + + if let Some(guard_size) = opts.guard_size { + c.guard_size(guard_size); + } + + if let Some(pk_path) = &opts.pk_path { + c.pk(PublicKey::from_file(pk_path)?); + } + + if let Some(sk_path) = &opts.sk_path { + c.sk(signature::sk_from_file(sk_path)?); + } + + if opts.verify { + c.verify(); + } + + if opts.sign { + c.sign(); + } + + if opts.count_instructions { + c.count_instructions(true); + } + + match opts.codegen { + CodegenOutput::Obj => c.object_file(&opts.output)?, + CodegenOutput::SharedObj => c.shared_object_file(&opts.output)?, + CodegenOutput::Clif => c.clif_ir(&opts.output)?, + } + Ok(()) +} + +fn keygen(opts: &Options) -> Result<(), Error> { + let (pk_path, sk_path) = match (&opts.pk_path, &opts.sk_path) { + (Some(pk_path), Some(sk_path)) => (pk_path, sk_path), + _ => Err(format_err!("Keypair generation requires --signature-pk and --signature-sk to specify where the keys should be stored to"))? + }; + signature::keygen(pk_path, sk_path)?; + Ok(()) +} diff --git a/lucetc/lucetc/options.rs b/lucetc/lucetc/options.rs new file mode 100644 index 000000000..eb1bc2c4e --- /dev/null +++ b/lucetc/lucetc/options.rs @@ -0,0 +1,467 @@ +use anyhow::Error; +use clap::{Arg, ArgMatches, Values}; +use lucetc::{CpuFeatures, HeapSettings, OptLevel, SpecificFeature, TargetCpu}; +use std::path::PathBuf; +use std::str::FromStr; +use target_lexicon::{Architecture, Triple}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CodegenOutput { + Clif, + Obj, + SharedObj, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ErrorStyle { + Human, + Json, +} + +impl Default for ErrorStyle { + fn default() -> Self { + ErrorStyle::Human + } +} + +fn parse_humansized(desc: &str) -> Result { + use human_size::{Byte, ParsingError, Size, SpecificSize}; + match desc.parse::() { + Ok(s) => { + let bytes: SpecificSize = s.into(); + Ok(bytes.value() as u64) + } + Err(ParsingError::MissingMultiple) => Ok(desc.parse::()?), + Err(e) => Err(e)?, + } +} + +fn humansized(bytes: u64) -> String { + use human_size::{Byte, Mebibyte, SpecificSize}; + let bytes = SpecificSize::new(bytes as f64, Byte).expect("bytes"); + let mb: SpecificSize = bytes.into(); + mb.to_string() +} + +fn cpu_features_from_args(cpu: Option<&str>, features: Values) -> Result { + use SpecificFeature::*; + use TargetCpu::*; + if cpu.is_none() && features.len() == 0 { + Ok(CpuFeatures::detect_cpuid()) + } else { + let cpu: TargetCpu = match cpu { + None => Baseline, + Some(s) => match s.to_lowercase().as_str() { + "native" => Native, + "baseline" => Baseline, + "nehalem" => Nehalem, + "sandybridge" => Sandybridge, + "haswell" => Haswell, + "broadwell" => Broadwell, + "skylake" => Skylake, + "cannonlake" => Cannonlake, + "icelake" => Icelake, + "znver1" => Znver1, + _ => unreachable!("invalid CPU string despite passing validation: {}", s), + }, + }; + let specific_features = features + .map(|fstr| { + let b = match fstr.chars().nth(0) { + Some('+') => true, + Some('-') => false, + _ => unreachable!( + "invalid feature string despite passing validation: {}", + fstr + ), + }; + // the only valid starting characters are single-byte '+' and '-', so this indexing + // ought not to fail + let f = match &fstr[1..] { + "sse3" => SSE3, + "ssse3" => SSSE3, + "sse41" => SSE41, + "sse42" => SSE42, + "popcnt" => Popcnt, + "avx" => AVX, + "bmi1" => BMI1, + "bmi2" => BMI2, + "lzcnt" => Lzcnt, + _ => unreachable!( + "invalid feature string despite passing validation: {}", + fstr + ), + }; + (f, b) + }) + .collect(); + Ok(CpuFeatures::new(cpu, specific_features)) + } +} + +#[derive(Debug)] +pub struct Options { + pub output: PathBuf, + pub input: Vec, + pub codegen: CodegenOutput, + pub binding_files: Vec, + pub witx_specs: Vec, + pub wasi_exe: bool, + pub builtins_path: Option, + pub min_reserved_size: Option, + pub max_reserved_size: Option, + pub reserved_size: Option, + pub guard_size: Option, + pub opt_level: OptLevel, + pub cpu_features: CpuFeatures, + pub keygen: bool, + pub sign: bool, + pub verify: bool, + pub pk_path: Option, + pub sk_path: Option, + pub count_instructions: bool, + pub error_style: ErrorStyle, + pub target: Triple, +} + +impl Options { + pub fn from_args(m: &ArgMatches<'_>) -> Result { + let input: Vec = m + .values_of("input") + .unwrap_or_default() + .map(PathBuf::from) + .collect(); + + let output = PathBuf::from(m.value_of("output").unwrap_or("a.out")); + + let binding_files: Vec = m + .values_of("bindings") + .unwrap_or_default() + .map(PathBuf::from) + .collect(); + + let witx_specs: Vec = m + .values_of("witx_specs") + .unwrap_or_default() + .map(PathBuf::from) + .collect(); + let wasi_exe = m.is_present("wasi_exe"); + + let codegen = match m.value_of("emit") { + None => CodegenOutput::SharedObj, + Some("clif") => CodegenOutput::Clif, + Some("obj") => CodegenOutput::Obj, + Some("so") => CodegenOutput::SharedObj, + Some(_) => panic!("unknown value for emit"), + }; + + let builtins_path = m.value_of("builtins").map(PathBuf::from); + + let min_reserved_size = if let Some(min_reserved_str) = m.value_of("min_reserved_size") { + Some(parse_humansized(min_reserved_str)?) + } else { + None + }; + + let max_reserved_size = if let Some(max_reserved_str) = m.value_of("max_reserved_size") { + Some(parse_humansized(max_reserved_str)?) + } else { + None + }; + + let reserved_size = if let Some(reserved_str) = m.value_of("reserved_size") { + Some(parse_humansized(reserved_str)?) + } else { + None + }; + + let guard_size = if let Some(guard_str) = m.value_of("guard_size") { + Some(parse_humansized(guard_str)?) + } else { + None + }; + + let opt_level = match m.value_of("opt_level") { + None => OptLevel::SpeedAndSize, + Some("0") | Some("none") => OptLevel::None, + Some("1") | Some("speed") => OptLevel::Speed, + Some("2") | Some("speed_and_size") => OptLevel::SpeedAndSize, + Some(_) => panic!("unknown value for opt-level"), + }; + + let target = match m.value_of("target") { + None => Triple::host(), + Some(t) => match Triple::from_str(&t) { + Ok(triple) => triple, + Err(_) => panic!("specified target is invalid"), + }, + }; + + let cpu_features = cpu_features_from_args( + m.value_of("target-cpu"), + m.values_of("target-feature").unwrap_or_default(), + )?; + + if target.architecture != Architecture::X86_64 { + panic!("architectures other than x86-64 are unsupported"); + } + + let keygen = m.is_present("keygen"); + let sign = m.is_present("sign"); + let verify = m.is_present("verify"); + let sk_path = m.value_of("sk_path").map(PathBuf::from); + let pk_path = m.value_of("pk_path").map(PathBuf::from); + let count_instructions = m.is_present("count_instructions"); + + let error_style = match m.value_of("error_style") { + None => ErrorStyle::default(), + Some("human") => ErrorStyle::Human, + Some("json") => ErrorStyle::Json, + Some(_) => panic!("unknown value for error-style"), + }; + + Ok(Options { + output, + input, + codegen, + binding_files, + witx_specs, + wasi_exe, + builtins_path, + min_reserved_size, + max_reserved_size, + reserved_size, + guard_size, + opt_level, + cpu_features, + keygen, + sign, + verify, + sk_path, + pk_path, + count_instructions, + error_style, + target, + }) + } + pub fn get() -> Result { + let _ = include_str!("../Cargo.toml"); + let m = app_from_crate!() + .arg( + Arg::with_name("precious") + .long("--precious") + .takes_value(true) + .help("directory to keep intermediate build artifacts in"), + ) + .arg( + Arg::with_name("emit") + .long("emit") + .takes_value(true) + .possible_values(&["obj", "so", "clif"]) + .help("type of code to generate (default: so)"), + ) + .arg( + Arg::with_name("output") + .short("o") + .long("output") + .takes_value(true) + .multiple(false) + .help("output destination, defaults to a.out if unspecified"), + ) + .arg( + Arg::with_name("target") + .long("target") + .takes_value(true) + .multiple(false) + .help(format!("target to compile for, defaults to {} if unspecified", Triple::host()).as_str()), + ) + .arg( + Arg::with_name("target-cpu") + .long("--target-cpu") + .takes_value(true) + .multiple(false) + .number_of_values(1) + .possible_values(&[ + "native", + "baseline", + "nehalem", + "sandybridge", + "haswell", + "broadwell", + "skylake", + "cannonlake", + "icelake", + "znver1", + ]) + .help("Generate code for a particular type of CPU.") + .long_help( +"Generate code for a particular type of CPU. + +If neither `--target-cpu` nor `--target-feature` is provided, `lucetc` +will automatically detect and use the features available on the host CPU. +This is equivalent to choosing `--target-cpu=native`. + +" + ) + ) + .arg( + Arg::with_name("target-feature") + .long("--target-feature") + .takes_value(true) + .multiple(true) + .use_delimiter(true) + .possible_values(&[ + "+sse3", "-sse3", + "+ssse3", "-ssse3", + "+sse41", "-sse41", + "+sse42", "-sse42", + "+popcnt", "-popcnt", + "+avx", "-avx", + "+bmi1", "-bmi1", + "+bmi2", "-bmi2", + "+lzcnt", "-lzcnt", + ]) + .help("Enable (+) or disable (-) specific CPU features.") + .long_help( +"Enable (+) or disable (-) specific CPU features. + +If neither `--target-cpu` nor `--target-feature` is provided, `lucetc` +will automatically detect and use the features available on the host CPU. + +This option is additive with, but takes precedence over `--target-cpu`. +For example, `--target-cpu=haswell --target-feature=-avx` will disable +AVX, but leave all other default Haswell features enabled. + +Multiple `--target-feature` groups may be specified, with precedence +increasing from left to right. For example, these arguments will enable +SSE3 but not AVX: + + --target-feature=+sse3,+avx --target-feature=-avx + +" + ) + ) + .arg( + Arg::with_name("bindings") + .long("--bindings") + .takes_value(true) + .multiple(true) + .number_of_values(1) + .help("path to bindings json file"), + ) + .arg( + Arg::with_name("witx_specs") + .long("--witx") + .takes_value(true) + .multiple(true) + .number_of_values(1) + .help("path to witx spec to validate against"), + ) + .arg( + Arg::with_name("wasi_exe") + .long("--wasi_exe") + .takes_value(false) + .multiple(false) + .help("validate as a wasi executable"), + ) + .arg( + Arg::with_name("min_reserved_size") + .long("--min-reserved-size") + .takes_value(true) + .multiple(false) + .help(&format!( + "minimum size of usable linear memory region. must be multiple of 4k. default: {}", + humansized(HeapSettings::default().min_reserved_size) + )), + ) + .arg( + Arg::with_name("max_reserved_size") + .long("--max-reserved-size") + .takes_value(true) + .multiple(false) + .help("maximum size of usable linear memory region. must be multiple of 4k. default: 4 GiB"), + ) + .arg( + Arg::with_name("reserved_size") + .long("--reserved-size") + .takes_value(true) + .multiple(false) + .help("exact size of usable linear memory region, overriding --{min,max}-reserved-size. must be multiple of 4k"), + ) + .arg( + Arg::with_name("guard_size") + .long("--guard-size") + .takes_value(true) + .multiple(false) + .help(&format!( + "size of linear memory guard. must be multiple of 4k. default: {}", + humansized(HeapSettings::default().guard_size) + )), + ) + .arg( + Arg::with_name("builtins") + .long("--builtins") + .takes_value(true) + .help("builtins file"), + ) + .arg( + Arg::with_name("input") + .multiple(false) + .required(false) + .help("input file"), + ) + .arg( + Arg::with_name("opt_level") + .long("--opt-level") + .takes_value(true) + .possible_values(&["0", "1", "2", "none", "speed", "speed_and_size"]) + .help("optimization level (default: 'speed_and_size'). 0 is alias to 'none', 1 to 'speed', 2 to 'speed_and_size'"), + ) + .arg( + Arg::with_name("keygen") + .long("--signature-keygen") + .takes_value(false) + .help("Create a new key pair") + ) + .arg( + Arg::with_name("verify") + .long("--signature-verify") + .takes_value(false) + .help("Verify the signature of the source file") + ) + .arg( + Arg::with_name("sign") + .long("--signature-create") + .takes_value(false) + .help("Sign the object file") + ) + .arg( + Arg::with_name("pk_path") + .long("--signature-pk") + .takes_value(true) + .help("Path to the public key to verify the source code signature") + ) + .arg( + Arg::with_name("sk_path") + .long("--signature-sk") + .takes_value(true) + .help("Path to the secret key to sign the object file. The file can be prefixed with \"raw:\" in order to store a raw, unencrypted secret key") + ) + .arg( + Arg::with_name("count_instructions") + .long("--count-instructions") + .takes_value(false) + .help("Instrument the produced binary to count the number of wasm operations the translated program executes") + ) + .arg( + Arg::with_name("error_style") + .long("error-style") + .takes_value(true) + .possible_values(&["human", "json"]) + .help("Style of error reporting (default: human)"), + ) + .get_matches(); + + Self::from_args(&m) + } +} diff --git a/lucetc/src/bindings.rs b/lucetc/src/bindings.rs deleted file mode 100644 index 54774d6e3..000000000 --- a/lucetc/src/bindings.rs +++ /dev/null @@ -1,179 +0,0 @@ -use failure::{format_err, Error}; -use serde_json::{self, Map, Value}; -use std::collections::{hash_map::Entry, HashMap}; -use std::fs; -use std::path::Path; - -fn parse_modules( - m: &Map, -) -> Result>, Error> { - let mut res = HashMap::new(); - for (modulename, values) in m { - match values.as_object() { - Some(methods) => { - let methodmap = parse_methods(methods)?; - res.insert(modulename.to_owned(), methodmap); - } - None => Err(format_err!(""))?, - } - } - Ok(res) -} - -fn parse_methods(m: &Map) -> Result, Error> { - let mut res = HashMap::new(); - for (method, i) in m { - match i.as_str() { - Some(importbinding) => { - res.insert(method.to_owned(), importbinding.to_owned()); - } - None => Err(format_err!(""))?, - } - } - Ok(res) -} - -#[derive(Debug, Clone)] -pub struct Bindings { - bindings: HashMap>, -} - -impl Bindings { - pub fn new(bindings: HashMap>) -> Bindings { - Self { bindings: bindings } - } - - pub fn env(env: HashMap) -> Bindings { - let mut bindings = HashMap::new(); - bindings.insert("env".to_owned(), env); - Self::new(bindings) - } - - pub fn empty() -> Bindings { - Self::new(HashMap::new()) - } - - pub fn from_json(v: &Value) -> Result { - let bindings = match v.as_object() { - Some(modules) => parse_modules(modules)?, - None => Err(format_err!("top level json expected to be object"))?, - }; - Ok(Self::new(bindings)) - } - - pub fn from_str(s: &str) -> Result { - let top: Value = serde_json::from_str(s)?; - Ok(Self::from_json(&top)?) - } - - pub fn from_file>(path: P) -> Result { - let contents = fs::read_to_string(path.as_ref())?; - Ok(Self::from_str(&contents)?) - } - - pub fn extend(&mut self, other: Bindings) -> Result<(), Error> { - //self.bindings.extend(other.bindings); - for (modname, othermodbindings) in other.bindings { - match self.bindings.entry(modname) { - Entry::Occupied(mut e) => { - let existing = e.get_mut(); - for (bindname, binding) in othermodbindings { - match existing.entry(bindname) { - Entry::Vacant(e) => { - e.insert(binding); - } - Entry::Occupied(e) => { - if &binding != e.get() { - Err(format_err!( - "cannot re-bind {} from {} to {}", - e.key(), - binding, - e.get() - ))?; - } - } - } - } - } - Entry::Vacant(e) => { - e.insert(othermodbindings); - } - } - } - Ok(()) - } - - pub fn translate(&self, module: &str, symbol: &str) -> Result { - match self.bindings.get(module) { - Some(m) => match m.get(symbol) { - Some(s) => Ok(s.clone()), - None => Err(format_err!("Unknown symbol `{}::{}`", module, symbol)), - }, - None => Err(format_err!( - "Unknown module for symbol `{}::{}`", - module, - symbol - )), - } - } -} - -#[cfg(test)] -mod tests { - fn test_file(f: &str) -> PathBuf { - PathBuf::from(format!("tests/bindings/{}", f)) - } - - use super::Bindings; - use std::collections::HashMap; - use std::path::PathBuf; - - #[test] - fn explicit() { - let mut explicit_map = HashMap::new(); - explicit_map.insert(String::from("hello"), String::from("goodbye")); - let map = Bindings::env(explicit_map); - - let result = map.translate("env", "hello").unwrap(); - assert!(result == "goodbye"); - - let result = map.translate("env", "nonexistent"); - if let Ok(_) = result { - assert!( - false, - "explicit import map returned value for non-existent symbol" - ) - } - } - - #[test] - fn explicit_from_nonexistent_file() { - let fail_map = Bindings::from_file(&test_file("nonexistent_bindings.json")); - assert!( - fail_map.is_err(), - "ImportMap::explicit_from_file did not fail on a non-existent file" - ); - } - - #[test] - fn explicit_from_garbage_file() { - let fail_map = Bindings::from_file(&test_file("garbage.json")); - assert!( - fail_map.is_err(), - "ImportMap::explicit_from_file did not fail on a garbage file" - ); - } - - #[test] - fn explicit_from_file() { - let map = Bindings::from_file(&test_file("bindings_test.json")) - .expect("load valid bindings from file"); - let result = map.translate("env", "hello").expect("hello has a binding"); - assert!(result == "json is cool"); - - assert!( - map.translate("env", "nonexistent").is_err(), - "bindings from file returned value for non-existent symbol" - ); - } -} diff --git a/lucetc/src/compiler.rs b/lucetc/src/compiler.rs new file mode 100644 index 000000000..43b374df7 --- /dev/null +++ b/lucetc/src/compiler.rs @@ -0,0 +1,294 @@ +mod cpu_features; + +pub use self::cpu_features::{CpuFeatures, SpecificFeature, TargetCpu}; +use crate::decls::ModuleDecls; +use crate::error::Error; +use crate::function::FuncInfo; +use crate::heap::HeapSettings; +use crate::module::ModuleInfo; +use crate::output::{CraneliftFuncs, ObjectFile}; +use crate::runtime::Runtime; +use crate::stack_probe; +use crate::table::write_table_data; +use cranelift_codegen::{ + ir, + isa::TargetIsa, + settings::{self, Configurable}, + Context as ClifContext, +}; +use cranelift_faerie::{FaerieBackend, FaerieBuilder, FaerieTrapCollection}; +use cranelift_module::{Backend as ClifBackend, Module as ClifModule}; +use cranelift_wasm::{translate_module, FuncTranslator, ModuleTranslationState, WasmError}; +use lucet_module::bindings::Bindings; +use lucet_module::{FunctionSpec, ModuleData, ModuleFeatures, MODULE_DATA_SYM}; +use lucet_validate::Validator; +use target_lexicon::Triple; + +#[derive(Debug, Clone, Copy)] +pub enum OptLevel { + None, + Speed, + SpeedAndSize, +} + +impl Default for OptLevel { + fn default() -> OptLevel { + OptLevel::SpeedAndSize + } +} + +impl OptLevel { + pub fn to_flag(&self) -> &str { + match self { + OptLevel::None => "none", + OptLevel::Speed => "speed", + OptLevel::SpeedAndSize => "speed_and_size", + } + } +} + +pub struct Compiler<'a> { + decls: ModuleDecls<'a>, + clif_module: ClifModule, + target: Triple, + opt_level: OptLevel, + cpu_features: CpuFeatures, + count_instructions: bool, + module_translation_state: ModuleTranslationState, +} + +impl<'a> Compiler<'a> { + pub fn new( + wasm_binary: &'a [u8], + target: Triple, + opt_level: OptLevel, + cpu_features: CpuFeatures, + bindings: &'a Bindings, + heap_settings: HeapSettings, + count_instructions: bool, + validator: &Option, + ) -> Result { + let isa = Self::target_isa(target.clone(), opt_level, &cpu_features)?; + + let frontend_config = isa.frontend_config(); + let mut module_info = ModuleInfo::new(frontend_config.clone()); + + if let Some(v) = validator { + v.validate(wasm_binary).map_err(Error::LucetValidation)?; + } else { + // As of cranelift-wasm 0.43 which uses wasmparser 0.39.1, the parser used inside + // cranelift-wasm does not validate. We need to run the validating parser on the binary + // first. The InvalidWebAssembly error below will never trigger. + wasmparser::validate(wasm_binary, None).map_err(Error::WasmValidation)?; + } + + let module_translation_state = + translate_module(wasm_binary, &mut module_info).map_err(|e| match e { + WasmError::User(u) => Error::Input(u.to_string()), + WasmError::InvalidWebAssembly { .. } => { + // Since wasmparser was already used to validate, + // reaching this case means there's a significant + // bug in either wasmparser or cranelift-wasm. + unreachable!(); + } + WasmError::Unsupported(s) => Error::Unsupported(s.to_owned()), + WasmError::ImplLimitExceeded { .. } => Error::ClifWasmError(e), + })?; + + let libcalls = Box::new(move |libcall| match libcall { + ir::LibCall::Probestack => stack_probe::STACK_PROBE_SYM.to_owned(), + _ => (cranelift_module::default_libcall_names())(libcall), + }); + + let mut clif_module: ClifModule = ClifModule::new(FaerieBuilder::new( + isa, + "lucet_guest".to_owned(), + FaerieTrapCollection::Enabled, + libcalls, + )?); + + let runtime = Runtime::lucet(frontend_config); + let decls = ModuleDecls::new( + module_info, + &mut clif_module, + bindings, + runtime, + heap_settings, + )?; + + Ok(Self { + decls, + clif_module, + opt_level, + cpu_features, + count_instructions, + module_translation_state, + target, + }) + } + + pub fn module_features(&self) -> ModuleFeatures { + // This will grow in the future to encompass other options describing the compiled module. + (&self.cpu_features).into() + } + + pub fn module_data(&self) -> Result, Error> { + self.decls.get_module_data(self.module_features()) + } + + pub fn object_file(mut self) -> Result { + let mut func_translator = FuncTranslator::new(); + + for (ref func, (code, code_offset)) in self.decls.function_bodies() { + let mut func_info = FuncInfo::new(&self.decls, self.count_instructions); + let mut clif_context = ClifContext::new(); + clif_context.func.name = func.name.as_externalname(); + clif_context.func.signature = func.signature.clone(); + + func_translator + .translate( + &self.module_translation_state, + code, + *code_offset, + &mut clif_context.func, + &mut func_info, + ) + .map_err(|source| Error::FunctionTranslation { + symbol: func.name.symbol().to_string(), + source, + })?; + self.clif_module + .define_function(func.name.as_funcid().unwrap(), &mut clif_context) + .map_err(|source| Error::FunctionDefinition { + symbol: func.name.symbol().to_string(), + source, + })?; + } + + stack_probe::declare_metadata(&mut self.decls, &mut self.clif_module).unwrap(); + + let module_data_bytes = self.module_data()?.serialize()?; + + let module_data_len = module_data_bytes.len(); + + write_module_data(&mut self.clif_module, module_data_bytes)?; + write_startfunc_data(&mut self.clif_module, &self.decls)?; + let table_names = write_table_data(&mut self.clif_module, &self.decls)?; + + let function_manifest: Vec<(String, FunctionSpec)> = self + .clif_module + .declared_functions() + .map(|f| { + ( + f.decl.name.to_owned(), // this copy is only necessary because `clif_module` is moved in `finish, below` + FunctionSpec::new( + 0, + f.compiled.as_ref().map(|c| c.code_length()).unwrap_or(0), + 0, + 0, + ), + ) + }) + .collect(); + + let obj = ObjectFile::new( + self.clif_module.finish(), + module_data_len, + function_manifest, + table_names, + )?; + + Ok(obj) + } + + pub fn cranelift_funcs(self) -> Result { + use std::collections::HashMap; + + let mut funcs = HashMap::new(); + let mut func_translator = FuncTranslator::new(); + + for (ref func, (code, code_offset)) in self.decls.function_bodies() { + let mut func_info = FuncInfo::new(&self.decls, self.count_instructions); + let mut clif_context = ClifContext::new(); + clif_context.func.name = func.name.as_externalname(); + clif_context.func.signature = func.signature.clone(); + + func_translator + .translate( + &self.module_translation_state, + code, + *code_offset, + &mut clif_context.func, + &mut func_info, + ) + .map_err(|source| Error::FunctionTranslation { + symbol: func.name.symbol().to_string(), + source, + })?; + + funcs.insert(func.name.clone(), clif_context.func); + } + Ok(CraneliftFuncs::new( + funcs, + Self::target_isa(self.target, self.opt_level, &self.cpu_features)?, + )) + } + + fn target_isa( + target: Triple, + opt_level: OptLevel, + cpu_features: &CpuFeatures, + ) -> Result, Error> { + let mut flags_builder = settings::builder(); + let isa_builder = cpu_features.isa_builder(target)?; + flags_builder.enable("enable_verifier").unwrap(); + flags_builder.enable("is_pic").unwrap(); + flags_builder.set("opt_level", opt_level.to_flag()).unwrap(); + Ok(isa_builder.finish(settings::Flags::new(flags_builder))) + } +} + +fn write_module_data( + clif_module: &mut ClifModule, + module_data_bytes: Vec, +) -> Result<(), Error> { + use cranelift_module::{DataContext, Linkage}; + + let mut module_data_ctx = DataContext::new(); + module_data_ctx.define(module_data_bytes.into_boxed_slice()); + + let module_data_decl = clif_module + .declare_data(MODULE_DATA_SYM, Linkage::Local, true, None) + .map_err(Error::ClifModuleError)?; + clif_module + .define_data(module_data_decl, &module_data_ctx) + .map_err(Error::ClifModuleError)?; + + Ok(()) +} + +fn write_startfunc_data( + clif_module: &mut ClifModule, + decls: &ModuleDecls<'_>, +) -> Result<(), Error> { + use cranelift_module::{DataContext, Linkage}; + + if let Some(func_ix) = decls.get_start_func() { + let name = clif_module + .declare_data("guest_start", Linkage::Export, false, None) + .map_err(Error::MetadataSerializer)?; + let mut ctx = DataContext::new(); + ctx.define_zeroinit(8); + + let start_func = decls + .get_func(func_ix) + .expect("start func is valid func id"); + let fid = start_func.name.as_funcid().expect("start func is a func"); + let fref = clif_module.declare_func_in_data(fid, &mut ctx); + ctx.write_function_addr(0, fref); + clif_module + .define_data(name, &ctx) + .map_err(Error::MetadataSerializer)?; + } + Ok(()) +} diff --git a/lucetc/src/compiler/cpu_features.rs b/lucetc/src/compiler/cpu_features.rs new file mode 100644 index 000000000..d08ebb3bc --- /dev/null +++ b/lucetc/src/compiler/cpu_features.rs @@ -0,0 +1,223 @@ +use crate::error::Error; +use cranelift_codegen::{isa, settings::Configurable}; +use lucet_module::ModuleFeatures; +use std::collections::{HashMap, HashSet}; +use target_lexicon::Triple; + +use raw_cpuid::CpuId; + +/// x86 CPU families used as shorthand for different CPU feature configurations. +/// +/// Matches the definitions from `cranelift-codegen`'s x86 settings definition. +#[derive(Debug, Clone, Copy)] +pub enum TargetCpu { + Native, + Baseline, + Nehalem, + Sandybridge, + Haswell, + Broadwell, + Skylake, + Cannonlake, + Icelake, + Znver1, +} + +impl TargetCpu { + fn features(&self) -> Vec { + use SpecificFeature::*; + use TargetCpu::*; + match self { + Native | Baseline => vec![], + Nehalem => vec![SSE3, SSSE3, SSE41, SSE42, Popcnt], + // Note: this is not part of the Cranelift profile for Haswell, and there is no Sandy + // Bridge profile. Instead, Cranelift only uses CPUID detection to enable AVX. If we + // want to bypass CPUID when compiling, we need to set AVX manually, and Sandy Bridge is + // the first family of Intel CPUs with AVX. + Sandybridge => [Nehalem.features().as_slice(), &[AVX]].concat(), + Haswell => [Sandybridge.features().as_slice(), &[BMI1, BMI2, Lzcnt]].concat(), + Broadwell => Haswell.features(), + Skylake => Broadwell.features(), + Cannonlake => Skylake.features(), + Icelake => Cannonlake.features(), + Znver1 => vec![SSE3, SSSE3, SSE41, SSE42, Popcnt, AVX, BMI1, BMI2, Lzcnt], + } + } +} + +/// Individual CPU features that may be used during codegen. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum SpecificFeature { + SSE3, + SSSE3, + SSE41, + SSE42, + Popcnt, + AVX, + BMI1, + BMI2, + Lzcnt, +} + +/// An x86-specific configuration of CPU features that affect code generation. +#[derive(Debug, Clone)] +pub struct CpuFeatures { + /// Base CPU profile to use + cpu: TargetCpu, + /// Specific CPU features to add or remove from the profile + specific_features: HashMap, +} + +fn detect_features(features: &mut ModuleFeatures) { + let cpuid = CpuId::new(); + + if let Some(info) = cpuid.get_feature_info() { + features.sse3 = info.has_sse3(); + features.ssse3 = info.has_ssse3(); + features.sse41 = info.has_sse41(); + features.sse42 = info.has_sse42(); + features.avx = info.has_avx(); + features.popcnt = info.has_popcnt(); + } + + if let Some(info) = cpuid.get_extended_feature_info() { + features.bmi1 = info.has_bmi1(); + features.bmi2 = info.has_bmi2(); + } + + if let Some(info) = cpuid.get_extended_function_info() { + features.lzcnt = info.has_lzcnt(); + } +} + +impl From<&CpuFeatures> for ModuleFeatures { + fn from(cpu_features: &CpuFeatures) -> ModuleFeatures { + let mut module_features = ModuleFeatures::none(); + + let mut feature_set: HashSet = HashSet::new(); + + if let TargetCpu::Native = cpu_features.cpu { + // If the target is `Native`, start with the current set of cpu features.. + detect_features(&mut module_features); + } else { + // otherwise, start with the target cpu's default feature set + feature_set = cpu_features.cpu.features().into_iter().collect(); + } + + for (feature, enabled) in cpu_features.specific_features.iter() { + if *enabled { + feature_set.insert(*feature); + } else { + feature_set.remove(feature); + } + } + + for feature in feature_set { + use SpecificFeature::*; + match feature { + SSE3 => { + module_features.sse3 = true; + } + SSSE3 => { + module_features.ssse3 = true; + } + SSE41 => { + module_features.sse41 = true; + } + SSE42 => { + module_features.sse42 = true; + } + AVX => { + module_features.avx = true; + } + BMI1 => { + module_features.bmi1 = true; + } + BMI2 => { + module_features.bmi2 = true; + } + Popcnt => { + module_features.popcnt = true; + } + Lzcnt => { + module_features.lzcnt = true; + } + } + } + module_features + } +} + +impl Default for CpuFeatures { + fn default() -> Self { + Self::detect_cpuid() + } +} + +impl CpuFeatures { + pub fn new(cpu: TargetCpu, specific_features: HashMap) -> Self { + Self { + cpu, + specific_features, + } + } + + /// Return a `CpuFeatures` that uses the CPUID instruction to determine which features to enable. + pub fn detect_cpuid() -> Self { + CpuFeatures { + cpu: TargetCpu::Native, + specific_features: HashMap::new(), + } + } + + /// Return a `CpuFeatures` with no optional features enabled. + pub fn baseline() -> Self { + CpuFeatures { + cpu: TargetCpu::Baseline, + specific_features: HashMap::new(), + } + } + + pub fn set(&mut self, sf: SpecificFeature, enabled: bool) { + self.specific_features.insert(sf, enabled); + } + + /// Return a `cranelift_codegen::isa::Builder` configured with these CPU features. + + pub fn isa_builder(&self, target: Triple) -> Result { + use SpecificFeature::*; + use TargetCpu::*; + + let mut isa_builder = if let Native = self.cpu { + cranelift_native::builder().map_err(|_| { + Error::Unsupported("host machine is not a supported target".to_string()) + }) + } else { + isa::lookup(target).map_err(Error::UnsupportedIsa) + }?; + + let mut specific_features = self.specific_features.clone(); + + // add any features from the CPU profile if they are not already individually specified + for cpu_feature in self.cpu.features() { + specific_features.entry(cpu_feature).or_insert(true); + } + + for (feature, enabled) in specific_features.into_iter() { + let enabled = if enabled { "true" } else { "false" }; + match feature { + SSE3 => isa_builder.set("has_sse3", enabled).unwrap(), + SSSE3 => isa_builder.set("has_ssse3", enabled).unwrap(), + SSE41 => isa_builder.set("has_sse41", enabled).unwrap(), + SSE42 => isa_builder.set("has_sse42", enabled).unwrap(), + Popcnt => isa_builder.set("has_popcnt", enabled).unwrap(), + AVX => isa_builder.set("has_avx", enabled).unwrap(), + BMI1 => isa_builder.set("has_bmi1", enabled).unwrap(), + BMI2 => isa_builder.set("has_bmi2", enabled).unwrap(), + Lzcnt => isa_builder.set("has_lzcnt", enabled).unwrap(), + } + } + + Ok(isa_builder) + } +} diff --git a/lucetc/src/compiler/data.rs b/lucetc/src/compiler/data.rs deleted file mode 100644 index 4d84df8b7..000000000 --- a/lucetc/src/compiler/data.rs +++ /dev/null @@ -1,125 +0,0 @@ -use crate::compiler::Compiler; -use byteorder::{LittleEndian, WriteBytesExt}; -use cranelift_module::{DataContext, Linkage}; -use failure::Error; - -/// Formats and stores WASM data segment initializers[0] from the program to -/// symbols in the data section of obj. -/// -/// - The segment initialization data is stored to the "wasm_data_segment" -/// symbol -/// - The total number of bytes stored to "wasm_data_segment" symbol is -/// stored in the "wasm_data_segment_length" symbol -/// -/// The program that consumes the resulting ELF object is responsible for -/// using it to initialize WASM linear memory regions. -/// -/// [0] https://webassembly.github.io/spec/syntax/modules.html#data-segments -/// -/// WARNING: At present, this code -/// - Does limited validation of data segments -/// - Does not coalesce data segments -/// - Uses an implicit data format for the serialized segment data, defined -/// only in the code below -pub fn compile_data_initializers(compiler: &mut Compiler) -> Result<(), Error> { - let mut serialized: Vec = Vec::new(); - - for initializer in compiler.prog.data_initializers()? { - // Data segment has been validated in program::data. - // memory_index is always 0 per spec, so we dont put it in data. - let memory_index: u32 = 0; - serialized.write_u32::(memory_index).unwrap(); - serialized - .write_u32::(initializer.offset) - .unwrap(); - serialized - .write_u32::(initializer.data.len() as u32) - .unwrap(); - serialized.extend_from_slice(initializer.data); - - // Pad to 8 bytes: this aligns the data for architectures with 4 or - // 8 byte word sizes (i.e. everything we are likely to support at - // least until we replaced this with an engineered serialization - // format) - - let pad = vec![0u8; (8 - serialized.len() % 8) % 8]; - serialized.extend(pad); - } - - let mut serialized_len: Vec = Vec::new(); - serialized_len - .write_u32::(serialized.len() as u32) - .unwrap(); - let mut seg_len_ctx = DataContext::new(); - seg_len_ctx.define(serialized_len.into_boxed_slice()); - - let writeable = false; - let seg_len_decl = - compiler - .module - .declare_data("wasm_data_segments_len", Linkage::Export, writeable)?; - compiler.module.define_data(seg_len_decl, &seg_len_ctx)?; - - let mut seg_ctx = DataContext::new(); - seg_ctx.define(serialized.into_boxed_slice()); - let seg_decl = - compiler - .module - .declare_data("wasm_data_segments", Linkage::Export, writeable)?; - compiler.module.define_data(seg_decl, &seg_ctx)?; - - Ok(()) -} - -use std::io::Cursor; - -pub fn compile_sparse_page_data(compiler: &mut Compiler) -> Result<(), Error> { - use crate::program::data::sparse::OwnedSparseData; - let owned_data = OwnedSparseData::new( - &compiler.prog.data_initializers()?, - compiler.prog.heap_spec()?, - ); - let sparse_data = owned_data.sparse_data(); - - let mut table_ctx = DataContext::new(); - let mut table_data: Cursor> = - Cursor::new(Vec::with_capacity(sparse_data.pages().len() * 8 + 8)); - - // The table is an array of 64-bit elements: - // [0] the number subsequent elements - // [1..] a pointer to a 4096-byte array of the contents of the page, - // or null if it is initialized as zero. - - table_data - .write_u64::(sparse_data.pages().len() as u64) - .unwrap(); - for (dix, d) in sparse_data.pages().iter().enumerate() { - if let Some(vs) = d { - // Define the 4096-byte array for the contents of the page - let seg_decl = compiler.module.declare_data( - &format!("guest_sparse_page_data_{}", dix), - Linkage::Local, - false, - )?; - let mut seg_ctx = DataContext::new(); - seg_ctx.define(vs.to_vec().into_boxed_slice()); - compiler.module.define_data(seg_decl, &seg_ctx)?; - - // Put a relocation to that array into the table: - let seg_gv = compiler - .module - .declare_data_in_data(seg_decl, &mut table_ctx); - table_ctx.write_data_addr(table_data.position() as u32, seg_gv, 0); - } - table_data.write_u64::(0)?; - } - - table_ctx.define(table_data.into_inner().into_boxed_slice()); - let table_decl = - compiler - .module - .declare_data("guest_sparse_page_data", Linkage::Export, false)?; - compiler.module.define_data(table_decl, &table_ctx)?; - - Ok(()) -} diff --git a/lucetc/src/compiler/entity/bases.rs b/lucetc/src/compiler/entity/bases.rs deleted file mode 100644 index 47e6e2da7..000000000 --- a/lucetc/src/compiler/entity/bases.rs +++ /dev/null @@ -1,61 +0,0 @@ -use super::POINTER_SIZE; -use crate::compiler::Compiler; -use cranelift_codegen::ir::{self, types::I64, GlobalValueData}; - -// VMContext points directly to the heap (offset 0). -// Directly before the heap is a pointer to the globals (offset -POINTER_SIZE). -const GLOBAL_BASE_OFFSET: i32 = -1 * POINTER_SIZE as i32; - -pub struct GlobalBases { - heap: Option, - globals: Option, - table: Option, -} - -impl GlobalBases { - pub fn new() -> Self { - Self { - heap: None, - globals: None, - table: None, - } - } - - pub fn table(&mut self, func: &mut ir::Function, compiler: &Compiler) -> ir::GlobalValue { - self.table.unwrap_or_else(|| { - let table = &compiler.prog.tables()[0]; - let gv = func.create_global_value(GlobalValueData::Symbol { - name: compiler - .get_table(table) - .expect("table must be declared") - .into(), - offset: 0.into(), // Symbol points directly to table. - colocated: true, - }); - self.table = Some(gv); - gv - }) - } - - pub fn heap(&mut self, func: &mut ir::Function, _compiler: &Compiler) -> ir::GlobalValue { - self.heap.unwrap_or_else(|| { - let gv = func.create_global_value(GlobalValueData::VMContext); - self.heap = Some(gv); - gv - }) - } - - pub fn globals(&mut self, func: &mut ir::Function, _compiler: &Compiler) -> ir::GlobalValue { - self.globals.unwrap_or_else(|| { - let vmctx = func.create_global_value(GlobalValueData::VMContext); - let gv = func.create_global_value(GlobalValueData::Load { - base: vmctx, - offset: GLOBAL_BASE_OFFSET.into(), - global_type: I64, - readonly: false, - }); - self.globals = Some(gv); - gv - }) - } -} diff --git a/lucetc/src/compiler/entity/cache.rs b/lucetc/src/compiler/entity/cache.rs deleted file mode 100644 index 1b8462fb8..000000000 --- a/lucetc/src/compiler/entity/cache.rs +++ /dev/null @@ -1,90 +0,0 @@ -use crate::compiler::entity::{ - FunctionIndex, GlobalIndex, GlobalValue, MemoryIndex, SignatureIndex, -}; -use crate::program::{Function, FunctionSig}; -use cranelift_codegen::ir; -use failure::Error; -use std::collections::HashMap; - -#[derive(Debug, PartialEq, Eq, Hash)] -pub enum FunctionCacheIndex { - Wasm(FunctionIndex), - Runtime(String), -} - -pub struct Cache<'p> { - /// Collection of global variables that have been brought into scope - globals: HashMap, - /// Collection of heaps that have been brought into scope - heaps: HashMap, - /// Collection of indirect call signatures that have been brought into scope, - /// and the signatures themselves - signatures: HashMap, - /// Collection of functions that have been brought into scope, and the functions - /// themselves - functions: HashMap, -} - -impl<'p> Cache<'p> { - pub fn new() -> Self { - Self { - globals: HashMap::new(), - heaps: HashMap::new(), - signatures: HashMap::new(), - functions: HashMap::new(), - } - } - - pub fn global(&mut self, index: GlobalIndex, makeglob: F) -> Result - where - F: FnOnce() -> Result, - { - let r = entry_or_insert_result(&mut self.globals, index, makeglob); - Ok(*r?) - } - - pub fn heap(&mut self, index: MemoryIndex, makeheap: F) -> Result - where - F: FnOnce() -> Result, - { - let r = entry_or_insert_result(&mut self.heaps, index, makeheap); - Ok(*r?) - } - - pub fn signature( - &mut self, - index: SignatureIndex, - makesig: F, - ) -> Result<&(ir::SigRef, FunctionSig), Error> - where - F: FnOnce() -> Result<(ir::SigRef, FunctionSig), Error>, - { - entry_or_insert_result(&mut self.signatures, index, makesig) - } - - pub fn function( - &mut self, - index: FunctionCacheIndex, - makefunc: F, - ) -> Result<&(ir::FuncRef, &'p Function), Error> - where - F: FnOnce() -> Result<(ir::FuncRef, &'p Function), Error>, - { - entry_or_insert_result(&mut self.functions, index, makefunc) - } -} - -use std::collections::hash_map::Entry; -use std::hash::Hash; - -fn entry_or_insert_result(map: &mut HashMap, key: K, mkval: F) -> Result<&V, E> -where - K: Eq + Hash, - F: FnOnce() -> Result, -{ - let entry = map.entry(key); - Ok(match entry { - Entry::Occupied(entry) => entry.into_mut(), - Entry::Vacant(entry) => entry.insert(mkval()?), - }) -} diff --git a/lucetc/src/compiler/entity/global.rs b/lucetc/src/compiler/entity/global.rs deleted file mode 100644 index 00f406225..000000000 --- a/lucetc/src/compiler/entity/global.rs +++ /dev/null @@ -1,8 +0,0 @@ -use cranelift_codegen::ir; - -/// Value of WebAssembly global variable -#[derive(Clone, Copy, Debug)] -pub struct GlobalValue { - pub var: ir::GlobalValue, - pub ty: ir::Type, -} diff --git a/lucetc/src/compiler/entity/mod.rs b/lucetc/src/compiler/entity/mod.rs deleted file mode 100644 index 32b97125b..000000000 --- a/lucetc/src/compiler/entity/mod.rs +++ /dev/null @@ -1,169 +0,0 @@ -mod bases; -mod cache; -mod global; - -pub use self::global::GlobalValue; - -use self::bases::GlobalBases; -use self::cache::{Cache, FunctionCacheIndex}; -use crate::compiler::Compiler; -use crate::program::{CtonSignature, Function, FunctionSig, Program, TableDef}; -use cranelift_codegen::ir; -use cranelift_codegen::ir::types::{I32, I64}; -use cranelift_module::Linkage; -use failure::{format_err, Error, ResultExt}; -use std::fmt; - -pub type GlobalIndex = u32; -pub type MemoryIndex = u32; -pub type SignatureIndex = u32; -pub type FunctionIndex = u32; -pub type TableIndex = u32; - -fn global_var_offset(index: isize) -> isize { - index * POINTER_SIZE as isize -} - -// For readability -pub const POINTER_SIZE: usize = 8; -pub const NATIVE_POINTER: ir::Type = I64; - -pub struct EntityCreator<'p> { - program: &'p Program, - bases: GlobalBases, - cache: Cache<'p>, -} - -impl<'p> EntityCreator<'p> { - pub fn new(program: &'p Program) -> Self { - Self { - program: program, - bases: GlobalBases::new(), - cache: Cache::new(), - } - } - - pub fn get_global( - &mut self, - func: &mut ir::Function, - index: GlobalIndex, - compiler: &Compiler, - ) -> Result { - let global = self - .program - .globals() - .get(index as usize) - .ok_or_else(|| format_err!("global out of range: {}", index))?; - let base = self.bases.globals(func, compiler); - self.cache.global(index, || { - let offset = global_var_offset(index as isize); - let gv = func.create_global_value(ir::GlobalValueData::IAddImm { - base: base, - offset: (offset as i64).into(), - global_type: NATIVE_POINTER, - }); - Ok(GlobalValue { - var: gv, - ty: global.cton_type(), - }) - }) - } - - pub fn get_heap( - &mut self, - func: &mut ir::Function, - index: MemoryIndex, - compiler: &Compiler, - ) -> Result { - let base = self.bases.heap(func, compiler); - let heap_spec = self.program.heap_spec()?; - - self.cache.heap(index, || { - if index != 0 { - return Err(format_err!( - "can only create heap for memory index 0; got {}", - index - )); - } - Ok(func.create_heap(ir::HeapData { - base, - min_size: heap_spec.initial_size.into(), - offset_guard_size: heap_spec.guard_size.into(), - style: ir::HeapStyle::Static { - bound: heap_spec.reserved_size.into(), - }, - index_type: I32, - })) - }) - } - - pub fn get_table( - &mut self, - ix: TableIndex, - func: &mut ir::Function, - compiler: &Compiler, - ) -> Result<(TableDef, ir::GlobalValue), Error> { - let tbl = self.program.get_table(ix)?; - let base = self.bases.table(func, compiler); - Ok((tbl.clone(), base)) - } - - pub fn get_indirect_sig( - &mut self, - func: &mut ir::Function, - index: SignatureIndex, - ) -> Result<&(ir::SigRef, FunctionSig), Error> { - let sig = self.program.get_signature(index)?; - self.cache.signature(index, || { - let sigref = func.import_signature(sig.cton_signature()); - Ok((sigref, sig)) - }) - } - - pub fn get_direct_func( - &mut self, - func: &mut ir::Function, - index: FunctionIndex, - compiler: &Compiler, - ) -> Result<&(ir::FuncRef, &'p Function), Error> { - let f = self.program.get_function(index)?; - self.cache - .function(FunctionCacheIndex::Wasm(index), move || { - let import = func.import_signature(f.signature()); - let fref = func.import_function(ir::ExtFuncData { - name: compiler.get_function(f).context("direct call")?.into(), - signature: import, - colocated: match f.linkage() { - Linkage::Import => false, - _ => true, - }, - }); - Ok((fref, f)) - }) - } - - pub fn get_runtime_func( - &mut self, - func: &mut ir::Function, - name: String, - compiler: &Compiler, - ) -> Result<&(ir::FuncRef, &'p Function), Error> { - let f = self.program.get_runtime_function(&name)?; - self.cache - .function(FunctionCacheIndex::Runtime(name), move || { - let import = func.import_signature(f.signature()); - let fref = func.import_function(ir::ExtFuncData { - name: compiler.get_function(f).context("runtime call")?.into(), - signature: import, - colocated: false, - }); - Ok((fref, f)) - }) - } -} - -impl<'p> fmt::Debug for EntityCreator<'p> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "EntityCreator") - } -} diff --git a/lucetc/src/compiler/function.rs b/lucetc/src/compiler/function.rs deleted file mode 100644 index 9e485bbeb..000000000 --- a/lucetc/src/compiler/function.rs +++ /dev/null @@ -1,127 +0,0 @@ -use crate::compiler::entity::EntityCreator; -use crate::compiler::opcode::translate_opcode; -use crate::compiler::state::TranslationState; -use crate::compiler::Compiler; -use crate::program::types::cton_valuetype; -use crate::program::FunctionDef; -use cranelift_codegen::ir::{self, InstBuilder}; -use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext, Variable}; -use failure::{format_err, Error}; -use parity_wasm::elements::{self, FuncBody, ValueType}; - -pub fn compile_function<'p>( - compiler: &mut Compiler<'p>, - function: &FunctionDef, - body: &FuncBody, -) -> Result<(), Error> { - let sig = function.signature(); - - let name = compiler.get_function(function)?; - let mut func = ir::Function::with_name_signature(name.clone().into(), sig.clone()); - - { - let mut ctx: FunctionBuilderContext = FunctionBuilderContext::new(); - let mut builder = FunctionBuilder::new(&mut func, &mut ctx); - - // Create entry block. - let entry_block = builder.create_ebb(); - builder.append_ebb_params_for_function_params(entry_block); - builder.switch_to_block(entry_block); - builder.seal_block(entry_block); - builder.ensure_inserted_ebb(); - - let mut vargen = VariableGen::new(); - - // Declare a local for each wasm parameter. - for (param_ix, param_type) in sig.params.iter().enumerate() { - if param_type.purpose == ir::ArgumentPurpose::Normal { - let var = vargen.mint(); - builder.declare_var(var, param_type.value_type); - let value = builder.ebb_params(entry_block)[param_ix]; - builder.def_var(var, value); - } - } - // local decls - declare_locals(&mut builder, &mut vargen, body.locals()); - - // Create exit block. - let exit_block = builder.create_ebb(); - builder.append_ebb_params_for_function_returns(exit_block); - - // TranslationState is used to track control frames, the wasm stack, and translate - // wasm entities into cretone entities (entities means globals, heaps, sigs, funcs). - // The exit block is the final dest of all control frames, and return values are the - // bottom of the wasm stack. - let mut translation = TranslationState::new(&sig, exit_block); - - let mut entity_creator = EntityCreator::new(&compiler.prog); - - // Function body - let mut op_iter = body.code().elements().iter(); - while !translation.control_stack.is_empty() { - let op = op_iter - .next() - .ok_or(format_err!("ran out of opcodes before control stack"))?; - translate_opcode( - op, - &mut builder, - &mut translation, - &mut entity_creator, - compiler, - )?; - } - - // The end of the iteration left us in the exit block. As long as that block is reachable, - // need to manually pass the wasm stack to the return instruction. - if translation.reachable { - debug_assert!(builder.is_pristine()); - if !builder.is_unreachable() { - builder.ins().return_(&translation.stack); - } - } - - builder.finalize(); - } - - compiler.define_function(name, func)?; - Ok(()) -} - -fn declare_locals( - builder: &mut FunctionBuilder, - vargen: &mut VariableGen, - locals: &[elements::Local], -) { - for local in locals { - let localtype = local.value_type(); - let zeroval = match localtype { - ValueType::I32 => builder.ins().iconst(ir::types::I32, 0), - ValueType::I64 => builder.ins().iconst(ir::types::I64, 0), - ValueType::F32 => builder.ins().f32const(ir::immediates::Ieee32::with_bits(0)), - ValueType::F64 => builder.ins().f64const(ir::immediates::Ieee64::with_bits(0)), - ValueType::V128 => unimplemented!(), - }; - for _ in 0..local.count() { - let lvar = vargen.mint(); - builder.declare_var(lvar, cton_valuetype(&localtype)); - builder.def_var(lvar, zeroval); - } - } -} - -/// `VariableGen` is a source of fresh `Variable`s. It is never used directly by Cretonne. -#[derive(Debug)] -struct VariableGen { - index: u32, -} - -impl VariableGen { - pub fn new() -> Self { - Self { index: 0 } - } - pub fn mint(&mut self) -> Variable { - let var = Variable::with_u32(self.index); - self.index += 1; - var - } -} diff --git a/lucetc/src/compiler/globals.rs b/lucetc/src/compiler/globals.rs deleted file mode 100644 index ccdf2d060..000000000 --- a/lucetc/src/compiler/globals.rs +++ /dev/null @@ -1,87 +0,0 @@ -use crate::compiler::Compiler; -use crate::program::globals::Global; -use byteorder::{LittleEndian, WriteBytesExt}; -use cranelift_module::{DataContext, Linkage}; -use failure::Error; -use std::io::Cursor; - -pub fn compile_global_specs(compiler: &mut Compiler) -> Result<(), Error> { - let globals = compiler.prog.globals(); - let len = globals.len(); - - let mut spec_contents: Cursor> = Cursor::new(Vec::with_capacity(8 + 24 * len)); - spec_contents.write_u64::(len as u64).unwrap(); - - let mut spec_ctx = DataContext::new(); - - for ref g in globals { - spec_contents.write_u64::(flags(g)).unwrap(); - spec_contents.write_u64::(initval(g)).unwrap(); - if let Some(name) = name(&g) { - let sym_name = &format!("lucet_globals_name_{}", name); - // Declare data with the name - let name_decl = compiler - .module - .declare_data(sym_name, Linkage::Local, false)?; - // Put a relocation to the name into the spec - let sym_gv = compiler - .module - .declare_data_in_data(name_decl, &mut spec_ctx); - let position = spec_contents.position(); - assert!(position < ::max_value() as u64); - spec_ctx.write_data_addr(position as u32, sym_gv, 0); - - // Define the name in the module - let mut name_ctx = DataContext::new(); - name_ctx.define(sym_name.clone().into_bytes().into_boxed_slice()); - compiler.module.define_data(name_decl, &name_ctx)?; - } - spec_contents - .write_u64::(0) // Reloc goes here - .unwrap(); - } - spec_ctx.define(spec_contents.into_inner().into_boxed_slice()); - let spec_decl = compiler - .module - .declare_data("lucet_globals_spec", Linkage::Export, false)?; - compiler.module.define_data(spec_decl, &spec_ctx)?; - - Ok(()) -} - -fn flags(g: &Global) -> u64 { - let mut flags = 0; - match g { - &Global::Import(_) => { - flags |= 1; - } - _ => {} - } - match name(g) { - Some(_) => { - flags |= 2; - } - _ => {} - } - flags -} - -fn initval(g: &Global) -> u64 { - match g { - &Global::Def(ref def) => def.value() as u64, - _ => 0, - } -} - -fn name(g: &Global) -> Option { - match g { - &Global::Def(ref def) => def.export().map(|s| s.to_owned()), - &Global::Import(ref import) => { - if let Some(ex) = import.export() { - Some(ex.to_owned()) - } else { - Some(format!("{}::{}", import.module(), import.field())) - } - } - } -} diff --git a/lucetc/src/compiler/memory.rs b/lucetc/src/compiler/memory.rs deleted file mode 100644 index c71614e34..000000000 --- a/lucetc/src/compiler/memory.rs +++ /dev/null @@ -1,40 +0,0 @@ -use super::Compiler; -use crate::program::memory::HeapSpec; -use byteorder::{LittleEndian, WriteBytesExt}; -use cranelift_module::{DataContext, Linkage}; -use failure::Error; - -pub fn compile_memory_specs(compiler: &mut Compiler) -> Result<(), Error> { - let heap = compiler.prog.heap_spec()?; - - let mut heap_spec_ctx = DataContext::new(); - heap_spec_ctx.define(serialize_spec(&heap).into_boxed_slice()); - let heap_spec_decl = compiler - .module - .declare_data("lucet_heap_spec", Linkage::Export, false)?; - compiler - .module - .define_data(heap_spec_decl, &heap_spec_ctx)?; - Ok(()) -} - -fn serialize_spec(spec: &HeapSpec) -> Vec { - let mut serialized: Vec = Vec::with_capacity(5 * 8); - - serialized - .write_u64::(spec.reserved_size) - .unwrap(); - serialized - .write_u64::(spec.guard_size) - .unwrap(); - serialized - .write_u64::(spec.initial_size) - .unwrap(); - serialized - .write_u64::(spec.max_size.unwrap_or(0)) - .unwrap(); - serialized - .write_u64::(if spec.max_size.is_none() { 0 } else { 1 }) - .unwrap(); - serialized -} diff --git a/lucetc/src/compiler/mod.rs b/lucetc/src/compiler/mod.rs deleted file mode 100644 index 4377c0cfc..000000000 --- a/lucetc/src/compiler/mod.rs +++ /dev/null @@ -1,306 +0,0 @@ -pub mod data; -pub mod entity; -pub mod function; -pub mod globals; -pub mod memory; -pub mod module_data; -pub mod opcode; -pub mod state; -pub mod table; -pub mod traps; - -mod name; -mod stack_probe; - -pub use self::name::Name; - -use crate::compiler::traps::write_trap_manifest; -use crate::program::{Function, Program, TableDef}; -use byteorder::{LittleEndian, WriteBytesExt}; -use cranelift_codegen::settings::{self, Configurable}; -use cranelift_codegen::{ir, isa, print_errors::pretty_error, CodegenError}; -use cranelift_faerie::{FaerieBackend, FaerieBuilder, FaerieProduct, FaerieTrapCollection}; -use cranelift_module::{DataContext, Linkage, Module, ModuleError}; -use cranelift_native; -use faerie::Artifact; -use failure::{format_err, Error, ResultExt}; -use std::collections::HashMap; -use std::fs::File; -use std::io::Write; -use std::path::Path; - -#[derive(Debug, Clone, Copy)] -pub enum OptLevel { - Default, - Best, - Fastest, -} - -impl Default for OptLevel { - fn default() -> OptLevel { - OptLevel::Default - } -} - -impl OptLevel { - fn to_flag(&self) -> &str { - match self { - OptLevel::Default => "default", - OptLevel::Best => "best", - OptLevel::Fastest => "fastest", - } - } -} - -fn isa(opt_level: OptLevel) -> Box { - let mut flags_builder = settings::builder(); - let isa_builder = cranelift_native::builder().expect("host machine is not a supported target"); - flags_builder.enable("enable_verifier").unwrap(); - flags_builder.enable("is_pic").unwrap(); - flags_builder.set("opt_level", opt_level.to_flag()).unwrap(); - isa_builder.finish(settings::Flags::new(flags_builder)) -} - -pub struct Compiler<'p> { - pub prog: &'p Program, - funcs: HashMap, - module: Module, - opt_level: OptLevel, -} - -impl<'p> Compiler<'p> { - pub fn new(name: String, prog: &'p Program, opt_level: OptLevel) -> Result { - let libcalls = Box::new(move |libcall| match libcall { - ir::LibCall::Probestack => stack_probe::STACK_PROBE_SYM.to_owned(), - _ => (FaerieBuilder::default_libcall_names())(libcall), - }); - - let mut compiler = Self { - funcs: HashMap::new(), - module: Module::new(FaerieBuilder::new( - isa(opt_level), - name, - FaerieTrapCollection::Enabled, - libcalls, - )?), - prog: prog, - opt_level: opt_level, - }; - - for f in prog.import_functions() { - compiler.declare_function(f)?; - } - - let start_section = prog.module().start_section(); - for f in prog.defined_functions() { - let name = compiler.declare_function(f)?; - if Some(f.wasmidx) == start_section { - compiler.define_start_symbol(&name)?; - } - } - - for f in prog.runtime_functions() { - compiler.declare_function(f)?; - } - - for t in prog.tables() { - compiler.declare_table(t)?; - } - Ok(compiler) - } - - pub fn isa(&self) -> Box { - isa(self.opt_level) - } - - /// Add a `guest_start` data symbol pointing to the `start` section. - /// - /// We want to have the symbol `guest_start` point to the function - /// designated in the `start` section of the wasm module, but we - /// also want whatever function that is to be callable by its - /// normal symbol. Since ELF doesn't support aliasing function - /// symbols, we add a data symbol with a reloc pointer to the - /// function's normal symbol. - pub fn define_start_symbol(&mut self, start_func: &Name) -> Result<(), Error> { - let name = self.declare_data("guest_start", Linkage::Export, false)?; - let mut ctx = DataContext::new(); - ctx.define_zeroinit(8); - let fid = start_func - .into_funcid() - .ok_or(format_err!("start index pointed to a non-function"))?; - let fref = self.module.declare_func_in_data(fid, &mut ctx); - ctx.write_function_addr(0, fref); - self.define_data(name, &ctx) - } - - pub fn declare_function(&mut self, func: &Function) -> Result { - let funcid = self - .module - .declare_function(&func.symbol(), func.linkage(), &func.signature()) - .context(format!("declaration of {}", func.symbol()))?; - Ok(Name::new_func(func.symbol().to_owned(), funcid)) - } - - pub fn declare_table(&mut self, table: &TableDef) -> Result { - let mut serialized_len: Vec = Vec::new(); - serialized_len - .write_u64::(table.len() as u64) - .unwrap(); - let mut len_ctx = DataContext::new(); - len_ctx.define(serialized_len.into_boxed_slice()); - let len_decl = self - .module - .declare_data(&table.len_symbol(), Linkage::Export, false)?; - self.module.define_data(len_decl, &len_ctx)?; - - let dataid = self - .module - .declare_data(&table.symbol(), Linkage::Export, false)?; - Ok(Name::new_data(table.symbol(), dataid)) - } - - pub fn declare_data( - &mut self, - sym: &str, - linkage: Linkage, - mutable: bool, - ) -> Result { - let dataid = self.module.declare_data(sym, linkage, mutable)?; - Ok(Name::new_data(sym.to_owned(), dataid)) - } - - pub fn get_function(&self, func: &Function) -> Result { - let ident = self - .module - .get_name(&func.symbol()) - .ok_or(format_err!("function named {} undeclared", func.symbol()))?; - Ok(Name::new(func.symbol().to_owned(), ident)) - } - - pub fn get_table(&self, table: &TableDef) -> Result { - let ident = self - .module - .get_name(&table.symbol()) - .ok_or(format_err!("table named {} undeclared", table.symbol()))?; - Ok(Name::new(table.symbol(), ident)) - } - - pub fn get_data(&self, name: &str) -> Result { - let ident = self - .module - .get_name(name) - .ok_or(format_err!("data named {} undeclared", name,))?; - Ok(Name::new(name.to_owned(), ident)) - } - - pub fn define_function(&mut self, name: Name, func: ir::Function) -> Result<(), Error> { - use std::collections::hash_map::Entry; - match self.funcs.entry(name.clone()) { - Entry::Occupied(_entry) => { - return Err(format_err!( - "function {} has duplicate definition", - name.symbol() - )); - } - Entry::Vacant(entry) => { - entry.insert(func); - } - } - Ok(()) - } - - pub fn define_data(&mut self, name: Name, data: &DataContext) -> Result<(), Error> { - let id = name - .into_dataid() - .ok_or(format_err!("data defined with invalid name {:?}", name))?; - self.module.define_data(id, data)?; - Ok(()) - } - - pub fn cranelift_funcs(self) -> CraneliftFuncs { - let isa = self.isa(); - CraneliftFuncs { - funcs: self.funcs, - isa: isa, - } - } - - pub fn codegen(self) -> Result { - use cranelift_codegen::Context; - - let isa = &*self.isa(); - let mut ctx = Context::new(); - let mut module = self.module; - - for (name, func) in self.funcs.iter() { - ctx.func = func.clone(); - let id = name - .into_funcid() - .ok_or(format_err!("function defined with invalid name {:?}", name,))?; - module.define_function(id, &mut ctx).map_err(|e| match e { - ModuleError::Compilation(ce) => match ce { - CodegenError::Verifier(_) => - // Verifier errors are never recoverable. This is the last - // time we'll have enough information still around to pretty-print - { - format_err!( - "code generation error:\n{}", - pretty_error(func, Some(isa), ce) - ) - } - _ => ModuleError::Compilation(ce).into(), - }, - _ => e.into(), - })?; - ctx.clear(); - } - - ObjectFile::new(module.finish()) - } -} - -pub struct CraneliftFuncs { - funcs: HashMap, - isa: Box, -} - -impl CraneliftFuncs { - /// This outputs a .clif file - pub fn write>(&self, path: P) -> Result<(), Error> { - use cranelift_codegen::write_function; - let mut buffer = String::new(); - for (n, func) in self.funcs.iter() { - buffer.push_str(&format!("; {}\n", n.symbol())); - write_function(&mut buffer, func, Some(self.isa.as_ref())) - .context(format_err!("writing func {:?}", n))? - } - let mut file = File::create(path)?; - file.write_all(buffer.as_bytes())?; - Ok(()) - } -} - -pub struct ObjectFile { - artifact: Artifact, -} -impl ObjectFile { - pub fn new(mut product: FaerieProduct) -> Result { - stack_probe::declare_and_define(&mut product)?; - let trap_manifest = &product - .trap_manifest - .expect("trap manifest will be present"); - write_trap_manifest(trap_manifest, &mut product.artifact)?; - Ok(Self { - artifact: product.artifact, - }) - } - pub fn write>(&self, path: P) -> Result<(), Error> { - let _ = path.as_ref().file_name().ok_or(format_err!( - "path {:?} needs to have filename", - path.as_ref() - )); - let file = File::create(path)?; - self.artifact.write(file)?; - Ok(()) - } -} diff --git a/lucetc/src/compiler/module_data.rs b/lucetc/src/compiler/module_data.rs deleted file mode 100644 index 6fa5280fa..000000000 --- a/lucetc/src/compiler/module_data.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::compiler::Compiler; -use crate::program::data::sparse::OwnedSparseData; -use byteorder::{LittleEndian, WriteBytesExt}; -use cranelift_module::{DataContext, Linkage}; -use failure::Error; -use lucet_module_data::ModuleData; - -pub fn compile_module_data(compiler: &mut Compiler) -> Result<(), Error> { - let module_data_serialized: Vec = { - let heap_spec = compiler.prog.heap_spec()?; - let compiled_data = OwnedSparseData::new( - &compiler.prog.data_initializers()?, - compiler.prog.heap_spec()?, - ); - let sparse_data = compiled_data.sparse_data(); - - let globals = compiler.prog.globals(); - let globals_spec = globals.iter().map(|g| g.to_spec()).collect(); - - let module_data = ModuleData::new(heap_spec, sparse_data, globals_spec); - module_data.serialize()? - }; - - { - let mut serialized_len: Vec = Vec::new(); - serialized_len - .write_u32::(module_data_serialized.len() as u32) - .unwrap(); - let mut data_len_ctx = DataContext::new(); - data_len_ctx.define(serialized_len.into_boxed_slice()); - - let data_len_decl = - compiler - .module - .declare_data("lucet_module_data_len", Linkage::Export, false)?; - compiler.module.define_data(data_len_decl, &data_len_ctx)?; - } - - { - let mut module_data_ctx = DataContext::new(); - module_data_ctx.define(module_data_serialized.into_boxed_slice()); - - let module_data_decl = - compiler - .module - .declare_data("lucet_module_data", Linkage::Export, true)?; - compiler - .module - .define_data(module_data_decl, &module_data_ctx)?; - } - Ok(()) -} diff --git a/lucetc/src/compiler/opcode.rs b/lucetc/src/compiler/opcode.rs deleted file mode 100644 index c780c8ebf..000000000 --- a/lucetc/src/compiler/opcode.rs +++ /dev/null @@ -1,1175 +0,0 @@ -// This file is derived from cranelift/lib/wasm/src/code_translator.rs -// at commit 5a952497b9144f8a9437a4fb3af73b03b5be8dde - -use crate::compiler::entity::{EntityCreator, NATIVE_POINTER, POINTER_SIZE}; -use crate::compiler::state::{ControlVariant, TranslationState}; -use crate::compiler::Compiler; -use crate::program::types::cton_valuetype; -use crate::program::CtonSignature; -use cranelift_codegen::ir::condcodes::{FloatCC, IntCC}; -use cranelift_codegen::ir::types::{F32, F64, I32, I64}; -use cranelift_codegen::ir::{self, InstBuilder, JumpTableData, MemFlags}; -use cranelift_codegen::packed_option::ReservedValue; -use cranelift_frontend::{FunctionBuilder, Variable}; -use failure::{format_err, Error}; -use parity_wasm::elements::{BlockType, Instruction}; -use std::{i32, u32}; - -pub fn translate_opcode( - op: &Instruction, - builder: &mut FunctionBuilder, - state: &mut TranslationState, - entity_creator: &mut EntityCreator, - compiler: &Compiler, -) -> Result<(), Error> { - if !state.reachable { - return translate_unreachable_opcode(op, builder, state); - } - - match *op { - /********************************** Locals **************************************** - * `get_local` and `set_local` are treated as non-SSA variables and will completely - * diseappear in the Cretonne Code - ***********************************************************************************/ - Instruction::GetLocal(local_index) => { - state.push1(builder.use_var(Variable::with_u32(local_index))) - } - Instruction::SetLocal(local_index) => { - let val = state.pop1(); - builder.def_var(Variable::with_u32(local_index), val); - } - Instruction::TeeLocal(local_index) => { - let val = state.peek1(); - builder.def_var(Variable::with_u32(local_index), val); - } - - /********************************** Globals **************************************** - * `get_global` and `set_global` are handled by the environment. - ***********************************************************************************/ - Instruction::GetGlobal(global_index) => { - let global = entity_creator.get_global(builder.func, global_index, compiler)?; - let addr = builder.ins().global_value(NATIVE_POINTER, global.var); - let flags = ir::MemFlags::new(); - let val = builder.ins().load(global.ty, flags, addr, 0); - state.push1(val); - } - - Instruction::SetGlobal(global_index) => { - let global = entity_creator.get_global(builder.func, global_index, compiler)?; - let addr = builder.ins().global_value(NATIVE_POINTER, global.var); - let flags = ir::MemFlags::new(); - let val = state.pop1(); - builder.ins().store(flags, val, addr, 0); - } - - /********************************* Stack misc *************************************** - * `drop`, `nop`, `select`. - ***********************************************************************************/ - // Stack Misc - Instruction::Nop => {} - Instruction::Drop => { - state.pop1(); - } - Instruction::Select => { - let (whentrue, whenfalse, cond) = state.pop3(); - state.push1(builder.ins().select(cond, whentrue, whenfalse)); - } - - /***************************** Control flow blocks ********************************** - * When starting a control flow block, we create a new `Ebb` that will hold the code - * after the block, and we push a frame on the control stack. Depending on the type - * of block, we create a new `Ebb` for the body of the block with an associated - * jump instruction. - * - * The `End` instruction pops the last control frame from the control stack, seals - * the destination block (since `br` instructions targeting it only appear inside the - * block and have already been translated) and modify the value stack to use the - * possible `Ebb`'s arguments values. - ***********************************************************************************/ - Instruction::Unreachable => { - // Generates a trap instr and sets state so translate_unreachable_opcode is - // used until the control flow frame is complete. - builder.ins().trap(ir::TrapCode::User(0)); - state.reachable = false; - } - // Create a new Ebb for the continuation, push frame onto control type. - Instruction::Block(ty) => { - let next = builder.create_ebb(); - if let Some(ty) = cton_blocktype(&ty) { - builder.append_ebb_param(next, ty); - } - state.push_control_frame(ControlVariant::_block(), next, num_return_values(&ty)); - } - Instruction::Loop(ty) => { - let loop_body = builder.create_ebb(); - let next = builder.create_ebb(); - if let Some(ty) = cton_blocktype(&ty) { - builder.append_ebb_param(next, ty); - } - builder.ins().jump(loop_body, &[]); - state.push_control_frame( - ControlVariant::_loop(loop_body), - next, - num_return_values(&ty), - ); - builder.switch_to_block(loop_body); - } - Instruction::If(ty) => { - let val = state.pop1(); - let if_not = builder.create_ebb(); - // No arguments to jump: - // When If has no Else cuause, ty is EmptyBlock. - // When there is an Else clause, this jump destination will be - // overwritten, and the ultimate continuation will get the correct - // arguments from the Else ebb. - let branch_inst = builder.ins().brz(val, if_not, &[]); - if let Some(ty) = cton_blocktype(&ty) { - builder.append_ebb_param(if_not, ty); - } - let reachable = state.reachable; - state.push_control_frame( - ControlVariant::_if(branch_inst, reachable), - if_not, - num_return_values(&ty), - ); - } - - Instruction::Else => { - let last = state.control_stack.len() - 1; - let (branch_inst, ref mut reachable_from_top) = match state.control_stack[last].variant - { - ControlVariant::If { - branch_inst, - reachable_from_top, - .. - } => (branch_inst, reachable_from_top), - _ => panic!("impossible: else instruction when not in `if`"), - }; - // The if h as an else, so there's no branch to end from the top. - *reachable_from_top = false; - let retcnt = state.control_stack[last].num_return_values; - let dest = state.control_stack[last].destination; - builder.ins().jump(dest, state.peekn(retcnt)); - state.dropn(retcnt); - // Retarget the If branch to the else block - let else_ebb = builder.create_ebb(); - builder.change_jump_destination(branch_inst, else_ebb); - builder.seal_block(else_ebb); - builder.switch_to_block(else_ebb); - // When the End of this else block is reached, it will terminate the - // If just as above, to the destination. - } - - Instruction::End => { - let frame = state - .control_stack - .pop() - .expect("end instruction has populated ctrl stack"); - let return_count = frame.num_return_values; - if !builder.is_unreachable() || !builder.is_pristine() { - builder - .ins() - .jump(frame.following_code(), state.peekn(return_count)); - } - builder.switch_to_block(frame.following_code()); - builder.seal_block(frame.following_code()); - // Loop body needs to be sealed as well - if let ControlVariant::Loop { body } = frame.variant { - builder.seal_block(body); - } - state.stack.truncate(frame.original_stack_size); - let following_params = builder.ebb_params(frame.following_code()); - state.stack.extend_from_slice(following_params); - } - /**************************** Branch instructions ********************************* - * The branch instructions all have as arguments a target nesting level, which - * corresponds to how many control stack frames do we have to pop to get the - * destination `Ebb`. - * - * Once the destination `Ebb` is found, we sometimes have to declare a certain depth - * of the stack unreachable, because some branch instructions are terminator. - * - * The `br_table` case is much more complicated because Cretonne's `br_table` instruction - * does not support jump arguments like all the other branch instructions. That is why, in - * the case where we would use jump arguments for every other branch instructions, we - * need to split the critical edges leaving the `br_tables` by creating one `Ebb` per - * table destination; the `br_table` will point to these newly created `Ebbs` and these - * `Ebb`s contain only a jump instruction pointing to the final destination, this time with - * jump arguments. - * - * This system is also implemented in Cretonne's SSA construction algorithm, because - * `use_var` located in a destination `Ebb` of a `br_table` might trigger the addition - * of jump arguments in each predecessor branch instruction, one of which might be a - * `br_table`. - ***********************************************************************************/ - Instruction::Br(relative_depth) => { - let i = state.control_stack.len() - 1 - (relative_depth as usize); - let (return_count, br_destination) = { - let frame = &mut state.control_stack[i]; - // We signal that all the code that follows until the next End is unreachable - frame.set_branched_to_exit(); - let return_count = if frame.is_loop() { - 0 - } else { - frame.num_return_values - }; - (return_count, frame.br_destination()) - }; - builder - .ins() - .jump(br_destination, state.peekn(return_count)); - state.dropn(return_count); - state.reachable = false; - } - Instruction::BrIf(relative_depth) => { - let val = state.pop1(); - let i = state.control_stack.len() - 1 - (relative_depth as usize); - let (return_count, br_destination) = { - let frame = &mut state.control_stack[i]; - // The values returned by the branch are still available for the reachable - // code that comes after it - frame.set_branched_to_exit(); - let return_count = if frame.is_loop() { - 0 - } else { - frame.num_return_values - }; - (return_count, frame.br_destination()) - }; - builder - .ins() - .brnz(val, br_destination, state.peekn(return_count)); - } - Instruction::BrTable(ref data_table) => { - let depths = &data_table.table; - let default = data_table.default; - use std::collections::hash_map::{self, HashMap}; - let mut min_depth: u32 = default; - for depth in depths.iter() { - if *depth < min_depth { - min_depth = *depth; - } - } - let jump_args_count = { - let i = state.control_stack.len() - 1 - (min_depth as usize); - let min_depth_frame = &state.control_stack[i]; - if min_depth_frame.is_loop() { - 0 - } else { - min_depth_frame.num_return_values - } - }; - if jump_args_count == 0 { - // No jump arguments - let val = state.pop1(); - let mut data = JumpTableData::with_capacity(depths.len()); - for depth in depths.iter() { - let ebb = { - let i = state.control_stack.len() - 1 - (*depth as usize); - let frame = &mut state.control_stack[i]; - frame.set_branched_to_exit(); - frame.br_destination() - }; - data.push_entry(ebb); - } - let jt = builder.create_jump_table(data); - let ebb = { - let i = state.control_stack.len() - 1 - (default as usize); - let frame = &mut state.control_stack[i]; - frame.set_branched_to_exit(); - frame.br_destination() - }; - builder.ins().br_table(val, ebb, jt); - } else { - // Here we have jump arguments, but Cretonne's br_table doesn't support them - // We then proceed to split the edges going out of the br_table - let val = state.pop1(); - let return_count = jump_args_count; - let mut data = JumpTableData::with_capacity(depths.len()); - let mut dest_ebb_sequence = Vec::new(); - let mut dest_ebb_map = HashMap::new(); - for depth in depths.iter() { - let branch_ebb = match dest_ebb_map.entry(*depth as usize) { - hash_map::Entry::Occupied(entry) => *entry.get(), - hash_map::Entry::Vacant(entry) => { - let ebb = builder.create_ebb(); - dest_ebb_sequence.push((*depth as usize, ebb)); - *entry.insert(ebb) - } - }; - data.push_entry(branch_ebb); - } - let jt = builder.create_jump_table(data); - let default_ebb = { - let i = state.control_stack.len() - 1 - (default as usize); - let frame = &mut state.control_stack[i]; - frame.set_branched_to_exit(); - frame.br_destination() - }; - dest_ebb_sequence.push((default as usize, default_ebb)); - builder.ins().br_table(val, default_ebb, jt); - for (depth, dest_ebb) in dest_ebb_sequence { - builder.switch_to_block(dest_ebb); - builder.seal_block(dest_ebb); - let i = state.control_stack.len() - 1 - depth; - let real_dest_ebb = { - let frame = &mut state.control_stack[i]; - frame.set_branched_to_exit(); - frame.br_destination() - }; - builder.ins().jump(real_dest_ebb, state.peekn(return_count)); - } - state.dropn(return_count); - } - state.reachable = false; - } - Instruction::Return => { - let (return_count, br_destination) = { - let frame = &mut state.control_stack[0]; - frame.set_branched_to_exit(); - let return_count = frame.num_return_values; - (return_count, frame.br_destination()) - }; - { - let args = state.peekn(return_count); - builder.ins().jump(br_destination, args); - } - state.dropn(return_count); - state.reachable = false; - } - - /************************************ Calls **************************************** - * The call instructions pop off their arguments from the stack and append their - * return values to it. - ************************************ Calls ****************************************/ - Instruction::Call(callee_index) => { - let &(ref callee_ref, ref callee_func) = - entity_creator.get_direct_func(builder.func, callee_index, compiler)?; - - let sig = callee_func.signature(); - let num_args = normal_args(&sig); - let call_args = with_vmctx(builder.func, state.peekn(num_args))?; - - let call = builder.cursor().ins().call(*callee_ref, &call_args); - - state.dropn(num_args); - state.pushn(builder.inst_results(call)); - } - - Instruction::CallIndirect(type_index, _reserved) => { - let table_index = 0; - let (table, table_base) = - entity_creator.get_table(table_index, builder.func, compiler)?; - let &(ref sig_ref, ref fnsig) = - entity_creator.get_indirect_sig(builder.func, type_index)?; - let num_args = normal_args(&fnsig.cton_signature()); - - let callee = state.pop1(); - - // Indirect calls are performed by looking up the callee function and type in a table that - // is present in the same object file. - // The table is an array of pairs of (type index, function pointer). Both elements in the - // pair are the size of a pointer. - // The array is indexed by the callee, as an integer. The callee passed in above is a - // symbolic value because it is only known at run-time. - // We bounds-check the callee, look up the type index, check that it is equal to the type - // index for the call, and then call the function at the pointer. - - let call: Result = { - let mut pos = builder.cursor(); - - // `callee` is an integer value that may represent a valid offset into the - // icall table. - let calleebound = table.elements().len(); - // First see if the callee is even a valid index into the table. - let inbounds = pos.ins().icmp_imm( - ir::condcodes::IntCC::UnsignedLessThan, - callee, - calleebound as i64, - ); - pos.ins().trapz(inbounds, ir::TrapCode::IndirectCallToNull); - - let table_addr = pos.ins().global_value(NATIVE_POINTER, table_base); - let callee_64 = pos.ins().uextend(ir::Type::int(64).unwrap(), callee); - // Get the type index from memory: - let table_type_offs = pos.ins().imul_imm(callee_64, 2 * POINTER_SIZE as i64); - let table_type = pos.ins().iadd(table_addr, table_type_offs); - let typ = pos.ins().load( - ir::Type::int(64).unwrap(), - ir::MemFlags::new(), - table_type, - 0, - ); - let valid_type = - pos.ins() - .icmp_imm(ir::condcodes::IntCC::Equal, typ, type_index as i64); - pos.ins().trapz(valid_type, ir::TrapCode::BadSignature); - // Get the function ptr from memory: - let func_addr = pos.ins().load( - NATIVE_POINTER, - ir::MemFlags::new(), - table_type, - 8, // Size of i64 above - ); - - let call_args = with_vmctx(pos.func, state.peekn(num_args))?; - - Ok(pos.ins().call_indirect(*sig_ref, func_addr, &call_args)) - }; - let call = call?; - state.dropn(num_args); - state.pushn(builder.inst_results(call)); - } - - /******************************* Memory management *********************************** - * Memory management calls out to functions that come from the runtime. - ************************************************************************************/ - Instruction::GrowMemory(_reserved) => { - let &(ref callee_ref, ref _callee_func) = entity_creator.get_runtime_func( - builder.func, - "lucet_vmctx_grow_memory".into(), - compiler, - )?; - - let new_pages = state.pop1(); - - let call_args = with_vmctx(builder.func, &[new_pages])?; - - let call = builder.cursor().ins().call(*callee_ref, &call_args); - - state.pushn(builder.inst_results(call)); - } - - Instruction::CurrentMemory(_reserved) => { - let &(ref callee_ref, ref _callee_func) = entity_creator.get_runtime_func( - builder.func, - "lucet_vmctx_current_memory".into(), - compiler, - )?; - - let call_args = with_vmctx(builder.func, &[])?; - - let call = builder.cursor().ins().call(*callee_ref, &call_args); - - state.pushn(builder.inst_results(call)); - } - - /******************************* Load instructions *********************************** - * Wasm specifies an integer alignment flag but we drop it in Cretonne. - * The memory base address is provided by the environment. - * TODO: differentiate between 32 bit and 64 bit architecture, to put the uextend or not - ************************************************************************************/ - Instruction::I32Load8U(_flags, offset) => { - translate_load( - offset, - ir::Opcode::Uload8, - I32, - builder, - state, - entity_creator, - compiler, - )?; - } - Instruction::I32Load16U(_flags, offset) => { - translate_load( - offset, - ir::Opcode::Uload16, - I32, - builder, - state, - entity_creator, - compiler, - )?; - } - Instruction::I32Load8S(_flags, offset) => { - translate_load( - offset, - ir::Opcode::Sload8, - I32, - builder, - state, - entity_creator, - compiler, - )?; - } - Instruction::I32Load16S(_flags, offset) => { - translate_load( - offset, - ir::Opcode::Sload16, - I32, - builder, - state, - entity_creator, - compiler, - )?; - } - Instruction::I64Load8U(_flags, offset) => { - translate_load( - offset, - ir::Opcode::Uload8, - I64, - builder, - state, - entity_creator, - compiler, - )?; - } - Instruction::I64Load16U(_flags, offset) => { - translate_load( - offset, - ir::Opcode::Uload16, - I64, - builder, - state, - entity_creator, - compiler, - )?; - } - Instruction::I64Load8S(_flags, offset) => { - translate_load( - offset, - ir::Opcode::Sload8, - I64, - builder, - state, - entity_creator, - compiler, - )?; - } - Instruction::I64Load16S(_flags, offset) => { - translate_load( - offset, - ir::Opcode::Sload16, - I64, - builder, - state, - entity_creator, - compiler, - )?; - } - Instruction::I64Load32S(_flags, offset) => { - translate_load( - offset, - ir::Opcode::Sload32, - I64, - builder, - state, - entity_creator, - compiler, - )?; - } - Instruction::I64Load32U(_flags, offset) => { - translate_load( - offset, - ir::Opcode::Uload32, - I64, - builder, - state, - entity_creator, - compiler, - )?; - } - Instruction::I32Load(_flags, offset) => { - translate_load( - offset, - ir::Opcode::Load, - I32, - builder, - state, - entity_creator, - compiler, - )?; - } - Instruction::F32Load(_flags, offset) => { - translate_load( - offset, - ir::Opcode::Load, - F32, - builder, - state, - entity_creator, - compiler, - )?; - } - Instruction::I64Load(_flags, offset) => { - translate_load( - offset, - ir::Opcode::Load, - I64, - builder, - state, - entity_creator, - compiler, - )?; - } - Instruction::F64Load(_flags, offset) => { - translate_load( - offset, - ir::Opcode::Load, - F64, - builder, - state, - entity_creator, - compiler, - )?; - } - /****************************** Store instructions *********************************** - * Wasm specifies an integer alignment flag but we drop it in Cretonne. - * The memory base address is provided by the environment. - * TODO: differentiate between 32 bit and 64 bit architecture, to put the uextend or not - ************************************************************************************/ - Instruction::I32Store(_flags, offset) - | Instruction::I64Store(_flags, offset) - | Instruction::F32Store(_flags, offset) - | Instruction::F64Store(_flags, offset) => { - translate_store( - offset, - ir::Opcode::Store, - builder, - state, - entity_creator, - compiler, - )?; - } - Instruction::I32Store8(_flags, offset) | Instruction::I64Store8(_flags, offset) => { - translate_store( - offset, - ir::Opcode::Istore8, - builder, - state, - entity_creator, - compiler, - )?; - } - Instruction::I32Store16(_flags, offset) | Instruction::I64Store16(_flags, offset) => { - translate_store( - offset, - ir::Opcode::Istore16, - builder, - state, - entity_creator, - compiler, - )?; - } - Instruction::I64Store32(_flags, offset) => { - translate_store( - offset, - ir::Opcode::Istore32, - builder, - state, - entity_creator, - compiler, - )?; - } - /****************************** Nullary Opcodes ************************************/ - Instruction::I32Const(value) => state.push1(builder.ins().iconst(I32, i64::from(value))), - Instruction::I64Const(value) => state.push1(builder.ins().iconst(I64, value)), - Instruction::F32Const(value) => { - state.push1(builder.ins().f32const(f32_translation(value))); - } - Instruction::F64Const(value) => { - state.push1(builder.ins().f64const(f64_translation(value))); - } - /******************************* Unary Opcodes *************************************/ - Instruction::I32Clz | Instruction::I64Clz => { - let arg = state.pop1(); - state.push1(builder.ins().clz(arg)); - } - Instruction::I32Ctz | Instruction::I64Ctz => { - let arg = state.pop1(); - state.push1(builder.ins().ctz(arg)); - } - Instruction::I32Popcnt | Instruction::I64Popcnt => { - let arg = state.pop1(); - state.push1(builder.ins().popcnt(arg)); - } - Instruction::I64ExtendSI32 => { - let val = state.pop1(); - state.push1(builder.ins().sextend(I64, val)); - } - Instruction::I64ExtendUI32 => { - let val = state.pop1(); - state.push1(builder.ins().uextend(I64, val)); - } - Instruction::I32WrapI64 => { - let val = state.pop1(); - state.push1(builder.ins().ireduce(I32, val)); - } - Instruction::F32Sqrt | Instruction::F64Sqrt => { - let arg = state.pop1(); - state.push1(builder.ins().sqrt(arg)); - } - Instruction::F32Ceil | Instruction::F64Ceil => { - let arg = state.pop1(); - state.push1(builder.ins().ceil(arg)); - } - Instruction::F32Floor | Instruction::F64Floor => { - let arg = state.pop1(); - state.push1(builder.ins().floor(arg)); - } - Instruction::F32Trunc | Instruction::F64Trunc => { - let arg = state.pop1(); - state.push1(builder.ins().trunc(arg)); - } - Instruction::F32Nearest | Instruction::F64Nearest => { - let arg = state.pop1(); - state.push1(builder.ins().nearest(arg)); - } - Instruction::F32Abs | Instruction::F64Abs => { - let val = state.pop1(); - state.push1(builder.ins().fabs(val)); - } - Instruction::F32Neg | Instruction::F64Neg => { - let arg = state.pop1(); - state.push1(builder.ins().fneg(arg)); - } - Instruction::F64ConvertUI64 | Instruction::F64ConvertUI32 => { - let val = state.pop1(); - state.push1(builder.ins().fcvt_from_uint(F64, val)); - } - Instruction::F64ConvertSI64 | Instruction::F64ConvertSI32 => { - let val = state.pop1(); - state.push1(builder.ins().fcvt_from_sint(F64, val)); - } - Instruction::F32ConvertSI64 | Instruction::F32ConvertSI32 => { - let val = state.pop1(); - state.push1(builder.ins().fcvt_from_sint(F32, val)); - } - Instruction::F32ConvertUI64 | Instruction::F32ConvertUI32 => { - let val = state.pop1(); - state.push1(builder.ins().fcvt_from_uint(F32, val)); - } - Instruction::F64PromoteF32 => { - let val = state.pop1(); - state.push1(builder.ins().fpromote(F64, val)); - } - Instruction::F32DemoteF64 => { - let val = state.pop1(); - state.push1(builder.ins().fdemote(F32, val)); - } - Instruction::I64TruncSF64 | Instruction::I64TruncSF32 => { - let val = state.pop1(); - state.push1(builder.ins().fcvt_to_sint(I64, val)); - } - Instruction::I32TruncSF64 | Instruction::I32TruncSF32 => { - let val = state.pop1(); - state.push1(builder.ins().fcvt_to_sint(I32, val)); - } - Instruction::I64TruncUF64 | Instruction::I64TruncUF32 => { - let val = state.pop1(); - state.push1(builder.ins().fcvt_to_uint(I64, val)); - } - Instruction::I32TruncUF64 | Instruction::I32TruncUF32 => { - let val = state.pop1(); - state.push1(builder.ins().fcvt_to_uint(I32, val)); - } - Instruction::F32ReinterpretI32 => { - let val = state.pop1(); - state.push1(builder.ins().bitcast(F32, val)); - } - Instruction::F64ReinterpretI64 => { - let val = state.pop1(); - state.push1(builder.ins().bitcast(F64, val)); - } - Instruction::I32ReinterpretF32 => { - let val = state.pop1(); - state.push1(builder.ins().bitcast(I32, val)); - } - Instruction::I64ReinterpretF64 => { - let val = state.pop1(); - state.push1(builder.ins().bitcast(I64, val)); - } - /****************************** Binary Opcodes ************************************/ - Instruction::I32Add | Instruction::I64Add => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().iadd(arg1, arg2)); - } - Instruction::I32And | Instruction::I64And => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().band(arg1, arg2)); - } - Instruction::I32Or | Instruction::I64Or => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().bor(arg1, arg2)); - } - Instruction::I32Xor | Instruction::I64Xor => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().bxor(arg1, arg2)); - } - Instruction::I32Shl | Instruction::I64Shl => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().ishl(arg1, arg2)); - } - Instruction::I32ShrS | Instruction::I64ShrS => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().sshr(arg1, arg2)); - } - Instruction::I32ShrU | Instruction::I64ShrU => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().ushr(arg1, arg2)); - } - Instruction::I32Rotl | Instruction::I64Rotl => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().rotl(arg1, arg2)); - } - Instruction::I32Rotr | Instruction::I64Rotr => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().rotr(arg1, arg2)); - } - Instruction::F32Add | Instruction::F64Add => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().fadd(arg1, arg2)); - } - Instruction::I32Sub | Instruction::I64Sub => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().isub(arg1, arg2)); - } - Instruction::F32Sub | Instruction::F64Sub => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().fsub(arg1, arg2)); - } - Instruction::I32Mul | Instruction::I64Mul => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().imul(arg1, arg2)); - } - Instruction::F32Mul | Instruction::F64Mul => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().fmul(arg1, arg2)); - } - Instruction::F32Div | Instruction::F64Div => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().fdiv(arg1, arg2)); - } - Instruction::I32DivS | Instruction::I64DivS => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().sdiv(arg1, arg2)); - } - Instruction::I32DivU | Instruction::I64DivU => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().udiv(arg1, arg2)); - } - Instruction::I32RemS | Instruction::I64RemS => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().srem(arg1, arg2)); - } - Instruction::I32RemU | Instruction::I64RemU => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().urem(arg1, arg2)); - } - Instruction::F32Min | Instruction::F64Min => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().fmin(arg1, arg2)); - } - Instruction::F32Max | Instruction::F64Max => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().fmax(arg1, arg2)); - } - Instruction::F32Copysign | Instruction::F64Copysign => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().fcopysign(arg1, arg2)); - } - /**************************** Comparison Opcodes **********************************/ - Instruction::I32LtS | Instruction::I64LtS => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().icmp(IntCC::SignedLessThan, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } - Instruction::I32LtU | Instruction::I64LtU => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().icmp(IntCC::UnsignedLessThan, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } - Instruction::I32LeS | Instruction::I64LeS => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().icmp(IntCC::SignedLessThanOrEqual, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } - Instruction::I32LeU | Instruction::I64LeU => { - let (arg1, arg2) = state.pop2(); - let val = builder - .ins() - .icmp(IntCC::UnsignedLessThanOrEqual, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } - Instruction::I32GtS | Instruction::I64GtS => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().icmp(IntCC::SignedGreaterThan, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } - Instruction::I32GtU | Instruction::I64GtU => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().icmp(IntCC::UnsignedGreaterThan, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } - Instruction::I32GeS | Instruction::I64GeS => { - let (arg1, arg2) = state.pop2(); - let val = builder - .ins() - .icmp(IntCC::SignedGreaterThanOrEqual, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } - Instruction::I32GeU | Instruction::I64GeU => { - let (arg1, arg2) = state.pop2(); - let val = builder - .ins() - .icmp(IntCC::UnsignedGreaterThanOrEqual, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } - Instruction::I32Eqz | Instruction::I64Eqz => { - let arg = state.pop1(); - let val = builder.ins().icmp_imm(IntCC::Equal, arg, 0); - state.push1(builder.ins().bint(I32, val)); - } - Instruction::I32Eq | Instruction::I64Eq => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().icmp(IntCC::Equal, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } - Instruction::F32Eq | Instruction::F64Eq => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().fcmp(FloatCC::Equal, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } - Instruction::I32Ne | Instruction::I64Ne => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().icmp(IntCC::NotEqual, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } - Instruction::F32Ne | Instruction::F64Ne => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().fcmp(FloatCC::NotEqual, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } - Instruction::F32Gt | Instruction::F64Gt => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().fcmp(FloatCC::GreaterThan, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } - Instruction::F32Ge | Instruction::F64Ge => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().fcmp(FloatCC::GreaterThanOrEqual, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } - Instruction::F32Lt | Instruction::F64Lt => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().fcmp(FloatCC::LessThan, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } - Instruction::F32Le | Instruction::F64Le => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().fcmp(FloatCC::LessThanOrEqual, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } - _ => panic!("Unimplemented opcode: {:?}", *op), - } - - Ok(()) -} - -fn translate_unreachable_opcode( - op: &Instruction, - builder: &mut FunctionBuilder, - state: &mut TranslationState, -) -> Result<(), Error> { - // Don't translate any ops for this code, because it is unreachable. - // Just record the phantom stack for this code so we know when unreachable code ends. - match *op { - Instruction::If(_) => { - state.push_control_frame( - ControlVariant::_if(ir::Inst::reserved_value(), false), - ir::Ebb::reserved_value(), - 0, - ); - } - Instruction::Loop(_) | Instruction::Block(_) => { - state.push_control_frame(ControlVariant::_block(), ir::Ebb::reserved_value(), 0); - } - Instruction::Else => { - let i = state.control_stack.len() - 1; - match state.control_stack[i].variant { - ControlVariant::If { - branch_inst, - ref mut reachable_from_top, - .. - } => { - if *reachable_from_top { - // We have reached a branch from the top of the if to the else - state.reachable = true; - // And because there's an else, there can no longer be a branch from the - // top directly to the end. - *reachable_from_top = false; - // Retarget the If branch to the else block - let else_ebb = builder.create_ebb(); - builder.change_jump_destination(branch_inst, else_ebb); - builder.seal_block(else_ebb); - builder.switch_to_block(else_ebb); - } - } - _ => panic!("impossible: else instruction when not in `if`"), - } - } - Instruction::End => { - let stack = &mut state.stack; - let control_stack = &mut state.control_stack; - let frame = control_stack.pop().unwrap(); - - // Now we have split off the stack the values not used by unreachable code that hasn't - // been translated - stack.truncate(frame.original_stack_size); - let reachable_anyway = match frame.variant { - ControlVariant::Loop { body, .. } => { - // Have to seal the body loop block - builder.seal_block(body); - // Loops cant have branches to the end - false - } - ControlVariant::If { - reachable_from_top, .. - } => { - // A reachable if without an else has a branch from the top directly to the - // bottom - reachable_from_top - } - // Blocks are already handled - ControlVariant::Block { .. } => false, - }; - - if frame.exit_is_branched_to() || reachable_anyway { - builder.switch_to_block(frame.following_code()); - builder.seal_block(frame.following_code()); - // Add the return values of the block only if the next block is reachable - stack.extend_from_slice(builder.ebb_params(frame.following_code())); - state.reachable = true; - } - } - _ => {} // All other opcodes are not translated - } - Ok(()) -} - -fn cton_blocktype(bt: &BlockType) -> Option { - match bt { - &BlockType::Value(vt) => Some(cton_valuetype(&vt)), - &BlockType::NoResult => None, - } -} - -fn num_return_values(bt: &BlockType) -> usize { - match cton_blocktype(bt) { - Some(_) => 1, - None => 0, - } -} - -// Translate a load instruction. -// XXX we could take the flags argument from the wasm load instruction, -// and use that to generate a cton memflags. -fn translate_load<'m>( - offset: u32, - opcode: ir::Opcode, - result_ty: ir::Type, - builder: &mut FunctionBuilder, - state: &mut TranslationState, - entity_creator: &mut EntityCreator<'m>, - compiler: &Compiler, -) -> Result<(), Error> { - let addr32 = state.pop1(); - // We don't yet support multiple linear memories. - let heap = entity_creator.get_heap(builder.func, 0, compiler)?; - let (base, offset) = get_heap_addr(heap, addr32, offset, NATIVE_POINTER, builder); - let flags = MemFlags::new(); - let (load, dfg) = builder - .ins() - .Load(opcode, result_ty, flags, offset.into(), base); - state.push1(dfg.first_result(load)); - Ok(()) -} - -// Translate a store instruction. -// XXX we could take the flags argument from the wasm store instruction, -// and use that to generate a cton memflags. -fn translate_store<'m>( - offset: u32, - opcode: ir::Opcode, - builder: &mut FunctionBuilder, - state: &mut TranslationState, - entity_creator: &mut EntityCreator<'m>, - compiler: &Compiler, -) -> Result<(), Error> { - let (addr32, val) = state.pop2(); - let val_ty = builder.func.dfg.value_type(val); - - // We don't yet support multiple linear memories. - let heap = entity_creator.get_heap(builder.func, 0, compiler)?; - let (base, offset) = get_heap_addr(heap, addr32, offset, NATIVE_POINTER, builder); - let flags = MemFlags::new(); - builder - .ins() - .Store(opcode, val_ty, flags, offset.into(), val, base); - Ok(()) -} - -// Get the address+offset to use for a heap access. -fn get_heap_addr( - heap: ir::Heap, - addr32: ir::Value, - offset: u32, - addr_ty: ir::Type, - builder: &mut FunctionBuilder, -) -> (ir::Value, i32) { - use std::cmp::min; - - let guard_size: u64 = builder.func.heaps[heap].offset_guard_size.into(); - assert!(guard_size > 0, "Heap guard pages currently required"); - - // Generate `heap_addr` instructions that are friendly to CSE by checking offsets that are - // multiples of the guard size. Add one to make sure that we check the pointer itself is in - // bounds. - // - // For accesses on the outer skirts of the guard pages, we expect that we get a trap - // even if the access goes beyond the guard pages. This is because the first byte pointed to is - // inside the guard pages. - let check_size = min( - u32::MAX as u64, - 1 + (offset as u64 / guard_size) * guard_size, - ) as u32; - let base = builder.ins().heap_addr(addr_ty, heap, addr32, check_size); - - // Native load/store instructions take a signed `Offset32` immediate, so adjust the base - // pointer if necessary. - if offset > i32::MAX as u32 { - // Offset doesn't fit in the load/store instruction. - let adj = builder.ins().iadd_imm(base, i64::from(i32::MAX) + 1); - (adj, (offset - (i32::MAX as u32 + 1)) as i32) - } else { - (base, offset as i32) - } -} - -fn f32_translation(x: u32) -> ir::immediates::Ieee32 { - ir::immediates::Ieee32::with_bits(x) -} - -fn f64_translation(x: u64) -> ir::immediates::Ieee64 { - ir::immediates::Ieee64::with_bits(x) -} - -fn normal_args(sig: &ir::Signature) -> usize { - sig.params - .iter() - .filter(|a| a.purpose == ir::ArgumentPurpose::Normal) - .count() -} - -fn with_vmctx(func: &ir::Function, base_args: &[ir::Value]) -> Result, Error> { - let mut args: Vec = Vec::with_capacity(base_args.len() + 1); - args.extend_from_slice(base_args); - args.insert( - 0, - func.special_param(ir::ArgumentPurpose::VMContext) - .ok_or(format_err!( - "getting vm context parameter insert in call args" - ))?, - ); - Ok(args) -} diff --git a/lucetc/src/compiler/state.rs b/lucetc/src/compiler/state.rs deleted file mode 100644 index 9990e8611..000000000 --- a/lucetc/src/compiler/state.rs +++ /dev/null @@ -1,192 +0,0 @@ -use cranelift_codegen::ir::{self, Value}; - -#[derive(Debug)] -pub struct TranslationState { - /// WebAssembly stack - pub stack: Vec, - /// WebAssembly control structures - pub control_stack: Vec, - pub reachable: bool, -} - -impl TranslationState { - /// Create a translation state for compiling a function with a given signature. - /// The exit block is the last block in the function and contains the return instruction. - pub fn new(sig: &ir::Signature, exit_block: ir::Ebb) -> Self { - let mut state = Self::empty(); - state.push_control_frame( - ControlVariant::_block(), - exit_block, - sig.returns - .iter() - .filter(|arg| arg.purpose == ir::ArgumentPurpose::Normal) - .count(), - ); - state - } - - fn empty() -> Self { - Self { - stack: Vec::new(), - control_stack: Vec::new(), - reachable: true, - } - } - - /// Push a value - pub fn push1(&mut self, val: Value) { - self.stack.push(val) - } - - /// Push multiple values - pub fn pushn(&mut self, vals: &[Value]) { - self.stack.extend_from_slice(vals) - } - - /// Pop one value - pub fn pop1(&mut self) -> Value { - self.stack.pop().unwrap() - } - - /// Peek at value on top of stack - pub fn peek1(&mut self) -> Value { - *self.stack.last().unwrap() - } - - /// Pop two values, return in order they were pushed - pub fn pop2(&mut self) -> (Value, Value) { - let v2 = self.stack.pop().unwrap(); - let v1 = self.stack.pop().unwrap(); - (v1, v2) - } - - /// Pop three values, return in order they were pushed - pub fn pop3(&mut self) -> (Value, Value, Value) { - let v3 = self.stack.pop().unwrap(); - let v2 = self.stack.pop().unwrap(); - let v1 = self.stack.pop().unwrap(); - (v1, v2, v3) - } - - /// Drop the top `n` values on the stack. - /// Use `peekn` to look at them before dropping - pub fn dropn(&mut self, n: usize) { - let new_len = self.stack.len() - n; - self.stack.truncate(new_len); - } - - /// Peek at top `n` values in order they were pushed - pub fn peekn(&self, n: usize) -> &[Value] { - &self.stack[self.stack.len() - n..] - } - - pub fn push_control_frame( - &mut self, - variant: ControlVariant, - following_code: ir::Ebb, - num_return_values: usize, - ) { - let frame = ControlStackFrame { - variant: variant, - destination: following_code, - original_stack_size: self.stack.len(), - num_return_values: num_return_values, - }; - self.control_stack.push(frame); - } -} - -#[derive(Debug)] -pub struct ControlStackFrame { - pub variant: ControlVariant, - pub destination: ir::Ebb, - pub num_return_values: usize, - pub original_stack_size: usize, -} - -impl ControlStackFrame { - pub fn following_code(&self) -> ir::Ebb { - self.destination - } - pub fn br_destination(&self) -> ir::Ebb { - match self.variant { - ControlVariant::If { .. } | ControlVariant::Block { .. } => self.destination, - ControlVariant::Loop { body } => body, - } - } - pub fn is_loop(&self) -> bool { - match self.variant { - ControlVariant::Loop { .. } => true, - _ => false, - } - } - pub fn exit_is_branched_to(&self) -> bool { - match self.variant { - ControlVariant::If { - exit_is_branched_to, - .. - } - | ControlVariant::Block { - exit_is_branched_to, - .. - } => exit_is_branched_to, - ControlVariant::Loop { .. } => false, - } - } - - pub fn set_branched_to_exit(&mut self) { - match self.variant { - ControlVariant::If { - ref mut exit_is_branched_to, - .. - } - | ControlVariant::Block { - ref mut exit_is_branched_to, - .. - } => *exit_is_branched_to = true, - ControlVariant::Loop { .. } => {} - } - } -} - -#[derive(Debug)] -/// Use the constructor methods defined in the impl to preserve -/// invariants. The methods have leading underscores so they do not -/// collide with rust keywords. -pub enum ControlVariant { - If { - branch_inst: ir::Inst, - exit_is_branched_to: bool, - reachable_from_top: bool, - }, - Block { - exit_is_branched_to: bool, - }, - Loop { - body: ir::Ebb, - }, -} - -impl ControlVariant { - /// Constructor for the If variant. exit_is_branched_to must be false until it is set - /// explicitly. - pub fn _if(branch_inst: ir::Inst, reachable_from_top: bool) -> ControlVariant { - ControlVariant::If { - branch_inst: branch_inst, - exit_is_branched_to: false, - reachable_from_top: reachable_from_top, - } - } - /// Constructor for the Block variant. exit_is_branched_to must be false until it is set - /// explicitly. - pub fn _block() -> ControlVariant { - ControlVariant::Block { - exit_is_branched_to: false, - } - } - /// Constructor for the Loop variant. Doesn't have the exit_is_branched_to but I didn't want to - /// leave an odd variant without a constructor. - pub fn _loop(body: ir::Ebb) -> ControlVariant { - ControlVariant::Loop { body: body } - } -} diff --git a/lucetc/src/compiler/table.rs b/lucetc/src/compiler/table.rs deleted file mode 100644 index 9dcac8b3e..000000000 --- a/lucetc/src/compiler/table.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::compiler::Compiler; -use crate::program::table::{TableDef, TableElem}; -use byteorder::{LittleEndian, WriteBytesExt}; -use cranelift_module::DataContext; -use failure::{format_err, Error, ResultExt}; -use std::io::Cursor; - -pub fn compile_table<'p>(compiler: &mut Compiler<'p>, table: &TableDef) -> Result<(), Error> { - // Indirect calls are performed by looking up the callee function and type in a table that - // is present in the same object file. - // The table is an array of pairs of (type index, function pointer). Both elements in the - // pair are the size of a pointer. - // This function creates that table as a section in the object. - - // For readability: - let ptr_size = 8; - - let mut table_data = Cursor::new(Vec::with_capacity(table.elements().len() * 2 * ptr_size)); - let putelem = - { |table: &mut Cursor>, elem: u64| table.write_u64::(elem).unwrap() }; - let mut table_ctx = DataContext::new(); - - // table.elems is a vector that gives every entry of the table, either specifying the - // wasm function index or that no element was given for that table entry. - for table_elem in table.elements() { - match table_elem { - &TableElem::FunctionIx(ref func_index) => { - // Note: this is the only place we validate that the table entry points to a valid - // function. If this is ever removed, make sure this check happens elsewhere. - let func = compiler - .prog - .get_function(*func_index) - .context("function index for table element")?; - let sig_ix = func - .signature_index() - .ok_or(format_err!("table function should have a signature index"))?; - - // First element in row is the SignatureIndex for the function - putelem(&mut table_data, sig_ix as u64); - - // Second element in row is the pointer to the function. The Reloc is doing the work - // here. We put a 0 in the table data itself to be overwritten at link time. - let funcname = compiler.get_function(func)?; - let funcref = table_ctx.import_function(funcname.into()); - let position = table_data.position(); - assert!(position < ::max_value() as u64); - table_ctx.write_function_addr(position as u32, funcref); - putelem(&mut table_data, 0); - } - &TableElem::Empty => { - // First element is the signature index. These will always be 32 bits in wasm, so - // u64::max will always be out of bounds. - putelem(&mut table_data, ::max_value()); - // Second element is the function pointer. No relocation here, it will always be - // null. - putelem(&mut table_data, 0); - } - } - } - table_ctx.define(table_data.into_inner().into_boxed_slice()); - let table_id = compiler - .get_table(table)? - .into_dataid() - .expect("tables are data"); - compiler.module.define_data(table_id, &table_ctx)?; - Ok(()) -} diff --git a/lucetc/src/compiler/traps.rs b/lucetc/src/compiler/traps.rs deleted file mode 100644 index 7efe1893d..000000000 --- a/lucetc/src/compiler/traps.rs +++ /dev/null @@ -1,127 +0,0 @@ -use cranelift_codegen::ir; -use cranelift_faerie::traps::FaerieTrapManifest; - -use byteorder::{LittleEndian, WriteBytesExt}; -use faerie::{Artifact, Decl, Link}; -use failure::{Error, ResultExt}; -use std::io::Cursor; - -pub fn write_trap_manifest(manifest: &FaerieTrapManifest, obj: &mut Artifact) -> Result<(), Error> { - // declare traptable symbol - let manifest_len_sym = "lucet_trap_manifest_len"; - obj.declare(&manifest_len_sym, Decl::data().global()) - .context(format!("declaring {}", &manifest_len_sym))?; - - let manifest_sym = "lucet_trap_manifest"; - obj.declare(&manifest_sym, Decl::data().global()) - .context(format!("declaring {}", &manifest_sym))?; - - let manifest_len = manifest.sinks.len(); - let mut manifest_len_buf: Vec = Vec::new(); - manifest_len_buf - .write_u32::(manifest_len as u32) - .unwrap(); - obj.define(&manifest_len_sym, manifest_len_buf) - .context(format!("defining {}", &manifest_len_sym))?; - - // Manifests are serialized with the following struct elements in order: - // { func_start: ptr, func_len: u64, traps: ptr, traps_len: u64 } - let manifest_row_size = 8 * 4; - let mut manifest_buf: Cursor> = - Cursor::new(Vec::with_capacity(manifest_len * manifest_row_size)); - - for sink in manifest.sinks.iter() { - let func_sym = &sink.name; - let trap_sym = trap_sym_for_func(func_sym); - - // declare function-level trap table - obj.declare(&trap_sym, Decl::data().global()) - .context(format!("declaring {}", &trap_sym))?; - - // function symbol is provided via a link (abs8 relocation) - obj.link(Link { - from: &manifest_sym, - to: func_sym, - at: manifest_buf.position(), - }) - .context("linking function sym into trap manifest")?; - manifest_buf.write_u64::(0).unwrap(); - - // write function length - manifest_buf - .write_u64::(sink.code_size as u64) - .unwrap(); - - // table for this function is provided via a link (abs8 relocation) - obj.link(Link { - from: &manifest_sym, - to: &trap_sym, - at: manifest_buf.position(), - }) - .context("linking trap table into trap manifest")?; - manifest_buf.write_u64::(0).unwrap(); - - // finally, write the length of the trap table - manifest_buf - .write_u64::(sink.sites.len() as u64) - .unwrap(); - - // ok, now write the actual function-level trap table - let mut traps: Vec = Vec::new(); - - for site in sink.sites.iter() { - // write offset into trap table - traps.write_u32::(site.offset as u32).unwrap(); - // write serialized trap code into trap table - traps - .write_u32::(serialize_trapcode(site.code)) - .unwrap(); - } - - // and finally write the function trap table into the object - obj.define(&trap_sym, traps) - .context(format!("defining {}", &trap_sym))?; - } - - obj.define(&manifest_sym, manifest_buf.into_inner()) - .context(format!("defining {}", &manifest_sym))?; - - // iterate over tables: - // write empty relocation thunk - // link from traptable symbol + thunk offset to function symbol - // write trapsite count - // - // iterate over trapsites: - // write offset - // write trapcode - - Ok(()) -} - -fn trap_sym_for_func(sym: &str) -> String { - return format!("lucet_trap_table_{}", sym); -} - -// Trapcodes can be thought of as a tuple of (type, subtype). Each are -// represented as a 16-bit unsigned integer. These are packed into a u32 -// wherein the type occupies the low 16 bites and the subtype takes the -// high bits. -// -// Not all types have subtypes. Currently, only the user User type has a -// subtype. -fn serialize_trapcode(code: ir::TrapCode) -> u32 { - match code { - ir::TrapCode::StackOverflow => 0, - ir::TrapCode::HeapOutOfBounds => 1, - ir::TrapCode::OutOfBounds => 2, - ir::TrapCode::IndirectCallToNull => 3, - ir::TrapCode::BadSignature => 4, - ir::TrapCode::IntegerOverflow => 5, - ir::TrapCode::IntegerDivisionByZero => 6, - ir::TrapCode::BadConversionToInteger => 7, - ir::TrapCode::Interrupt => 8, - ir::TrapCode::TableOutOfBounds => 9, - ir::TrapCode::UnreachableCodeReached => (u16::max_value() - 1) as u32, // XXX this used to be User(0) - ir::TrapCode::User(x) => ((u16::max_value() - 1) as u32) | ((x as u32) << 16), - } -} diff --git a/lucetc/src/decls.rs b/lucetc/src/decls.rs new file mode 100644 index 000000000..b2c6525fc --- /dev/null +++ b/lucetc/src/decls.rs @@ -0,0 +1,540 @@ +use crate::error::Error; +use crate::heap::HeapSettings; +pub use crate::module::{Exportable, TableElems}; +use crate::module::{ModuleInfo, UniqueFuncIndex}; +use crate::name::Name; +use crate::runtime::{Runtime, RuntimeFunc}; +use crate::table::TABLE_SYM; +use crate::types::to_lucet_signature; +use cranelift_codegen::entity::{EntityRef, PrimaryMap}; +use cranelift_codegen::ir; +use cranelift_codegen::isa::TargetFrontendConfig; +use cranelift_module::{Backend as ClifBackend, Linkage, Module as ClifModule}; +use cranelift_wasm::{ + Global, GlobalIndex, GlobalInit, MemoryIndex, ModuleEnvironment, SignatureIndex, Table, + TableIndex, +}; +use lucet_module::bindings::Bindings; +use lucet_module::ModuleFeatures; +use lucet_module::{ + owned::OwnedLinearMemorySpec, ExportFunction, FunctionIndex as LucetFunctionIndex, + FunctionMetadata, Global as GlobalVariant, GlobalDef, GlobalSpec, HeapSpec, ImportFunction, + ModuleData, Signature as LucetSignature, UniqueSignatureIndex, +}; +use std::collections::HashMap; + +#[derive(Debug)] +pub struct FunctionDecl<'a> { + pub import_name: Option<(&'a str, &'a str)>, + pub export_names: Vec<&'a str>, + pub signature_index: UniqueSignatureIndex, + pub signature: &'a ir::Signature, + pub name: Name, +} + +impl<'a> FunctionDecl<'a> { + pub fn defined(&self) -> bool { + self.import_name.is_none() + } + pub fn imported(&self) -> bool { + !self.defined() + } +} + +#[derive(Debug)] +/// Function provided by lucet-runtime to be called from generated code, e.g. memory size & grow +/// functions. +pub struct RuntimeDecl<'a> { + signature: &'a ir::Signature, + pub name: Name, +} + +impl<'a> RuntimeDecl<'a> { + pub fn signature(&self) -> &'a ir::Signature { + self.signature + } +} + +#[derive(Debug)] +pub struct TableDecl<'a> { + pub import_name: Option<(&'a str, &'a str)>, + pub export_names: Vec<&'a str>, + pub table: &'a Table, + pub elems: &'a [TableElems], + pub contents_name: Name, +} + +pub struct ModuleDecls<'a> { + pub info: ModuleInfo<'a>, + function_names: PrimaryMap, + imports: Vec>, + exports: Vec>, + tables_list_name: Name, + table_names: PrimaryMap, + runtime_names: HashMap, + globals_spec: Vec>, + linear_memory_spec: Option, +} + +impl<'a> ModuleDecls<'a> { + pub fn new( + info: ModuleInfo<'a>, + clif_module: &mut ClifModule, + bindings: &'a Bindings, + runtime: Runtime, + heap_settings: HeapSettings, + ) -> Result { + let imports: Vec> = Vec::with_capacity(info.imported_funcs.len()); + let (tables_list_name, table_names) = Self::declare_tables(&info, clif_module)?; + let globals_spec = Self::build_globals_spec(&info)?; + let linear_memory_spec = Self::build_linear_memory_spec(&info, heap_settings)?; + let mut decls = Self { + info, + function_names: PrimaryMap::new(), + imports, + exports: vec![], + tables_list_name, + table_names, + runtime_names: HashMap::new(), + globals_spec, + linear_memory_spec, + }; + + Self::declare_funcs(&mut decls, clif_module, bindings)?; + Self::declare_runtime(&mut decls, clif_module, runtime)?; + + Ok(decls) + } + + // ********************* Constructor auxillary functions *********************** + + fn declare_funcs( + decls: &mut ModuleDecls<'a>, + clif_module: &mut ClifModule, + bindings: &'a Bindings, + ) -> Result<(), Error> { + // Get the name for this function from the module names section, if it exists. + // Because names have to be unique, we append the index value (ix) to the name. + fn custom_name_for<'a>( + ix: usize, + func_index: UniqueFuncIndex, + decls: &mut ModuleDecls<'a>, + ) -> Option { + decls + .info + .function_names + .get(func_index) + .map(|s| format!("{}_{}", s, ix)) + } + + fn export_name_for<'a>( + func_ix: UniqueFuncIndex, + decls: &mut ModuleDecls<'a>, + ) -> Option { + let export = decls.info.functions.get(func_ix).unwrap(); + if !export.export_names.is_empty() { + decls.exports.push(ExportFunction { + fn_idx: LucetFunctionIndex::from_u32(decls.function_names.len() as u32), + names: export.export_names.clone(), + }); + Some(format!("guest_func_{}", export.export_names[0])) + } else { + None + } + } + + fn import_name_for<'a>( + func_ix: UniqueFuncIndex, + decls: &mut ModuleDecls<'a>, + bindings: &'a Bindings, + ) -> Result, Error> { + if let Some((import_mod, import_field)) = decls.info.imported_funcs.get(func_ix) { + let import_symbol = bindings.translate(import_mod, import_field)?; + decls.imports.push(ImportFunction { + fn_idx: LucetFunctionIndex::from_u32(decls.function_names.len() as u32), + module: import_mod, + name: import_field, + }); + Ok(Some(import_symbol.to_string())) + } else { + Ok(None) + } + } + + for ix in 0..decls.info.functions.len() { + let func_index = UniqueFuncIndex::new(ix); + let import_info = import_name_for(func_index, decls, bindings)?; + let export_info = export_name_for(func_index, decls); + + match (import_info, export_info) { + (Some(import_sym), _) => { + // if a function is only an import, declare the corresponding artifact import. + // if a function is an export and import, it will not have a real function body + // in this program, and we must not declare it with Linkage::Export (there will + // never be a define to satisfy the symbol!) + decls.declare_function(clif_module, import_sym, Linkage::Import, func_index)?; + } + (None, Some(export_sym)) => { + // This is a function that is only exported, so there will be a body in this + // artifact. We can declare the export. + decls.declare_function(clif_module, export_sym, Linkage::Export, func_index)?; + } + (None, None) => { + // No import or export for this function, which means that it is local. We can + // look for a name provided in the custom names section, otherwise we have to + // make up a placeholder name for it using its index. + let local_sym = custom_name_for(ix, func_index, decls) + .unwrap_or_else(|| format!("guest_func_{}", ix)); + decls.declare_function(clif_module, local_sym, Linkage::Local, func_index)?; + } + } + } + Ok(()) + } + + /// Insert a new function into this set of decls and declare it appropriately to `clif_module`. + /// This is intended for cases where `lucetc` adds a new function that was not present in the + /// original wasm - in these cases, Cranelift has not already declared the signature or + /// function type, let alone name, linkage, etc. So we must do that ourselves! + pub fn declare_new_function( + &mut self, + clif_module: &mut ClifModule, + decl_sym: String, + decl_linkage: Linkage, + signature: ir::Signature, + ) -> Result { + let (new_funcidx, _) = self.info.declare_func_with_sig(signature)?; + + self.declare_function(clif_module, decl_sym, decl_linkage, new_funcidx)?; + + Ok(new_funcidx) + } + + /// The internal side of fixing up a new function declaration. This is also the work that must + /// be done when building a ModuleDecls record of functions that were described by ModuleInfo. + fn declare_function( + &mut self, + clif_module: &mut ClifModule, + decl_sym: String, + decl_linkage: Linkage, + func_ix: UniqueFuncIndex, + ) -> Result { + // This function declaration may be a subsequent additional declaration for a function + // we've already been told about. In that case, func_ix will already be a valid index for a + // function name, and we should not try to declare it again. + // + // Regardless of the function being known internally, we must forward the additional + // declaration to `clif_module` so functions with multiple forms of linkage (import + + // export, exported twice, ...) are correctly declared in the resultant artifact. + let funcid = clif_module.declare_function( + &decl_sym, + decl_linkage, + self.info.signature_for_function(func_ix), + )?; + + if func_ix.as_u32() as usize >= self.function_names.len() { + // `func_ix` is new, so we need to add the name. If func_ix is new, it should be + // an index for the Name we're about to add. That is, it should be the same as the + // current value for `self.function_names.len()`. + // + // If this fails, we're declaring functions out of order. oops! + debug_assert!(func_ix.as_u32() as usize == self.function_names.len()); + + self.function_names.push(Name::new_func(decl_sym, funcid)); + } + + Ok(UniqueFuncIndex::new(self.function_names.len() - 1)) + } + + fn declare_tables( + info: &ModuleInfo<'a>, + clif_module: &mut ClifModule, + ) -> Result<(Name, PrimaryMap), Error> { + let mut table_names = PrimaryMap::new(); + for ix in 0..info.tables.len() { + let def_symbol = format!("guest_table_{}", ix); + let def_data_id = + clif_module.declare_data(&def_symbol, Linkage::Export, false, None)?; + let def_name = Name::new_data(def_symbol, def_data_id); + + table_names.push(def_name); + } + + let tables_list_id = clif_module.declare_data(TABLE_SYM, Linkage::Export, false, None)?; + let tables_list = Name::new_data(TABLE_SYM.to_string(), tables_list_id); + + Ok((tables_list, table_names)) + } + + fn declare_runtime( + decls: &mut ModuleDecls<'a>, + clif_module: &mut ClifModule, + runtime: Runtime, + ) -> Result<(), Error> { + for (func, (symbol, signature)) in runtime.functions.iter() { + let func_id = decls.declare_new_function( + clif_module, + symbol.clone(), + Linkage::Import, + signature.clone(), + )?; + + decls.runtime_names.insert(*func, func_id); + } + Ok(()) + } + + fn build_linear_memory_spec( + info: &ModuleInfo<'a>, + heap_settings: HeapSettings, + ) -> Result, Error> { + use crate::sparsedata::owned_sparse_data_from_initializers; + if let Some(heap_spec) = Self::build_heap_spec(info, heap_settings)? { + let data_initializers = info + .data_initializers + .get(&MemoryIndex::new(0)) + .expect("heap spec implies data initializers should exist"); + let sparse_data = owned_sparse_data_from_initializers(data_initializers, &heap_spec)?; + + Ok(Some(OwnedLinearMemorySpec { + heap: heap_spec, + initializer: sparse_data, + })) + } else { + Ok(None) + } + } + + fn build_globals_spec(info: &ModuleInfo<'a>) -> Result>, Error> { + let mut globals = Vec::new(); + for ix in 0..info.globals.len() { + let ix = GlobalIndex::new(ix); + let g_decl = info.globals.get(ix).unwrap(); + + let global = match g_decl.entity.initializer { + GlobalInit::I32Const(i) => Ok(GlobalVariant::Def(GlobalDef::I32(i))), + GlobalInit::I64Const(i) => Ok(GlobalVariant::Def(GlobalDef::I64(i))), + GlobalInit::F32Const(f) => { + Ok(GlobalVariant::Def(GlobalDef::F32(f32::from_bits(f)))) + } + GlobalInit::F64Const(f) => { + Ok(GlobalVariant::Def(GlobalDef::F64(f64::from_bits(f)))) + } + GlobalInit::GetGlobal(ref_ix) => { + let ref_decl = info.globals.get(ref_ix).unwrap(); + if let GlobalInit::Import = ref_decl.entity.initializer { + if let Some((module, field)) = info.imported_globals.get(ref_ix) { + Ok(GlobalVariant::Import { module, field }) + } else { + Err(Error::GlobalDeclarationError(ref_ix.as_u32())) + } + } else { + // This WASM restriction may be loosened in the future: + Err(Error::GlobalInitError(ix.as_u32())) + } + } + GlobalInit::Import => { + if let Some((module, field)) = info.imported_globals.get(ix) { + Ok(GlobalVariant::Import { module, field }) + } else { + Err(Error::GlobalDeclarationError(ix.as_u32())) + } + } + GlobalInit::V128Const(_) => Err(Error::GlobalUnsupported(ix.as_u32())), + }?; + + globals.push(GlobalSpec::new(global, g_decl.export_names.clone())); + } + Ok(globals) + } + + fn build_heap_spec( + info: &ModuleInfo<'a>, + heap_settings: HeapSettings, + ) -> Result, Error> { + match info.memories.len() { + 0 => Ok(None), + 1 => { + let memory = info + .memories + .get(MemoryIndex::new(0)) + .expect("memory in range") + .entity; + + let wasm_page: u64 = 64 * 1024; + let initial_size = memory.minimum as u64 * wasm_page; + + let reserved_size = std::cmp::max(initial_size, heap_settings.min_reserved_size); + if reserved_size > heap_settings.max_reserved_size { + let message = format!( + "module reserved size ({}) exceeds max reserved size ({})", + reserved_size, heap_settings.max_reserved_size + ); + Err(Error::MemorySpecs(message))?; + } + // Find the max size permitted by the heap and the memory spec + let max_size = memory.maximum.map(|pages| pages as u64 * wasm_page); + Ok(Some(HeapSpec { + reserved_size, + guard_size: heap_settings.guard_size, + initial_size, + max_size, + })) + } + _ => Err(Error::Unsupported( + "lucetc only supports memory 0".to_string(), + ))?, + } + } + // ********************* Public Interface ************************** + + pub fn target_config(&self) -> TargetFrontendConfig { + self.info.target_config() + } + + pub fn function_bodies(&self) -> impl Iterator, &(&'a [u8], usize))> { + Box::new( + self.info + .function_bodies + .iter() + .map(move |(fidx, code)| (self.get_func(*fidx).unwrap(), code)), + ) + } + + pub fn get_func(&self, func_index: UniqueFuncIndex) -> Option> { + let name = self.function_names.get(func_index).unwrap(); + let exportable_sigix = self.info.functions.get(func_index).unwrap(); + let signature_index = self.get_signature_uid(exportable_sigix.entity).unwrap(); + let signature = self.info.signatures.get(signature_index).unwrap(); + let import_name = self.info.imported_funcs.get(func_index); + Some(FunctionDecl { + signature, + signature_index, + export_names: exportable_sigix.export_names.clone(), + import_name: import_name.cloned(), + name: name.clone(), + }) + } + + pub fn get_start_func(&self) -> Option { + self.info.start_func.clone() + } + + pub fn get_runtime(&self, runtime_func: RuntimeFunc) -> Result, Error> { + let func_id = *self.runtime_names.get(&runtime_func).unwrap(); + let name = self.function_names.get(func_id).unwrap(); + Ok(RuntimeDecl { + signature: self.info.signature_for_function(func_id), + name: name.clone(), + }) + } + + pub fn get_tables_list_name(&self) -> &Name { + &self.tables_list_name + } + + pub fn get_table(&self, table_index: TableIndex) -> Result, Error> { + let contents_name = self.table_names.get(table_index).ok_or_else(|| { + let message = format!("{:?}", table_index); + Error::TableIndexError(message) + })?; + let exportable_tbl = self.info.tables.get(table_index).unwrap(); + let import_name = self.info.imported_tables.get(table_index); + let elems = self + .info + .table_elems + .get(&table_index) + .ok_or_else(|| { + let message = format!("table is not local: {:?}", table_index); + Error::Unsupported(message) + })? + .as_slice(); + Ok(TableDecl { + table: &exportable_tbl.entity, + elems, + export_names: exportable_tbl.export_names.clone(), + import_name: import_name.cloned(), + contents_name: contents_name.clone(), + }) + } + + pub fn get_signature(&self, signature_index: SignatureIndex) -> Result<&ir::Signature, Error> { + self.get_signature_uid(signature_index).and_then(|uid| { + self.info.signatures.get(uid).ok_or_else(|| { + let message = format!("signature out of bounds: {:?}", uid); + Error::Signature(message) + }) + }) + } + + pub fn get_signature_uid( + &self, + signature_index: SignatureIndex, + ) -> Result { + self.info + .signature_mapping + .get(signature_index) + .map(|x| *x) + .ok_or_else(|| { + let message = format!("signature out of bounds: {:?}", signature_index); + Error::Signature(message) + }) + } + + pub fn get_global(&self, global_index: GlobalIndex) -> Result<&Exportable<'_, Global>, Error> { + self.info.globals.get(global_index).ok_or_else(|| { + let message = format!("global out of bounds: {:?}", global_index); + Error::GlobalIndexError(message) + }) + } + + pub fn get_heap(&self) -> Option<&HeapSpec> { + if let Some(ref spec) = self.linear_memory_spec { + Some(&spec.heap) + } else { + None + } + } + + pub fn get_module_data(&self, features: ModuleFeatures) -> Result, Error> { + let linear_memory = if let Some(ref spec) = self.linear_memory_spec { + Some(spec.to_ref()) + } else { + None + }; + + let mut functions: Vec> = Vec::new(); + for fn_index in self.function_names.keys() { + let decl = self.get_func(fn_index).unwrap(); + + // can't use `decl.name` for `FunctionMetadata::name` as `decl` is dropped in the next + // iteration of this loop. + let name = self + .function_names + .get(fn_index) + .expect("fn_index is key into function_names"); + + functions.push(FunctionMetadata { + signature: decl.signature_index, + name: Some(name.symbol()), + }); + } + + let signatures = self + .info + .signatures + .values() + .map(|sig| to_lucet_signature(sig).map_err(Error::SignatureConversion)) + .collect::, Error>>()?; + + Ok(ModuleData::new( + linear_memory, + self.globals_spec.clone(), + functions, + self.imports.clone(), + self.exports.clone(), + signatures, + features, + )) + } +} diff --git a/lucetc/src/error.rs b/lucetc/src/error.rs index 29b13de31..5c9cde522 100644 --- a/lucetc/src/error.rs +++ b/lucetc/src/error.rs @@ -1,82 +1,84 @@ -use failure::{Backtrace, Context, Error, Fail}; -use pwasm_validation; -use std::fmt::{self, Display}; +use crate::types::SignatureError; +use cranelift_module::ModuleError as ClifModuleError; +use cranelift_wasm::WasmError as ClifWasmError; +use faerie::ArtifactError; +use lucet_module::error::Error as LucetModuleError; +use thiserror::Error; -#[derive(Debug)] -pub struct LucetcError { - inner: Context, -} - -impl From> for LucetcError { - fn from(inner: Context) -> LucetcError { - LucetcError { inner } - } -} - -impl From for LucetcError { - fn from(kind: LucetcErrorKind) -> LucetcError { - LucetcError { - inner: Context::new(kind), - } - } -} - -impl LucetcError { - pub fn get_context(&self) -> &LucetcErrorKind { - self.inner.get_context() - } -} - -impl Fail for LucetcError { - fn cause(&self) -> Option<&Fail> { - self.inner.cause() - } - fn backtrace(&self) -> Option<&Backtrace> { - self.inner.backtrace() - } -} - -impl Display for LucetcError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - Display::fmt(&self.inner, f) - } -} - -impl From for LucetcError { - fn from(e: Error) -> LucetcError { - e.context(LucetcErrorKind::UnknownKind).into() - } -} - -impl From for LucetcError { - fn from(e: pwasm_validation::Error) -> LucetcError { - e.context(LucetcErrorKind::Validation).into() - } -} - -#[derive(Debug, Fail, PartialEq, Eq)] -pub enum LucetcErrorKind { - #[fail(display = "Data Initializers")] - DataInitializers, - #[fail(display = "Memory specs")] - MemorySpecs, - #[fail(display = "Global specs")] - GlobalSpecs, - #[fail(display = "Module data")] - ModuleData, - #[fail(display = "Function {}", _0)] - Function(String), - #[fail(display = "Table {}", _0)] - Table(String), - #[fail(display = "Validation")] - Validation, - - #[fail(display = "Unsupported: {}", _0)] +#[derive(Debug, Error)] +pub enum Error { + // + // General #[from] implementations. + #[error("Builtins error")] + Builtins(#[from] parity_wasm::elements::Error), + #[error("Clif module error")] + ClifModuleError(#[from] ClifModuleError), + #[error("Translating error")] + ClifWasmError(#[from] ClifWasmError), + #[error("Lucet Module error")] + LucetModule(#[from] LucetModuleError), + #[error("Lucet validation error")] + LucetValidation(#[from] lucet_validate::Error), + #[error("I/O error")] + IOError(#[from] std::io::Error), + #[error("Error converting cranelift signature to wasm signature")] + SignatureConversion(#[from] SignatureError), + #[error("Wasm validating parser error")] + WasmValidation(#[from] wasmparser::BinaryReaderError), + #[error("Wat input")] + WatInput(#[from] wabt::Error), + // + // Cannot apply #[from] or #[source] to these error types due to missing traits. + #[error("Artifact error: {1}. {0:?}")] + ArtifactError(ArtifactError, String), + #[error("Failure: {1}. {0:?}")] + Failure(failure::Error, String), + #[error("Patcher error: {0:?}")] + Patcher(wasmonkey::WError), + // + // And all the rest + #[error("Function definition error in {symbol}")] + FunctionDefinition { + symbol: String, + #[source] + source: ClifModuleError, + }, + #[error("Function index out of bounds: {0}")] + FunctionIndexError(String), + #[error("Function translation error in {symbol}")] + FunctionTranslation { + symbol: String, + #[source] + source: ClifWasmError, + }, + #[error("Inconsistent state when translating module: global {0} is declared as an import but has no entry in imported_globals")] + GlobalDeclarationError(u32), + #[error("global out of bounds: {0}")] + GlobalIndexError(String), + #[error("global {0} is initialized by referencing another global value, but the referenced global is not an import")] + GlobalInitError(u32), + #[error("v128const type is not supported: {0}")] + GlobalUnsupported(u32), + #[error("Cannot initialize data beyond linear memory's initial size")] + InitData, + #[error("Input error: {0}")] + Input(String), + #[error("Ld error: {0}")] + LdError(String), + #[error("Memory specs: {0}")] + MemorySpecs(String), + #[error("Metadata serializer; start index point to a non-function")] + MetadataSerializer(#[source] ClifModuleError), + #[error("Output function: error writing function {1}")] + OutputFunction(#[source] std::fmt::Error, String), + #[error("Signature error: {0}")] + Signature(String), + #[error("Table index is out of bounds: {0}")] + TableIndexError(String), + #[error("Trap records are present for function {0} but the function does not exist.")] + TrapRecord(String), + #[error("Unsupported: {0}")] Unsupported(String), - - #[fail(display = "{}", _0)] - Other(String), - - #[fail(display = "Unknown error:")] - UnknownKind, + #[error("host machine is not a supported target")] + UnsupportedIsa(#[from] cranelift_codegen::isa::LookupError), } diff --git a/lucetc/src/function.rs b/lucetc/src/function.rs new file mode 100644 index 000000000..74e929501 --- /dev/null +++ b/lucetc/src/function.rs @@ -0,0 +1,506 @@ +use super::runtime::RuntimeFunc; +use crate::decls::ModuleDecls; +use crate::pointer::{NATIVE_POINTER, NATIVE_POINTER_SIZE}; +use crate::table::TABLE_REF_SIZE; +use cranelift_codegen::cursor::FuncCursor; +use cranelift_codegen::entity::EntityRef; +use cranelift_codegen::ir::{self, InstBuilder}; +use cranelift_codegen::isa::TargetFrontendConfig; +use cranelift_frontend::FunctionBuilder; +use cranelift_wasm::{ + FuncEnvironment, FuncIndex, FuncTranslationState, GlobalIndex, GlobalVariable, MemoryIndex, + SignatureIndex, TableIndex, WasmError, WasmResult, +}; +use lucet_module::InstanceRuntimeData; +use memoffset::offset_of; +use std::collections::HashMap; +use wasmparser::Operator; + +pub struct FuncInfo<'a> { + module_decls: &'a ModuleDecls<'a>, + count_instructions: bool, + scope_costs: Vec, + vmctx_value: Option, + global_base_value: Option, + runtime_funcs: HashMap, +} + +impl<'a> FuncInfo<'a> { + pub fn new(module_decls: &'a ModuleDecls<'a>, count_instructions: bool) -> Self { + Self { + module_decls, + count_instructions, + scope_costs: vec![0], + vmctx_value: None, + global_base_value: None, + runtime_funcs: HashMap::new(), + } + } + + pub fn get_vmctx(&mut self, func: &mut ir::Function) -> ir::GlobalValue { + self.vmctx_value.unwrap_or_else(|| { + let vmctx_value = func.create_global_value(ir::GlobalValueData::VMContext); + self.vmctx_value = Some(vmctx_value); + vmctx_value + }) + } + + pub fn get_global_base(&mut self, func: &mut ir::Function) -> ir::GlobalValue { + self.global_base_value.unwrap_or_else(|| { + let vmctx = self.get_vmctx(func); + let global_base_value = func.create_global_value(ir::GlobalValueData::Load { + base: vmctx, + offset: (-(std::mem::size_of::() as i32) + + (offset_of!(InstanceRuntimeData, globals_ptr) as i32)) + .into(), + global_type: ir::types::I64, + readonly: false, + }); + self.global_base_value = Some(global_base_value); + global_base_value + }) + } + + pub fn get_runtime_func( + &mut self, + runtime_func: RuntimeFunc, + func: &mut ir::Function, + ) -> ir::FuncRef { + self.runtime_funcs + .get(&runtime_func) + .cloned() + .unwrap_or_else(|| { + let decl = self + .module_decls + .get_runtime(runtime_func) + .expect("runtime function not available"); + let signature = func.import_signature(decl.signature().to_owned()); + let fref = func.import_function(ir::ExtFuncData { + name: decl.name.into(), + signature, + colocated: false, + }); + self.runtime_funcs.insert(runtime_func, fref); + fref + }) + } + + fn update_instruction_count_instrumentation( + &mut self, + op: &Operator, + builder: &mut FunctionBuilder, + reachable: bool, + ) -> WasmResult<()> { + // So the operation counting works like this: + // record a stack corresponding with the stack of control flow in the wasm function. + // for non-control-flow-affecting instructions, increment the top of the stack. + // for control-flow-affecting operations (If, Else, Unreachable, Call, End, Return, Block, + // Loop, BrIf, CallIndirect), update the wasm instruction counter and: + // * if the operation introduces a new scope (If, Block, Loop), push a new 0 on the + // stack corresponding with that frame. + // * if the operation does not introduce a new scope (Else, Call, CallIndirect, BrIf), + // reset the top of stack to 0 + // * if the operation completes a scope (End), pop the top of the stack and reset the new + // top of stack to 0 + // * this leaves no special behavior for Unreachable and Return. This is acceptable as they + // are always followed by an End and are either about to trap, or return from a function. + // * Unreachable is either the end of VM execution and we are off by one instruction, or, + // is about to dispatch to an exception handler, which we should account for out of band + // anyway (exception dispatch is much more expensive than a single wasm op) + // * Return corresponds to exactly one function call, so we can count it by resetting the + // stack to 1 at return of a function. + + fn flush_counter(environ: &mut FuncInfo, builder: &mut FunctionBuilder) { + if environ.scope_costs.last() == Some(&0) { + return; + } + let instr_count_offset: ir::immediates::Offset32 = + (-(std::mem::size_of::() as i32) + + offset_of!(InstanceRuntimeData, instruction_count) as i32) + .into(); + let vmctx_gv = environ.get_vmctx(builder.func); + let addr = builder.ins().global_value(environ.pointer_type(), vmctx_gv); + let trusted_mem = ir::MemFlags::trusted(); + + // Now insert a sequence of clif that is, functionally: + // + // let instruction_count_ptr: &mut u64 = vmctx.instruction_count; + // let mut instruction_count: u64 = *instruction_count_ptr; + // instruction_count += ; + // *instruction_count_ptr = instruction_count; + + let cur_instr_count = + builder + .ins() + .load(ir::types::I64, trusted_mem, addr, instr_count_offset); + let update_const = builder.ins().iconst( + ir::types::I64, + i64::from(*environ.scope_costs.last().unwrap()), + ); + let new_instr_count = builder.ins().iadd(cur_instr_count, update_const.into()); + builder + .ins() + .store(trusted_mem, new_instr_count, addr, instr_count_offset); + + *environ.scope_costs.last_mut().unwrap() = 0; + }; + + // Only update or flush the counter when the scope is not sealed. + // + // Cranelift dutifully translates the entire wasm body, including dead code, and we can try + // to insert instrumentation for dead code, but Cranelift seals blocks at operations that + // involve control flow away from the current block. So we have to track when operations + // are unreachable and not instrument them, lest we cause a Cranelift panic trying to + // modify sealed basic blocks. + if reachable { + // Update the instruction counter, if necessary + let op_cost = match op { + // Opening a scope is a syntactic operation, and free. + Operator::Block { .. } | + // These do not add counts, see above comment about return/unreachable + Operator::Unreachable | + Operator::Return => 0, + // Call is quick + Operator::Call { .. } => 1, + // but indirect calls take some extra work to validate at runtime + Operator::CallIndirect { .. } => 2, + // Testing for an if involve some overhead, for now say it's also 1 + Operator::If { .. } => 1, + // Else is a fallthrough or alternate case for something that's been tested as `if`, so + // it's already counted + Operator::Else => 0, + // Entering a loop is a syntactic operation, and free. + Operator::Loop { .. } => 0, + // Closing a scope is a syntactic operation, and free. + Operator::End => 0, + // Taking a branch is an operation + Operator::Br { .. } => 1, + // brif might be two operations? + Operator::BrIf { .. } => 1, + // brtable is kind of cpu intensive compared to other wasm ops + Operator::BrTable { .. } => 2, + // nop and drop are free + Operator::Nop | + Operator::Drop => 0, + // everything else, just call it one operation. + _ => 1, + }; + self.scope_costs.last_mut().map(|x| *x += op_cost); + + // apply flushing behavior if applicable + match op { + Operator::Unreachable + | Operator::Return + | Operator::CallIndirect { .. } + | Operator::Call { .. } + | Operator::Block { .. } + | Operator::Loop { .. } + | Operator::If { .. } + | Operator::Else + | Operator::Br { .. } + | Operator::BrIf { .. } + | Operator::BrTable { .. } => { + flush_counter(self, builder); + } + Operator::End => { + // We have to be really careful here to avoid violating a cranelift invariant: + // if the next operation is `End` as well, this end will have marked the block + // finished, and attempting to add instructions to update the instruction counter + // will cause a panic. + // + // The only situation where this can occur is if the last structure in a scope is a + // subscope (the body of a Loop, If, or Else), so we flush the counter entering + // those structures, and guarantee the `End` for their enclosing scope will have a + // counter value of 0. In other cases, we're not at risk of closing a scope leading + // to closing another scope, and it's safe to flush the counter. + // + // An example to help: + // ``` + // block + // i32.const 4 ; counter += 1 + // i32.const -5 ; counter += 1 + // i32.add ; counter += 1 + // block ; flush counter (counter = 3 -> 0), flush here to avoid having + // ; accumulated count at the + // ; final `end` + // i32.const 4 ; counter += 1 + // i32.add ; counter += 1 + // end ; flush counter (counter = 2 -> 0) + // end ; flush counter (counter = 0 -> 0) and is a no-op + // ``` + flush_counter(self, builder); + } + _ => { /* regular operation, do nothing */ } + } + } else { + // just a consistency check - the counter must be 0 when exiting a region of + // unreachable code. If this assertion fails it means we either counted instructions + // we shouldn't (because they're unreachable), or we didn't flush the counter before + // starting to also instrument unreachable instructions (and would have tried to + // overcount) + assert_eq!(*self.scope_costs.last().unwrap(), 0); + } + + // finally, we might have to set up a new counter for a new scope, or fix up counts a bit. + // + // Note that nothing is required for `Else`, because it will have been preceded by an `End` + // to close the "then" arm of its enclosing `If`, so the counter will have already been + // flushed and reset to 0. + match op { + Operator::CallIndirect { .. } | Operator::Call { .. } => { + // only track the expected return if this call was reachable - if the call is not + // reachable, the "called" function won't return! + if reachable { + // add 1 to count the return from the called function + self.scope_costs.last_mut().map(|x| *x = 1); + } + } + Operator::Block { .. } | Operator::Loop { .. } | Operator::If { .. } => { + // opening a scope, which starts having executed zero wasm ops + self.scope_costs.push(0); + } + Operator::End => { + // close the top scope + self.scope_costs.pop(); + } + _ => {} + } + Ok(()) + } +} + +impl<'a> FuncEnvironment for FuncInfo<'a> { + fn target_config(&self) -> TargetFrontendConfig { + self.module_decls.target_config() + } + + fn make_global( + &mut self, + func: &mut ir::Function, + index: GlobalIndex, + ) -> Result { + let global_base = self.get_global_base(func); + let global = self.module_decls.get_global(index).expect("valid global"); + let index = index.as_u32() as i32; + let offset = (index * NATIVE_POINTER_SIZE as i32).into(); + Ok(GlobalVariable::Memory { + gv: global_base, + offset, + ty: global.entity.ty, + }) + } + + fn make_heap( + &mut self, + func: &mut ir::Function, + index: MemoryIndex, + ) -> Result { + assert_eq!(index, MemoryIndex::new(0), "only memory 0 is supported"); + let heap_spec = self.module_decls.get_heap().expect("valid heap"); + let vmctx = self.get_vmctx(func); + Ok(func.create_heap(ir::HeapData { + base: vmctx, + min_size: heap_spec.initial_size.into(), + offset_guard_size: heap_spec.guard_size.into(), + style: ir::HeapStyle::Static { + bound: heap_spec.reserved_size.into(), + }, + index_type: ir::types::I32, + })) + } + + fn make_table( + &mut self, + func: &mut ir::Function, + index: TableIndex, + ) -> Result { + let index_type = ir::types::I64; + let table_decl = self.module_decls.get_table(index).expect("valid table"); + let base_gv = func.create_global_value(ir::GlobalValueData::Symbol { + name: table_decl.contents_name.into(), + offset: 0.into(), + colocated: true, + }); + let tables_list_gv = func.create_global_value(ir::GlobalValueData::Symbol { + name: self.module_decls.get_tables_list_name().as_externalname(), + offset: 0.into(), + colocated: true, + }); + + let table_bound_offset = (TABLE_REF_SIZE as u32) + .checked_mul(index.as_u32()) + .and_then(|entry| entry.checked_add(NATIVE_POINTER_SIZE as u32)) + .ok_or(WasmError::ImplLimitExceeded)?; + + if table_bound_offset > std::i32::MAX as u32 { + return Err(WasmError::ImplLimitExceeded); + } + + let bound_gv = func.create_global_value(ir::GlobalValueData::Load { + base: tables_list_gv, + global_type: index_type, + offset: (table_bound_offset as i32).into(), + readonly: true, + }); + let element_size = ((NATIVE_POINTER_SIZE * 2) as u64).into(); + let min_size = (table_decl.table.minimum as u64).into(); + Ok(func.create_table(ir::TableData { + base_gv, + bound_gv, + element_size, + index_type, + min_size, + })) + } + + fn translate_call_indirect( + &mut self, + mut pos: FuncCursor<'_>, + _table_index: TableIndex, + table: ir::Table, + sig_index: SignatureIndex, + sig_ref: ir::SigRef, + callee: ir::Value, + call_args: &[ir::Value], + ) -> WasmResult { + let callee_u64 = pos.ins().sextend(ir::types::I64, callee); + let table_entry_addr = pos.ins().table_addr(ir::types::I64, table, callee_u64, 0); + // First element at the table entry is the signature index of the function + let table_entry_sig_offset = 0; + let table_entry_sig_ix = pos.ins().load( + ir::types::I64, + ir::MemFlags::trusted(), + table_entry_addr, + table_entry_sig_offset, + ); + + // Translate from the module's non-unique signature space to our internal unique space + let unique_sig_index = self + .module_decls + .get_signature_uid(sig_index) + .expect("signature index must be valid"); + + // Check it against the unique sig_index, trap if wrong + let valid_type = pos.ins().icmp_imm( + ir::condcodes::IntCC::Equal, + table_entry_sig_ix, + unique_sig_index.as_u32() as i64, + ); + pos.ins().trapz(valid_type, ir::TrapCode::BadSignature); + + // Second element at the table entry is the function pointer + let table_entry_fptr_offset = NATIVE_POINTER_SIZE as i32; + let table_entry_fptr = pos.ins().load( + NATIVE_POINTER, + ir::MemFlags::trusted(), + table_entry_addr, + table_entry_fptr_offset, + ); + + let mut args: Vec = Vec::with_capacity(call_args.len() + 1); + args.extend_from_slice(call_args); + args.insert( + 0, + pos.func + .special_param(ir::ArgumentPurpose::VMContext) + .expect("vmctx available"), + ); + + Ok(pos.ins().call_indirect(sig_ref, table_entry_fptr, &args)) + } + + fn make_indirect_sig( + &mut self, + func: &mut ir::Function, + index: SignatureIndex, + ) -> Result { + let sig = self.module_decls.get_signature(index).unwrap().clone(); + Ok(func.import_signature(sig)) + } + + fn make_direct_func( + &mut self, + func: &mut ir::Function, + index: FuncIndex, + ) -> Result { + let unique_index = *self + .module_decls + .info + .function_mapping + .get(index) + .expect("function indices are valid"); + let func_decl = self.module_decls.get_func(unique_index).unwrap(); + let signature = func.import_signature(func_decl.signature.clone()); + let colocated = !func_decl.imported(); + Ok(func.import_function(ir::ExtFuncData { + name: func_decl.name.into(), + signature, + colocated, + })) + } + + fn translate_call( + &mut self, + mut pos: FuncCursor<'_>, + _callee_index: FuncIndex, + callee: ir::FuncRef, + call_args: &[ir::Value], + ) -> WasmResult { + let mut args: Vec = Vec::with_capacity(call_args.len() + 1); + args.extend_from_slice(call_args); + args.insert( + 0, + pos.func + .special_param(ir::ArgumentPurpose::VMContext) + .expect("vmctx available"), + ); + Ok(pos.ins().call(callee, &args)) + } + + fn translate_memory_grow( + &mut self, + mut pos: FuncCursor<'_>, + index: MemoryIndex, + _heap: ir::Heap, + val: ir::Value, + ) -> WasmResult { + assert!(index == MemoryIndex::new(0)); + // TODO memory grow function doesnt take heap index as argument + let mem_grow_func = self.get_runtime_func(RuntimeFunc::MemGrow, &mut pos.func); + let vmctx = pos + .func + .special_param(ir::ArgumentPurpose::VMContext) + .unwrap(); + let inst = pos.ins().call(mem_grow_func, &[vmctx, val]); + Ok(*pos.func.dfg.inst_results(inst).first().unwrap()) + } + + fn translate_memory_size( + &mut self, + mut pos: FuncCursor<'_>, + index: MemoryIndex, + _heap: ir::Heap, + ) -> WasmResult { + assert!(index == MemoryIndex::new(0)); + // TODO memory size function doesnt take heap index as argument + let mem_size_func = self.get_runtime_func(RuntimeFunc::MemSize, &mut pos.func); + let vmctx = pos + .func + .special_param(ir::ArgumentPurpose::VMContext) + .unwrap(); + let inst = pos.ins().call(mem_size_func, &[vmctx]); + Ok(*pos.func.dfg.inst_results(inst).first().unwrap()) + } + + fn before_translate_operator( + &mut self, + op: &Operator, + builder: &mut FunctionBuilder, + state: &FuncTranslationState, + ) -> WasmResult<()> { + if self.count_instructions { + self.update_instruction_count_instrumentation(op, builder, state.reachable())?; + } + Ok(()) + } +} diff --git a/lucetc/src/function_manifest.rs b/lucetc/src/function_manifest.rs new file mode 100644 index 000000000..460f3ff13 --- /dev/null +++ b/lucetc/src/function_manifest.rs @@ -0,0 +1,69 @@ +use crate::error::Error; +use crate::output::write_relocated_slice; +use crate::traps::trap_sym_for_func; +use faerie::{Artifact, Decl}; +use lucet_module::FunctionSpec; +use std::io::Cursor; +use std::mem::size_of; + +pub const FUNCTION_MANIFEST_SYM: &str = "lucet_function_manifest"; + +/// +/// Writes a manifest of functions, with relocations, to the artifact. +/// +pub fn write_function_manifest( + functions: &[(String, FunctionSpec)], + obj: &mut Artifact, +) -> Result<(), Error> { + obj.declare(FUNCTION_MANIFEST_SYM, Decl::data()) + .map_err(|source| { + let message = format!("Manifest error declaring {}", FUNCTION_MANIFEST_SYM); + Error::ArtifactError(source, message) + })?; + + let mut manifest_buf: Cursor> = Cursor::new(Vec::with_capacity( + functions.len() * size_of::(), + )); + + for (fn_name, fn_spec) in functions.iter() { + /* + * This has implicit knowledge of the layout of `FunctionSpec`! + * + * Each iteraction writes out bytes with relocations that will + * result in data forming a valid FunctionSpec when this is loaded. + * + * Because the addresses don't exist until relocations are applied + * when the artifact is loaded, we can't just populate the fields + * and transmute, unfortunately. + */ + // Writes a (ptr, len) pair with relocation for code + write_relocated_slice( + obj, + &mut manifest_buf, + FUNCTION_MANIFEST_SYM, + Some(fn_name), + fn_spec.code_len() as u64, + )?; + // Writes a (ptr, len) pair with relocation for this function's trap table + let trap_sym = trap_sym_for_func(fn_name); + write_relocated_slice( + obj, + &mut manifest_buf, + FUNCTION_MANIFEST_SYM, + if fn_spec.traps_len() > 0 { + Some(&trap_sym) + } else { + None + }, + fn_spec.traps_len() as u64, + )?; + } + + obj.define(FUNCTION_MANIFEST_SYM, manifest_buf.into_inner()) + .map_err(|source| { + let message = format!("Manifest error declaring {}", FUNCTION_MANIFEST_SYM); + Error::ArtifactError(source, message) + })?; + + Ok(()) +} diff --git a/lucetc/src/heap.rs b/lucetc/src/heap.rs new file mode 100644 index 000000000..0e99673d4 --- /dev/null +++ b/lucetc/src/heap.rs @@ -0,0 +1,16 @@ +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct HeapSettings { + pub min_reserved_size: u64, + pub max_reserved_size: u64, + pub guard_size: u64, +} + +impl Default for HeapSettings { + fn default() -> Self { + Self { + min_reserved_size: 4 * 1024 * 1024, + max_reserved_size: 6 * 1024 * 1024 * 1024, + guard_size: 4 * 1024 * 1024, + } + } +} diff --git a/lucetc/src/lib.rs b/lucetc/src/lib.rs old mode 100644 new mode 100755 index 856c1af57..3a1adbcb0 --- a/lucetc/src/lib.rs +++ b/lucetc/src/lib.rs @@ -1,203 +1,426 @@ -pub mod bindings; -pub mod compiler; -pub mod error; -pub mod load; -pub mod patch; -pub mod program; - -use crate::compiler::data::{compile_data_initializers, compile_sparse_page_data}; -use crate::compiler::function::compile_function; -use crate::compiler::globals::compile_global_specs; -use crate::compiler::memory::compile_memory_specs; -use crate::compiler::module_data::compile_module_data; -use crate::compiler::table::compile_table; -use crate::error::{LucetcError, LucetcErrorKind}; -use crate::load::read_module; -use crate::patch::patch_module; -use crate::program::Program; -use failure::{format_err, Error, ResultExt}; -use parity_wasm::elements::Module; -use std::collections::HashMap; -use std::path::{Path, PathBuf}; -use tempfile; +#![deny(bare_trait_objects)] +mod compiler; +mod decls; +mod error; +mod function; +mod function_manifest; +mod heap; +mod load; +mod module; +mod name; +mod output; +mod patch; +mod pointer; +mod runtime; +pub mod signature; +mod sparsedata; +mod stack_probe; +mod table; +mod traps; +mod types; + +use crate::load::read_bytes; pub use crate::{ - bindings::Bindings, - compiler::{Compiler, OptLevel}, - program::memory::HeapSettings, + compiler::{Compiler, CpuFeatures, OptLevel, SpecificFeature, TargetCpu}, + error::Error, + heap::HeapSettings, + load::read_module, + patch::patch_module, }; +pub use lucet_module::bindings::Bindings; +pub use lucet_validate::Validator; +use signature::{PublicKey, SecretKey}; +use std::env; +use std::path::{Path, PathBuf}; +use target_lexicon::Triple; +use tempfile; + +enum LucetcInput { + Bytes(Vec), + Path(PathBuf), +} pub struct Lucetc { - module: Module, - name: String, - bindings: Bindings, + input: LucetcInput, + bindings: Vec, + target: Triple, opt_level: OptLevel, + cpu_features: CpuFeatures, heap: HeapSettings, - builtins_path: Option, + builtins_paths: Vec, + validator: Option, + sk: Option, + pk: Option, + sign: bool, + verify: bool, + count_instructions: bool, } -/* -*/ +pub trait AsLucetc { + fn as_lucetc(&mut self) -> &mut Lucetc; +} -impl Lucetc { - pub fn new>(input: P) -> Result { - let input = input.as_ref(); - let module = read_module(input)?; - let name = String::from( - input - .file_stem() - .ok_or(format_err!("input filename {:?} is not a file", input))? - .to_str() - .ok_or(format_err!("input filename {:?} is not valid utf8", input))?, - ); - Ok(Self { - module, - name, - bindings: Bindings::empty(), - opt_level: OptLevel::default(), - heap: HeapSettings::default(), - builtins_path: None, - }) +impl AsLucetc for Lucetc { + fn as_lucetc(&mut self) -> &mut Lucetc { + self } +} + +pub trait LucetcOpts { + fn bindings(&mut self, bindings: Bindings); + fn with_bindings(self, bindings: Bindings) -> Self; + + fn target(&mut self, target: Triple); + fn with_target(self, target: Triple) -> Self; + + fn opt_level(&mut self, opt_level: OptLevel); + fn with_opt_level(self, opt_level: OptLevel) -> Self; + + fn cpu_features(&mut self, cpu_features: CpuFeatures); + fn with_cpu_features(self, cpu_features: CpuFeatures) -> Self; - pub fn bindings(mut self, bindings: Bindings) -> Result { - self.with_bindings(bindings)?; - Ok(self) + fn builtins>(&mut self, builtins_path: P); + fn with_builtins>(self, builtins_path: P) -> Self; + + fn validator(&mut self, validator: Validator); + fn with_validator(self, validator: Validator) -> Self; + + fn min_reserved_size(&mut self, min_reserved_size: u64); + fn with_min_reserved_size(self, min_reserved_size: u64) -> Self; + + fn max_reserved_size(&mut self, max_reserved_size: u64); + fn with_max_reserved_size(self, max_reserved_size: u64) -> Self; + + /// Set the reserved size exactly. + /// + /// Equivalent to setting the minimum and maximum reserved sizes to the same value. + fn reserved_size(&mut self, reserved_size: u64); + /// Set the reserved size exactly. + /// + /// Equivalent to setting the minimum and maximum reserved sizes to the same value. + fn with_reserved_size(self, reserved_size: u64) -> Self; + + fn guard_size(&mut self, guard_size: u64); + fn with_guard_size(self, guard_size: u64) -> Self; + + fn pk(&mut self, pk: PublicKey); + fn with_pk(self, pk: PublicKey) -> Self; + fn sk(&mut self, sk: SecretKey); + fn with_sk(self, sk: SecretKey) -> Self; + fn verify(&mut self); + fn with_verify(self) -> Self; + fn sign(&mut self); + fn with_sign(self) -> Self; + fn count_instructions(&mut self, enable_count: bool); + fn with_count_instructions(self, enable_count: bool) -> Self; +} + +impl LucetcOpts for T { + fn bindings(&mut self, bindings: Bindings) { + self.as_lucetc().bindings.push(bindings); } - pub fn with_bindings(&mut self, bindings: Bindings) -> Result<(), Error> { - self.bindings.extend(bindings) + + fn with_bindings(mut self, bindings: Bindings) -> Self { + self.bindings(bindings); + self } - pub fn opt_level(mut self, opt_level: OptLevel) -> Self { - self.with_opt_level(opt_level); + fn target(&mut self, target: Triple) { + self.as_lucetc().target = target; + } + + fn with_target(mut self, target: Triple) -> Self { + self.target(target); self } - pub fn with_opt_level(&mut self, opt_level: OptLevel) { - self.opt_level = opt_level; + + fn opt_level(&mut self, opt_level: OptLevel) { + self.as_lucetc().opt_level = opt_level; } - pub fn builtins>(mut self, builtins: P) -> Result { - self.with_builtins(builtins)?; - Ok(self) + fn with_opt_level(mut self, opt_level: OptLevel) -> Self { + self.opt_level(opt_level); + self } - pub fn with_builtins>(&mut self, builtins_path: P) -> Result<(), Error> { - let (newmodule, builtins_map) = patch_module(self.module.clone(), builtins_path)?; - self.module = newmodule; - self.bindings.extend(Bindings::env(builtins_map))?; - Ok(()) + + fn cpu_features(&mut self, cpu_features: CpuFeatures) { + self.as_lucetc().cpu_features = cpu_features; } - pub fn min_reserved_size(mut self, min_reserved_size: u64) -> Self { - self.with_min_reserved_size(min_reserved_size); + fn with_cpu_features(mut self, cpu_features: CpuFeatures) -> Self { + self.cpu_features(cpu_features); self } - pub fn with_min_reserved_size(&mut self, min_reserved_size: u64) { - self.heap.min_reserved_size = min_reserved_size; + + fn builtins>(&mut self, builtins_path: P) { + self.as_lucetc() + .builtins_paths + .push(builtins_path.as_ref().to_owned()); } - pub fn max_reserved_size(mut self, max_reserved_size: u64) -> Self { - self.with_max_reserved_size(max_reserved_size); + fn with_builtins>(mut self, builtins_path: P) -> Self { + self.builtins(builtins_path); self } - pub fn with_max_reserved_size(&mut self, max_reserved_size: u64) { - self.heap.max_reserved_size = max_reserved_size; + + fn validator(&mut self, validator: Validator) { + self.as_lucetc().validator = Some(validator); } - pub fn guard_size(mut self, guard_size: u64) -> Self { - self.with_guard_size(guard_size); + fn with_validator(mut self, validator: Validator) -> Self { + self.validator(validator); self } - pub fn with_guard_size(&mut self, guard_size: u64) { - self.heap.guard_size = guard_size; + + fn min_reserved_size(&mut self, min_reserved_size: u64) { + self.as_lucetc().heap.min_reserved_size = min_reserved_size; } - pub fn object_file>(self, output: P) -> Result<(), Error> { - let prog = Program::new(self.module, self.bindings, self.heap.clone())?; - let comp = compile(&prog, &self.name, self.opt_level)?; + fn with_min_reserved_size(mut self, min_reserved_size: u64) -> Self { + self.min_reserved_size(min_reserved_size); + self + } - let obj = comp.codegen()?; - obj.write(output.as_ref()).context("writing object file")?; + fn max_reserved_size(&mut self, max_reserved_size: u64) { + self.as_lucetc().heap.max_reserved_size = max_reserved_size; + } - Ok(()) + fn with_max_reserved_size(mut self, max_reserved_size: u64) -> Self { + self.max_reserved_size(max_reserved_size); + self + } + + fn reserved_size(&mut self, reserved_size: u64) { + self.as_lucetc().heap.min_reserved_size = reserved_size; + self.as_lucetc().heap.max_reserved_size = reserved_size; + } + + fn with_reserved_size(mut self, reserved_size: u64) -> Self { + self.reserved_size(reserved_size); + self + } + + fn guard_size(&mut self, guard_size: u64) { + self.as_lucetc().heap.guard_size = guard_size; + } + + fn with_guard_size(mut self, guard_size: u64) -> Self { + self.guard_size(guard_size); + self } - pub fn clif_ir>(self, output: P) -> Result<(), Error> { - let (module, builtins_map) = if let Some(ref builtins_path) = self.builtins_path { - patch_module(self.module, builtins_path)? - } else { - (self.module, HashMap::new()) + fn pk(&mut self, pk: PublicKey) { + self.as_lucetc().pk = Some(pk); + } + + fn with_pk(mut self, pk: PublicKey) -> Self { + self.pk(pk); + self + } + + fn sk(&mut self, sk: SecretKey) { + self.as_lucetc().sk = Some(sk); + } + + fn with_sk(mut self, sk: SecretKey) -> Self { + self.sk(sk); + self + } + + fn verify(&mut self) { + self.as_lucetc().verify = true; + } + + fn with_verify(mut self) -> Self { + self.verify(); + self + } + + fn sign(&mut self) { + self.as_lucetc().sign = true; + } + + fn with_sign(mut self) -> Self { + self.sign(); + self + } + + fn count_instructions(&mut self, count_instructions: bool) { + self.as_lucetc().count_instructions = count_instructions; + } + + fn with_count_instructions(mut self, count_instructions: bool) -> Self { + self.count_instructions(count_instructions); + self + } +} + +impl Lucetc { + pub fn new>(input: P) -> Self { + let input = input.as_ref(); + Self { + input: LucetcInput::Path(input.to_owned()), + bindings: vec![], + target: Triple::host(), + opt_level: OptLevel::default(), + cpu_features: CpuFeatures::default(), + heap: HeapSettings::default(), + builtins_paths: vec![], + validator: None, + pk: None, + sk: None, + sign: false, + verify: false, + count_instructions: false, + } + } + + pub fn try_from_bytes>(bytes: B) -> Result { + let input = read_bytes(bytes.as_ref().to_vec())?; + Ok(Self { + input: LucetcInput::Bytes(input), + bindings: vec![], + target: Triple::host(), + opt_level: OptLevel::default(), + cpu_features: CpuFeatures::default(), + heap: HeapSettings::default(), + builtins_paths: vec![], + validator: None, + pk: None, + sk: None, + sign: false, + verify: false, + count_instructions: false, + }) + } + + fn build(&self) -> Result<(Vec, Bindings), Error> { + use parity_wasm::elements::{deserialize_buffer, serialize}; + + let mut builtins_bindings = vec![]; + let mut module_binary = match &self.input { + LucetcInput::Bytes(bytes) => bytes.clone(), + LucetcInput::Path(path) => read_module(&path, &self.pk, self.verify)?, }; - let mut bindings = self.bindings.clone(); - bindings.extend(Bindings::env(builtins_map))?; + if !self.builtins_paths.is_empty() { + let mut module = deserialize_buffer(&module_binary)?; + for builtins in self.builtins_paths.iter() { + let (newmodule, builtins_map) = patch_module(module, builtins)?; + module = newmodule; + builtins_bindings.push(Bindings::env(builtins_map)); + } + module_binary = serialize(module)?; + } - let prog = Program::new(module, bindings, self.heap.clone())?; - let comp = compile(&prog, &self.name, self.opt_level)?; + let mut bindings = Bindings::empty(); + + for binding in builtins_bindings.iter().chain(self.bindings.iter()) { + bindings.extend(binding)?; + } + + Ok((module_binary, bindings)) + } - comp.cranelift_funcs() - .write(&output) - .context("writing clif file")?; + pub fn object_file>(&self, output: P) -> Result<(), Error> { + let (module_contents, bindings) = self.build()?; + + let compiler = Compiler::new( + &module_contents, + self.target.clone(), + self.opt_level, + self.cpu_features.clone(), + &bindings, + self.heap.clone(), + self.count_instructions, + &self.validator, + )?; + let obj = compiler.object_file()?; + obj.write(output.as_ref())?; + + Ok(()) + } + + pub fn clif_ir>(&self, output: P) -> Result<(), Error> { + let (module_contents, bindings) = self.build()?; + + let compiler = Compiler::new( + &module_contents, + self.target.clone(), + self.opt_level, + self.cpu_features.clone(), + &bindings, + self.heap.clone(), + self.count_instructions, + &self.validator, + )?; + + compiler.cranelift_funcs()?.write(&output)?; Ok(()) } - pub fn shared_object_file>(self, output: P) -> Result<(), Error> { + pub fn shared_object_file>(&self, output: P) -> Result<(), Error> { let dir = tempfile::Builder::new().prefix("lucetc").tempdir()?; let objpath = dir.path().join("tmp.o"); self.object_file(objpath.clone())?; - link_so(objpath, output)?; + link_so(objpath, &self.target, &output)?; + if self.sign { + let sk = self.sk.as_ref().ok_or(Error::Signature( + "signing requires a secret key".to_string(), + ))?; + signature::sign_module(&output, sk)?; + } Ok(()) } } -fn link_so(objpath: P, sopath: Q) -> Result<(), Error> +const LD_DEFAULT: &str = "ld"; + +fn link_so(objpath: P, target: &Triple, sopath: Q) -> Result<(), Error> where P: AsRef, Q: AsRef, { use std::process::Command; - let mut cmd_ld = Command::new("ld"); + let mut cmd_ld = Command::new(env::var("LD").unwrap_or(LD_DEFAULT.into())); cmd_ld.arg(objpath.as_ref()); - cmd_ld.arg("-shared"); + let env_ldflags = env::var("LDFLAGS").unwrap_or_else(|_| ldflags_default(target)); + for flag in env_ldflags.split_whitespace() { + cmd_ld.arg(flag); + } cmd_ld.arg("-o"); cmd_ld.arg(sopath.as_ref()); - let run_ld = cmd_ld - .output() - .context(format_err!("running ld on {:?}", objpath.as_ref()))?; + let run_ld = cmd_ld.output()?; if !run_ld.status.success() { - Err(format_err!( + let message = format!( "ld of {} failed: {}", objpath.as_ref().to_str().unwrap(), String::from_utf8_lossy(&run_ld.stderr) - ))?; + ); + Err(Error::LdError(message))?; } Ok(()) } -pub fn compile<'p>( - program: &'p Program, - name: &str, - opt_level: OptLevel, -) -> Result, LucetcError> { - let mut compiler = Compiler::new(name.to_owned(), &program, opt_level)?; +fn ldflags_default(target: &Triple) -> String { + use target_lexicon::OperatingSystem; - compile_data_initializers(&mut compiler).context(LucetcErrorKind::DataInitializers)?; - compile_sparse_page_data(&mut compiler).context(LucetcErrorKind::DataInitializers)?; - compile_memory_specs(&mut compiler).context(LucetcErrorKind::MemorySpecs)?; - compile_global_specs(&mut compiler).context(LucetcErrorKind::GlobalSpecs)?; - compile_module_data(&mut compiler).context(LucetcErrorKind::ModuleData)?; + match target.operating_system { + OperatingSystem::Linux => "-shared", + OperatingSystem::MacOSX { .. } => { + "-dylib -dead_strip -export_dynamic -undefined dynamic_lookup" + } + _ => panic!( + "Cannot determine default flags for {}. - for function in program.defined_functions() { - let body = program.function_body(&function); - compile_function(&mut compiler, &function, body) - .context(LucetcErrorKind::Function(function.symbol().to_owned()))?; - } - for table in program.tables() { - compile_table(&mut compiler, &table) - .context(LucetcErrorKind::Table(table.symbol().to_owned()))?; +Please define the LDFLAGS environment variable with the necessary command-line +flags for generating shared libraries.", + Triple::host() + ), } - - Ok(compiler) + .into() } diff --git a/lucetc/src/load.rs b/lucetc/src/load.rs index 6b49365e5..e86f58bea 100644 --- a/lucetc/src/load.rs +++ b/lucetc/src/load.rs @@ -1,20 +1,56 @@ -use failure::*; -use parity_wasm::deserialize_buffer; -pub use parity_wasm::elements::Module; +use crate::error::Error; +use crate::signature::{self, PublicKey}; use std::fs::File; use std::io::Read; use std::path::Path; -use wabt::wat2wasm; +use wabt::{wat2wasm, ErrorKind}; -pub fn read_module>(path: P) -> Result { +pub fn read_module>( + path: P, + pk: &Option, + verify: bool, +) -> Result, Error> { + let signature_box = if verify { + Some(signature::signature_box_for_module_path(&path)?) + } else { + None + }; let contents = read_to_u8s(path)?; - let wasm = if wasm_preamble(&contents) { - contents + if let Some(signature_box) = signature_box { + signature::verify_source_code( + &contents, + &signature_box, + pk.as_ref() + .ok_or(Error::Signature("public key is missing".to_string()))?, + )?; + } + read_bytes(contents) +} + +pub fn read_bytes(bytes: Vec) -> Result, Error> { + let converted = if wasm_preamble(&bytes) { + bytes } else { - wat2wasm(contents)? + wat2wasm(bytes).map_err(|err| { + use std::error::Error; + let mut result = String::from("wat2wasm error: "); + result.push_str(err.description()); + match unsafe { std::mem::transmute::(err) } { + ErrorKind::Parse(msg) | + // this shouldn't be reachable - we're going the other way + ErrorKind::Deserialize(msg) | + // not sure how this error comes up + ErrorKind::ResolveNames(msg) | + ErrorKind::Validate(msg) => { + result.push_str(":\n"); + result.push_str(&msg); + }, + _ => { } + }; + crate::error::Error::Input(result) + })? }; - let module_res = deserialize_buffer(&wasm); - module_res.map_err(|e| format_err!("deserializing wasm module: {}", e)) + Ok(converted) } pub fn read_to_u8s>(path: P) -> Result, Error> { diff --git a/lucetc/src/main.rs b/lucetc/src/main.rs deleted file mode 100644 index 28f116c21..000000000 --- a/lucetc/src/main.rs +++ /dev/null @@ -1,71 +0,0 @@ -mod options; - -use crate::options::{CodegenOutput, Options}; -use failure::{format_err, Error, ResultExt}; -use log::info; -use lucetc::{Bindings, Lucetc}; - -use std::io::{self, Write}; -use std::path::PathBuf; -use std::process; - -fn main() { - env_logger::init(); - - let opts = Options::get().unwrap(); - - if let Err(err) = run(&opts) { - let mut msg = format!("{:?}", err); - if !msg.ends_with('\n') { - msg.push('\n'); - } - io::stderr().write(msg.as_bytes()).unwrap(); - process::exit(1); - } -} - -pub fn run(opts: &Options) -> Result<(), Error> { - info!("lucetc {:?}", opts); - - let input = &match opts.input.len() { - 0 => Err(format_err!("must provide at least one input")), - 1 => Ok(opts.input[0].clone()), - _ => Err(format_err!("provided too many inputs: {:?}", opts.input)), - }?; - - let mut bindings = Bindings::empty(); - for file in opts.binding_files.iter() { - let file_bindings = - Bindings::from_file(file).context(format!("bindings file {:?}", file))?; - bindings - .extend(file_bindings) - .context(format!("adding bindings from {:?}", file))?; - } - - let mut c = Lucetc::new(PathBuf::from(input))? - .bindings(bindings)? - .opt_level(opts.opt_level); - - if let Some(ref builtins) = opts.builtins_path { - c.with_builtins(builtins)?; - } - - if let Some(min_reserved_size) = opts.min_reserved_size { - c.with_min_reserved_size(min_reserved_size); - } - - if let Some(max_reserved_size) = opts.max_reserved_size { - c.with_max_reserved_size(max_reserved_size); - } - - if let Some(guard_size) = opts.guard_size { - c.with_guard_size(guard_size); - } - - match opts.codegen { - CodegenOutput::Obj => c.object_file(&opts.output)?, - CodegenOutput::SharedObj => c.shared_object_file(&opts.output)?, - CodegenOutput::Clif => c.clif_ir(&opts.output)?, - } - Ok(()) -} diff --git a/lucetc/src/module.rs b/lucetc/src/module.rs new file mode 100644 index 000000000..59a3101e3 --- /dev/null +++ b/lucetc/src/module.rs @@ -0,0 +1,413 @@ +//! Implements ModuleEnvironment for cranelift-wasm. Code derived from cranelift-wasm/environ/dummy.rs +use crate::error::Error; +use crate::pointer::NATIVE_POINTER; +use cranelift_codegen::entity::{entity_impl, EntityRef, PrimaryMap, SecondaryMap}; +use cranelift_codegen::ir; +use cranelift_codegen::isa::TargetFrontendConfig; +use cranelift_wasm::{ + FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, ModuleEnvironment, ModuleTranslationState, + SignatureIndex, Table, TableElementType, TableIndex, WasmResult, +}; +use lucet_module::UniqueSignatureIndex; +use std::collections::{hash_map::Entry, HashMap}; + +/// UniqueFuncIndex names a function after merging duplicate function declarations to a single +/// identifier, whereas FuncIndex is maintained by Cranelift and may have multiple indices referring +/// to a single function in the resulting artifact. +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] +pub struct UniqueFuncIndex(u32); +entity_impl!(UniqueFuncIndex); + +#[derive(Debug, Clone)] +pub struct Exportable<'a, T> { + pub entity: T, + pub export_names: Vec<&'a str>, +} + +impl<'a, T> Exportable<'a, T> { + pub fn new(entity: T) -> Self { + Self { + entity, + export_names: Vec::new(), + } + } + pub fn push_export(&mut self, name: &'a str) { + self.export_names.push(name); + } +} + +#[derive(Debug, Clone)] +pub struct TableElems { + pub base: Option, + pub offset: usize, + pub elements: Box<[UniqueFuncIndex]>, +} + +#[derive(Debug, Clone)] +pub struct DataInitializer<'a> { + pub base: Option, + pub offset: usize, + pub data: &'a [u8], +} + +pub struct ModuleInfo<'a> { + /// Target description used for codegen + pub target_config: TargetFrontendConfig, + /// This mapping lets us merge duplicate types (permitted by the wasm spec) as they're + /// declared. + pub signature_mapping: PrimaryMap, + /// Provided by `declare_signature` + pub signatures: PrimaryMap, + /// Provided by `declare_func_import` + pub imported_funcs: PrimaryMap, + /// Provided by `declare_global_import` + pub imported_globals: PrimaryMap, + /// Provided by `declare_table_import` + pub imported_tables: PrimaryMap, + /// Provided by `declare_memory_import` + pub imported_memories: PrimaryMap, + /// This mapping lets us merge duplicate functions (for example, multiple import declarations) + /// as they're declared. + pub function_mapping: PrimaryMap, + /// Function signatures: imported and local + pub functions: PrimaryMap>, + /// Function names. + pub function_names: SecondaryMap, + /// Provided by `declare_table` + pub tables: PrimaryMap>, + /// Provided by `declare_memory` + pub memories: PrimaryMap>, + /// Provided by `declare_global` + pub globals: PrimaryMap>, + /// Provided by `declare_start_func` + pub start_func: Option, + + /// Function bodies: local only + pub function_bodies: HashMap, + + /// Table elements: local only + pub table_elems: HashMap>, + + /// Data initializers: local only + pub data_initializers: HashMap>>, +} + +impl<'a> ModuleInfo<'a> { + pub fn new(target_config: TargetFrontendConfig) -> Self { + Self { + target_config, + signature_mapping: PrimaryMap::new(), + signatures: PrimaryMap::new(), + imported_funcs: PrimaryMap::new(), + imported_globals: PrimaryMap::new(), + imported_tables: PrimaryMap::new(), + imported_memories: PrimaryMap::new(), + function_mapping: PrimaryMap::new(), + functions: PrimaryMap::new(), + function_names: SecondaryMap::new(), + tables: PrimaryMap::new(), + memories: PrimaryMap::new(), + globals: PrimaryMap::new(), + start_func: None, + function_bodies: HashMap::new(), + table_elems: HashMap::new(), + data_initializers: HashMap::new(), + } + } + + pub fn signature_for_function(&self, func_index: UniqueFuncIndex) -> &ir::Signature { + // UniqueFuncIndex are valid (or the caller has very bad data) + let sigidx = self.functions.get(func_index).unwrap().entity; + + self.signature_by_id(sigidx) + } + + pub fn signature_by_id(&self, sig_idx: SignatureIndex) -> &ir::Signature { + // All signatures map to some unique signature index + let unique_sig_idx = self.signature_mapping.get(sig_idx).unwrap(); + // Unique signature indices are valid (or we're in some deeply bad state) + self.signatures.get(*unique_sig_idx).unwrap() + } + + pub fn declare_func_with_sig( + &mut self, + sig: ir::Signature, + ) -> Result<(UniqueFuncIndex, SignatureIndex), Error> { + let new_sigidx = SignatureIndex::from_u32(self.signature_mapping.len() as u32); + self.declare_signature(sig)?; + let new_funcidx = UniqueFuncIndex::from_u32(self.functions.len() as u32); + self.declare_func_type(new_sigidx)?; + Ok((new_funcidx, new_sigidx)) + } +} + +impl<'a> ModuleEnvironment<'a> for ModuleInfo<'a> { + fn target_config(&self) -> TargetFrontendConfig { + self.target_config + } + + fn declare_signature(&mut self, mut sig: ir::Signature) -> WasmResult<()> { + sig.params.insert( + 0, + ir::AbiParam::special(NATIVE_POINTER, ir::ArgumentPurpose::VMContext), + ); + + let match_key = self + .signatures + .iter() + .find(|(_, v)| *v == &sig) + .map(|(key, _)| key) + .unwrap_or_else(|| { + let lucet_sig_ix = UniqueSignatureIndex::from_u32(self.signatures.len() as u32); + self.signatures.push(sig); + lucet_sig_ix + }); + + self.signature_mapping.push(match_key); + Ok(()) + } + + fn declare_func_import( + &mut self, + sig_index: SignatureIndex, + module: &'a str, + field: &'a str, + ) -> WasmResult<()> { + debug_assert_eq!( + self.functions.len(), + self.imported_funcs.len(), + "import functions are declared first" + ); + + let unique_fn_index = self + .imported_funcs + .iter() + .find(|(_, v)| *v == &(module, field)) + .map(|(key, _)| key) + .unwrap_or_else(|| { + self.functions.push(Exportable::new(sig_index)); + self.imported_funcs.push((module, field)); + UniqueFuncIndex::from_u32(self.functions.len() as u32 - 1) + }); + + self.function_mapping.push(unique_fn_index); + Ok(()) + } + + fn declare_global_import( + &mut self, + global: Global, + module: &'a str, + field: &'a str, + ) -> WasmResult<()> { + debug_assert_eq!( + self.globals.len(), + self.imported_globals.len(), + "import globals are declared first" + ); + self.globals.push(Exportable::new(global)); + self.imported_globals.push((module, field)); + Ok(()) + } + + fn declare_table_import( + &mut self, + table: Table, + module: &'a str, + field: &'a str, + ) -> WasmResult<()> { + debug_assert_eq!( + self.tables.len(), + self.imported_tables.len(), + "import tables are declared first" + ); + self.tables.push(Exportable::new(table)); + self.imported_tables.push((module, field)); + Ok(()) + } + + fn declare_memory_import( + &mut self, + memory: Memory, + module: &'a str, + field: &'a str, + ) -> WasmResult<()> { + debug_assert_eq!( + self.memories.len(), + self.imported_memories.len(), + "import memories are declared first" + ); + self.data_initializers + .insert(MemoryIndex::new(self.memories.len()), vec![]); + self.memories.push(Exportable::new(memory)); + self.imported_memories.push((module, field)); + Ok(()) + } + + fn declare_func_type(&mut self, sig_index: SignatureIndex) -> WasmResult<()> { + self.functions.push(Exportable::new(sig_index)); + self.function_mapping + .push(UniqueFuncIndex::from_u32(self.functions.len() as u32 - 1)); + Ok(()) + } + + fn declare_table(&mut self, table: Table) -> WasmResult<()> { + self.table_elems + .insert(TableIndex::new(self.tables.len()), vec![]); + self.tables.push(Exportable::new(table)); + Ok(()) + } + + fn declare_memory(&mut self, memory: Memory) -> WasmResult<()> { + self.data_initializers + .insert(MemoryIndex::new(self.memories.len()), vec![]); + self.memories.push(Exportable::new(memory)); + Ok(()) + } + + fn declare_global(&mut self, global: Global) -> WasmResult<()> { + self.globals.push(Exportable::new(global)); + Ok(()) + } + + fn declare_func_export(&mut self, func_index: FuncIndex, name: &'a str) -> WasmResult<()> { + let unique_func_index = *self + .function_mapping + .get(func_index) + .expect("function indices are valid"); + self.functions + .get_mut(unique_func_index) + .expect("export of declared function") + .push_export(name); + Ok(()) + } + + fn declare_table_export(&mut self, table_index: TableIndex, name: &'a str) -> WasmResult<()> { + self.tables + .get_mut(table_index) + .expect("export of declared table") + .push_export(name); + Ok(()) + } + + fn declare_memory_export( + &mut self, + memory_index: MemoryIndex, + name: &'a str, + ) -> WasmResult<()> { + self.memories + .get_mut(memory_index) + .expect("export of declared memory") + .push_export(name); + Ok(()) + } + + fn declare_global_export( + &mut self, + global_index: GlobalIndex, + name: &'a str, + ) -> WasmResult<()> { + self.globals + .get_mut(global_index) + .expect("export of declared global") + .push_export(name); + Ok(()) + } + + fn declare_start_func(&mut self, func_index: FuncIndex) -> WasmResult<()> { + let unique_func_index = *self + .function_mapping + .get(func_index) + .expect("function indices are valid"); + debug_assert!( + self.start_func.is_none(), + "start func can only be defined once" + ); + self.start_func = Some(unique_func_index); + Ok(()) + } + + fn define_function_body( + &mut self, + _module_translation_state: &ModuleTranslationState, + body_bytes: &'a [u8], + body_offset: usize, + ) -> WasmResult<()> { + let func_index = + UniqueFuncIndex::new(self.imported_funcs.len() + self.function_bodies.len()); + self.function_bodies + .insert(func_index, (body_bytes, body_offset)); + Ok(()) + } + + fn declare_table_elements( + &mut self, + table_index: TableIndex, + base: Option, + offset: usize, + elements: Box<[FuncIndex]>, + ) -> WasmResult<()> { + let elements_vec: Vec = elements.into(); + let uniquified_elements = elements_vec + .into_iter() + .map(|fn_idx| { + *self + .function_mapping + .get(fn_idx) + .expect("function indices are valid") + }) + .collect(); + let table_elems = TableElems { + base, + offset, + elements: uniquified_elements, + }; + match self.table_elems.entry(table_index) { + Entry::Occupied(mut occ) => occ.get_mut().push(table_elems), + Entry::Vacant(vac) => { + if self.tables.len() == 0 && table_index == TableIndex::new(0) { + let table = Table { + ty: TableElementType::Func, + minimum: 0, + maximum: None, + }; + self.tables.push(Exportable::new(table)); + vac.insert(vec![table_elems]); + } else { + panic!("creation of elements for undeclared table! only table 0 is implicitly declared") + // Do we implicitly declare them all???? i sure hope not + } + } + } + Ok(()) + } + + fn declare_data_initialization( + &mut self, + memory_index: MemoryIndex, + base: Option, + offset: usize, + data: &'a [u8], + ) -> WasmResult<()> { + let data_init = DataInitializer { base, offset, data }; + match self.data_initializers.entry(memory_index) { + Entry::Occupied(mut occ) => { + occ.get_mut().push(data_init); + } + Entry::Vacant(_) => panic!( + "data initializer for undeclared memory {:?}: {:?}", + memory_index, data_init + ), + } + Ok(()) + } + + fn declare_func_name(&mut self, func_index: FuncIndex, name: &'a str) -> WasmResult<()> { + let unique_func_index = *self + .function_mapping + .get(func_index) + .expect("function indices are valid"); + self.function_names[unique_func_index] = name; + Ok(()) + } +} diff --git a/lucetc/src/compiler/name.rs b/lucetc/src/name.rs similarity index 84% rename from lucetc/src/compiler/name.rs rename to lucetc/src/name.rs index cfcc24808..8238cf3ea 100644 --- a/lucetc/src/compiler/name.rs +++ b/lucetc/src/name.rs @@ -27,19 +27,23 @@ impl Name { &self.symbol } - pub fn into_funcid(&self) -> Option { + pub fn as_funcid(&self) -> Option { match self.id { FuncOrDataId::Func(id) => Some(id), FuncOrDataId::Data(_) => None, } } - pub fn into_dataid(&self) -> Option { + pub fn as_dataid(&self) -> Option { match self.id { FuncOrDataId::Data(id) => Some(id), FuncOrDataId::Func(_) => None, } } + + pub fn as_externalname(&self) -> ExternalName { + ExternalName::from(self.id) + } } impl From for ExternalName { diff --git a/lucetc/src/options.rs b/lucetc/src/options.rs deleted file mode 100644 index 46ade8c18..000000000 --- a/lucetc/src/options.rs +++ /dev/null @@ -1,190 +0,0 @@ -use clap::{App, Arg, ArgMatches}; -use failure::Error; -use lucetc::{HeapSettings, OptLevel}; -use std::path::PathBuf; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum CodegenOutput { - Clif, - Obj, - SharedObj, -} - -fn parse_humansized(desc: &str) -> Result { - use human_size::{Byte, ParsingError, Size, SpecificSize}; - match desc.parse::() { - Ok(s) => { - let bytes: SpecificSize = s.into(); - Ok(bytes.value() as u64) - } - Err(ParsingError::MissingMultiple) => Ok(desc.parse::()?), - Err(e) => Err(e)?, - } -} - -fn humansized(bytes: u64) -> String { - use human_size::{Byte, Mebibyte, SpecificSize}; - let bytes = SpecificSize::new(bytes as f64, Byte).expect("bytes"); - let mb: SpecificSize = bytes.into(); - mb.to_string() -} - -#[derive(Debug)] -pub struct Options { - pub output: PathBuf, - pub input: Vec, - pub codegen: CodegenOutput, - pub binding_files: Vec, - pub builtins_path: Option, - pub min_reserved_size: Option, - pub max_reserved_size: Option, - pub guard_size: Option, - pub opt_level: OptLevel, -} - -impl Options { - pub fn from_args(m: &ArgMatches) -> Result { - let input: Vec = m - .values_of("input") - .unwrap_or_default() - .map(PathBuf::from) - .collect(); - - let output = PathBuf::from(m.value_of("output").unwrap_or("a.out")); - - let binding_files: Vec = m - .values_of("bindings") - .unwrap_or_default() - .map(PathBuf::from) - .collect(); - - let codegen = match m.value_of("emit") { - None => CodegenOutput::SharedObj, - Some("clif") => CodegenOutput::Clif, - Some("obj") => CodegenOutput::Obj, - Some("so") => CodegenOutput::SharedObj, - Some(_) => panic!("unknown value for emit"), - }; - - let builtins_path = m.value_of("builtins").map(PathBuf::from); - - let min_reserved_size = if let Some(min_reserved_str) = m.value_of("min_reserved_size") { - Some(parse_humansized(min_reserved_str)?) - } else { - None - }; - - let max_reserved_size = if let Some(max_reserved_str) = m.value_of("max_reserved_size") { - Some(parse_humansized(max_reserved_str)?) - } else { - None - }; - - let guard_size = if let Some(guard_str) = m.value_of("guard_size") { - Some(parse_humansized(guard_str)?) - } else { - None - }; - - let opt_level = match m.value_of("opt_level") { - None => OptLevel::Default, - Some("default") => OptLevel::Default, - Some("best") => OptLevel::Best, - Some("fastest") => OptLevel::Fastest, - Some(_) => panic!("unknown value for opt-level"), - }; - - Ok(Options { - output, - input, - codegen, - binding_files, - builtins_path, - min_reserved_size, - max_reserved_size, - guard_size, - opt_level, - }) - } - pub fn get() -> Result { - let m = App::new("lucetc") - .arg( - Arg::with_name("precious") - .long("--precious") - .takes_value(true) - .help("directory to keep intermediate build artifacts in"), - ) - .arg( - Arg::with_name("emit") - .long("emit") - .takes_value(true) - .possible_values(&["obj", "so", "clif"]) - .help("type of code to generate (default: so)"), - ) - .arg( - Arg::with_name("output") - .short("o") - .long("output") - .takes_value(true) - .multiple(false) - .help("output destination, defaults to a.out if unspecified"), - ) - .arg( - Arg::with_name("bindings") - .long("--bindings") - .takes_value(true) - .multiple(true) - .number_of_values(1) - .help("path to bindings json file"), - ) - .arg( - Arg::with_name("min_reserved_size") - .long("--min-reserved-size") - .takes_value(true) - .multiple(false) - .help(&format!( - "minimum size of usable linear memory region. must be multiple of 4k. default: {}", - humansized(HeapSettings::default().min_reserved_size) - )), - ) - .arg( - Arg::with_name("max_reserved_size") - .long("--max-reserved-size") - .takes_value(true) - .multiple(false) - .help("maximum size of usable linear memory region. must be multiple of 4k. default: 4 GiB"), - ) - .arg( - Arg::with_name("guard_size") - .long("--guard-size") - .takes_value(true) - .multiple(false) - .help(&format!( - "size of linear memory guard. must be multiple of 4k. default: {}", - humansized(HeapSettings::default().guard_size) - )), - ) - .arg( - Arg::with_name("builtins") - .long("--builtins") - .takes_value(true) - .help("builtins file"), - ) - .arg( - Arg::with_name("input") - .multiple(false) - .required(true) - .help("input file"), - ) - .arg( - Arg::with_name("opt_level") - .long("--opt-level") - .takes_value(true) - .possible_values(&["default", "fastest", "best"]) - .help("optimization level (default: 'default')"), - ) - .get_matches(); - - Self::from_args(&m) - } -} diff --git a/lucetc/src/output.rs b/lucetc/src/output.rs new file mode 100644 index 000000000..27ef1aecd --- /dev/null +++ b/lucetc/src/output.rs @@ -0,0 +1,247 @@ +use crate::error::Error; +use crate::function_manifest::{write_function_manifest, FUNCTION_MANIFEST_SYM}; +use crate::name::Name; +use crate::stack_probe; +use crate::table::{link_tables, TABLE_SYM}; +use crate::traps::write_trap_tables; +use byteorder::{LittleEndian, WriteBytesExt}; +use cranelift_codegen::{ir, isa}; +use cranelift_faerie::FaerieProduct; +use faerie::{Artifact, Decl, Link}; +use lucet_module::{ + FunctionSpec, SerializedModule, VersionInfo, LUCET_MODULE_SYM, MODULE_DATA_SYM, +}; +use std::collections::HashMap; +use std::fs::File; +use std::io::{Cursor, Write}; +use std::path::Path; +use target_lexicon::BinaryFormat; + +pub struct CraneliftFuncs { + funcs: HashMap, + isa: Box, +} + +impl CraneliftFuncs { + pub fn new(funcs: HashMap, isa: Box) -> Self { + Self { funcs, isa } + } + /// This outputs a .clif file + pub fn write>(&self, path: P) -> Result<(), Error> { + use cranelift_codegen::write_function; + let mut buffer = String::new(); + for (n, func) in self.funcs.iter() { + buffer.push_str(&format!("; {}\n", n.symbol())); + write_function(&mut buffer, func, &Some(self.isa.as_ref()).into()).map_err(|e| { + let message = format!("{:?}", n); + Error::OutputFunction(e, message) + })? + } + let mut file = File::create(path)?; + file.write_all(buffer.as_bytes())?; + Ok(()) + } +} + +pub struct ObjectFile { + artifact: Artifact, +} +impl ObjectFile { + pub fn new( + mut product: FaerieProduct, + module_data_len: usize, + mut function_manifest: Vec<(String, FunctionSpec)>, + table_manifest: Vec, + ) -> Result { + stack_probe::declare_and_define(&mut product)?; + + // stack_probe::declare_and_define never exists as clif, and as a result never exists a + // cranelift-compiled function. As a result, the declared length of the stack probe's + // "code" is 0. This is incorrect, and must be fixed up before writing out the function + // manifest. + + // because the stack probe is the last declared function... + let last_idx = function_manifest.len() - 1; + let stack_probe_entry = function_manifest + .get_mut(last_idx) + .expect("function manifest has entries"); + debug_assert!(stack_probe_entry.0 == stack_probe::STACK_PROBE_SYM); + debug_assert!(stack_probe_entry.1.code_len() == 0); + std::mem::swap( + &mut stack_probe_entry.1, + &mut FunctionSpec::new( + 0, // there is no real address for the function until written to an object file + stack_probe::STACK_PROBE_BINARY.len() as u32, + 0, + 0, // fix up this FunctionSpec with trap info like any other + ), + ); + + let trap_manifest = &product + .trap_manifest + .expect("trap manifest will be present"); + + // Now that we have trap information, we can fix up FunctionSpec entries to have + // correct `trap_length` values + let mut function_map: HashMap = HashMap::new(); + for (i, (name, _)) in function_manifest.iter().enumerate() { + function_map.insert(name.clone(), i as u32); + } + + for sink in trap_manifest.sinks.iter() { + if let Some(idx) = function_map.get(&sink.name) { + let (_, fn_spec) = &mut function_manifest + .get_mut(*idx as usize) + .expect("index is valid"); + + std::mem::replace::( + fn_spec, + FunctionSpec::new(0, fn_spec.code_len(), 0, sink.sites.len() as u64), + ); + } else { + return Err(Error::TrapRecord(sink.name.to_owned())); + } + } + + write_trap_tables(trap_manifest, &mut product.artifact)?; + write_function_manifest(function_manifest.as_slice(), &mut product.artifact)?; + link_tables(table_manifest.as_slice(), &mut product.artifact)?; + + // And now write out the actual structure tying together all the data in this module. + write_module( + module_data_len, + table_manifest.len(), + function_manifest.len(), + &mut product.artifact, + )?; + + Ok(Self { + artifact: product.artifact, + }) + } + + pub fn write>(&self, path: P) -> Result<(), Error> { + let _ = path.as_ref().file_name().ok_or(|| { + let message = format!("Path must be filename {:?}", path.as_ref()); + Error::Input(message); + }); + let file = File::create(path)?; + self.artifact + .write(file) + .map_err(|source| Error::Failure(source, "Write error".to_owned()))?; + Ok(()) + } +} + +fn write_module( + module_data_len: usize, + table_manifest_len: usize, + function_manifest_len: usize, + obj: &mut Artifact, +) -> Result<(), Error> { + let mut native_data = Cursor::new(Vec::with_capacity(std::mem::size_of::())); + obj.declare(LUCET_MODULE_SYM, Decl::data().global()) + .map_err(|source| { + let message = format!("Manifest error declaring {}", FUNCTION_MANIFEST_SYM); + Error::ArtifactError(source, message) + })?; + + let version = + VersionInfo::current(include_str!(concat!(env!("OUT_DIR"), "/commit_hash")).as_bytes()); + + version.write_to(&mut native_data)?; + + write_relocated_slice( + obj, + &mut native_data, + LUCET_MODULE_SYM, + Some(MODULE_DATA_SYM), + module_data_len as u64, + )?; + write_relocated_slice( + obj, + &mut native_data, + LUCET_MODULE_SYM, + Some(TABLE_SYM), + table_manifest_len as u64, + )?; + write_relocated_slice( + obj, + &mut native_data, + LUCET_MODULE_SYM, + Some(FUNCTION_MANIFEST_SYM), + function_manifest_len as u64, + )?; + + obj.define(LUCET_MODULE_SYM, native_data.into_inner()) + .map_err(|source| { + let message = format!("Manifest error defining {}", FUNCTION_MANIFEST_SYM); + Error::ArtifactError(source, message) + })?; + + Ok(()) +} + +pub(crate) fn write_relocated_slice( + obj: &mut Artifact, + buf: &mut Cursor>, + from: &str, + to: Option<&str>, + len: u64, +) -> Result<(), Error> { + match (to, len) { + (Some(to), 0) => { + // This is an imported slice of unknown size + let absolute_reloc = match obj.target.binary_format { + BinaryFormat::Elf => faerie::artifact::Reloc::Raw { + reloc: goblin::elf::reloc::R_X86_64_64, + addend: 0, + }, + BinaryFormat::Macho => faerie::artifact::Reloc::Raw { + reloc: goblin::mach::relocation::X86_64_RELOC_UNSIGNED as u32, + addend: 0, + }, + _ => panic!("Unsupported target format!"), + }; + + obj.link_with( + Link { + from, + to, + at: buf.position(), + }, + absolute_reloc, + ) + .map_err(|source| { + let message = format!("Manifest error linking {}", to); + Error::Failure(source, message) + })?; + } + (Some(to), _len) => { + // This is a local buffer of known size + obj.link(Link { + from, // the data at `from` + `at` (eg. FUNCTION_MANIFEST_SYM) + to, // is a reference to `to` (eg. fn_name) + at: buf.position(), + }) + .map_err(|source| { + let message = format!("Manifest error linking {}", to); + Error::Failure(source, message) + })?; + } + (None, len) => { + // There's actually no relocation to add, because there's no slice to put here. + // + // Since there's no slice, its length must be zero. + assert!( + len == 0, + "Invalid slice: no data, but there are more than zero bytes of it" + ); + } + } + + buf.write_u64::(0).unwrap(); + buf.write_u64::(len).unwrap(); + + Ok(()) +} diff --git a/lucetc/src/patch.rs b/lucetc/src/patch.rs index f59fcd1f9..7160ecca2 100644 --- a/lucetc/src/patch.rs +++ b/lucetc/src/patch.rs @@ -1,4 +1,4 @@ -use failure::Error; +use crate::error::Error; use parity_wasm::elements::Module; use std::collections::HashMap; use std::path::Path; @@ -11,8 +11,10 @@ pub fn patch_module>( let mut patcher_config = PatcherConfig::default(); patcher_config.builtins_map_original_names = false; patcher_config.builtins_path = Some(builtins_path.as_ref().into()); - let patcher = Patcher::new(patcher_config, module)?; - let patched_builtins_map = patcher.patched_builtins_map("env")?; + let patcher = Patcher::new(patcher_config, module).map_err(Error::Patcher)?; + let patched_builtins_map = patcher + .patched_builtins_map("env") + .map_err(Error::Patcher)?; let patched_module = patcher.patched_module(); Ok((patched_module, patched_builtins_map)) } diff --git a/lucetc/src/pointer.rs b/lucetc/src/pointer.rs new file mode 100644 index 000000000..d90b26146 --- /dev/null +++ b/lucetc/src/pointer.rs @@ -0,0 +1,4 @@ +use cranelift_codegen::ir; + +pub const NATIVE_POINTER: ir::Type = ir::types::I64; +pub const NATIVE_POINTER_SIZE: usize = 8; diff --git a/lucetc/src/program/data/mod.rs b/lucetc/src/program/data/mod.rs deleted file mode 100644 index 6a75ac03a..000000000 --- a/lucetc/src/program/data/mod.rs +++ /dev/null @@ -1,70 +0,0 @@ -pub mod sparse; - -use super::init_expr::const_init_expr; -use failure::{format_err, Error, ResultExt}; -use parity_wasm::elements::Module; - -#[derive(Debug)] -pub struct DataInit<'m> { - pub offset: u32, - pub data: &'m [u8], -} - -impl<'m> DataInit<'m> { - pub fn linear_memory_range(&self, start: usize, end: usize) -> &'m [u8] { - let offs = self.offset as usize; - assert!(end >= start); - assert!(start >= offs); - assert!(start < offs + self.data.len()); - assert!(end >= offs); - assert!(end <= offs + self.data.len()); - &self.data[(start - offs)..(end - offs)] - } -} - -pub fn module_data<'m>(module: &'m Module) -> Result>, Error> { - let mut initializers = Vec::new(); - // XXX check the location of these init sections against the size of the memory imported or - // declared. That means we need to actually care about memories that we see in module.rs - if let Some(data_section) = module.data_section() { - for (segment_ix, segment) in data_section.entries().iter().enumerate() { - if segment.index() != 0 { - // https://webassembly.github.io/spec/syntax/modules.html#data-segments - return Err(format_err!( - "In the current version of WebAssembly, at most one memory is \ - allowed in a module. Consequently, the only valid memidx is 0 \ - (segment index={})", - segment.index(), - )); - } - - // Take the offset, and treat it as an unsigned 32 bit number. - // XXX need a type checked const_init_expr - this should always be a u32. - let offset = const_init_expr( - segment - .offset() - .as_ref() - .ok_or(format_err!("Offset not found"))? - .code(), - ) - .context(format!("data segment {}", segment_ix))? as u32; - - let max_lm_size: i64 = 0xFFFFFFFF; // 4GiB, per spec - - // Compare them at i64 so that they do not overflow - if (offset as i64) + (segment.value().len() as i64) > max_lm_size { - return Err(format_err!( - "initalizer does not fit in linear memory offset={} len={}", - offset, - segment.value().len() - )); - } - - initializers.push(DataInit { - offset: offset, - data: segment.value(), - }) - } - } - Ok(initializers) -} diff --git a/lucetc/src/program/data/sparse.rs b/lucetc/src/program/data/sparse.rs deleted file mode 100644 index c8468ceda..000000000 --- a/lucetc/src/program/data/sparse.rs +++ /dev/null @@ -1,85 +0,0 @@ -use super::DataInit; -use crate::program::memory::HeapSpec; -use std::collections::hash_map::Entry; -use std::collections::HashMap; - -pub use lucet_module_data::SparseData; - -const PAGE_SIZE: usize = 4096; - -fn split<'m>(di: &DataInit<'m>) -> Vec<(usize, DataInit<'m>)> { - let start = di.offset as usize; - let end = start + di.data.len(); - let mut offs = start; - let mut out = Vec::new(); - - while offs < end { - let page = offs / PAGE_SIZE; - let page_offs = offs % PAGE_SIZE; - let next = usize::min((page + 1) * PAGE_SIZE, end); - - let subslice = di.linear_memory_range(offs, next); - out.push(( - page, - DataInit { - offset: page_offs as u32, - data: subslice, - }, - )); - offs = next; - } - out -} - -pub struct OwnedSparseData { - pagemap: HashMap>, - heap: HeapSpec, -} - -impl OwnedSparseData { - pub fn new<'m>(data: &[DataInit<'m>], heap: HeapSpec) -> Self { - let mut pagemap: HashMap> = HashMap::new(); - - for d in data { - let chunks = split(d); - for (pagenumber, chunk) in chunks { - let base = chunk.offset as usize; - match pagemap.entry(pagenumber) { - Entry::Occupied(occ) => { - let occ = occ.into_mut(); - for (offs, data) in chunk.data.iter().enumerate() { - occ[base + offs] = *data; - } - assert!(occ.len() == PAGE_SIZE); - } - Entry::Vacant(vac) => { - let vac = vac.insert(Vec::new()); - vac.resize(PAGE_SIZE, 0); - for (offs, data) in chunk.data.iter().enumerate() { - vac[base + offs] = *data; - } - assert!(vac.len() == PAGE_SIZE); - } - } - } - } - Self { pagemap, heap } - } - - pub fn sparse_data(&self) -> SparseData { - assert_eq!(self.heap.initial_size as usize % PAGE_SIZE, 0); - let highest_page = self.heap.initial_size as usize / PAGE_SIZE; - - let mut out = Vec::new(); - for page_ix in 0..highest_page { - if let Some(chunk) = self.pagemap.get(&page_ix) { - assert!(chunk.len() == 4096); - out.push(Some(chunk.as_slice())) - } else { - out.push(None) - } - } - assert_eq!(out.len() * 4096, self.heap.initial_size as usize); - SparseData::new(out).expect("sparse data invariants held") - } -} diff --git a/lucetc/src/program/function.rs b/lucetc/src/program/function.rs deleted file mode 100644 index c9f902434..000000000 --- a/lucetc/src/program/function.rs +++ /dev/null @@ -1,179 +0,0 @@ -use crate::bindings::Bindings; -use crate::program::types::{CtonSignature, FunctionSig}; -use cranelift_codegen::ir; -use cranelift_module::Linkage; -use failure::Error; -use parity_wasm::elements::{FunctionType, ImportEntry}; - -pub trait Function { - fn signature(&self) -> ir::Signature; - fn signature_index(&self) -> Option; - fn linkage(&self) -> Linkage; - fn symbol(&self) -> &str; -} - -impl Function for FunctionImport { - fn signature(&self) -> ir::Signature { - self.signature() - } - fn signature_index(&self) -> Option { - Some(self.signature_index()) - } - fn linkage(&self) -> Linkage { - self.linkage() - } - fn symbol(&self) -> &str { - self.symbol() - } -} - -impl Function for FunctionDef { - fn signature(&self) -> ir::Signature { - self.signature() - } - fn signature_index(&self) -> Option { - Some(self.signature_index()) - } - fn linkage(&self) -> Linkage { - self.linkage() - } - fn symbol(&self) -> &str { - self.symbol() - } -} - -impl Function for FunctionRuntime { - fn signature(&self) -> ir::Signature { - self.signature() - } - fn signature_index(&self) -> Option { - None - } - fn linkage(&self) -> Linkage { - self.linkage() - } - fn symbol(&self) -> &str { - self.symbol() - } -} - -#[derive(Debug, Clone)] -pub struct FunctionDef { - pub wasmidx: u32, - sig: FunctionSig, - exported: bool, - symbol: String, -} - -impl FunctionDef { - pub fn new(wasmidx: u32, sig: FunctionSig, exported: bool, symbol: String) -> Self { - Self { - wasmidx, - sig, - exported, - symbol, - } - } - - pub fn signature(&self) -> ir::Signature { - self.sig.cton_signature() - } - - pub fn signature_index(&self) -> u32 { - self.sig.sig_ix - } - - pub fn symbol(&self) -> &str { - &self.symbol - } - - pub fn linkage(&self) -> Linkage { - if self.exported { - Linkage::Export - } else { - Linkage::Local - } - } -} - -#[derive(Debug, Clone)] -pub struct FunctionImport { - wasmidx: u32, - module: String, - field: String, - sig: FunctionSig, - symbol: String, -} - -impl FunctionImport { - pub fn new( - wasmidx: u32, - importentry: &ImportEntry, - sig: FunctionSig, - bindings: &Bindings, - ) -> Result { - let module = String::from(importentry.module()); - let field = String::from(importentry.field()); - let symbol = bindings.translate(&module, &field)?; - Ok(Self { - wasmidx, - module, - field, - sig, - symbol, - }) - } - - pub fn module(&self) -> &str { - self.module.as_str() - } - - pub fn field(&self) -> &str { - self.field.as_str() - } - - pub fn signature(&self) -> ir::Signature { - self.sig.cton_signature() - } - - pub fn signature_index(&self) -> u32 { - self.sig.sig_ix - } - - pub fn symbol(&self) -> &str { - &self.symbol - } - - pub fn linkage(&self) -> Linkage { - Linkage::Import - } -} - -#[derive(Debug, Clone)] -pub struct FunctionRuntime { - ix: u32, - symbol: String, - ty: FunctionType, -} - -impl FunctionRuntime { - pub fn new(ix: u32, symbol: &str, ty: FunctionType) -> Self { - Self { - ix: ix, - symbol: String::from(symbol), - ty: ty, - } - } - - pub fn signature(&self) -> ir::Signature { - self.ty.cton_signature() - } - - pub fn symbol(&self) -> &str { - &self.symbol - } - - pub fn linkage(&self) -> Linkage { - Linkage::Import - } -} diff --git a/lucetc/src/program/globals.rs b/lucetc/src/program/globals.rs deleted file mode 100644 index fd272f434..000000000 --- a/lucetc/src/program/globals.rs +++ /dev/null @@ -1,130 +0,0 @@ -use super::init_expr::const_init_expr; -use super::types::cton_valuetype; -use crate::error::LucetcError; -use cranelift_codegen::ir; -use parity_wasm::elements::{GlobalType, ImportEntry, InitExpr}; - -#[derive(Debug, Clone)] -pub struct GlobalImport { - module: String, - field: String, - pub global_type: GlobalType, - export: Option, -} - -impl GlobalImport { - pub fn new(importentry: &ImportEntry, global_type: GlobalType, export: Option) -> Self { - Self { - module: String::from(importentry.module()), - field: String::from(importentry.field()), - global_type, - export, - } - } - - pub fn cton_type(&self) -> ir::Type { - cton_valuetype(&self.global_type.content_type()) - } - - pub fn module(&self) -> &str { - self.module.as_str() - } - - pub fn field(&self) -> &str { - self.field.as_str() - } - - pub fn export(&self) -> Option<&str> { - match self.export { - Some(ref ex) => Some(ex.as_str()), - None => None, - } - } -} - -#[derive(Debug, Clone)] -pub struct GlobalDef { - global_type: GlobalType, - value: i64, - export: Option, -} - -impl GlobalDef { - pub fn new( - global_type: GlobalType, - init_expr: &InitExpr, - export: Option, - ) -> Result { - let value = const_init_expr(init_expr.code())?; - Ok(Self { - global_type: global_type, - value: value, - export: export, - }) - } - pub fn cton_type(&self) -> ir::Type { - cton_valuetype(&self.global_type.content_type()) - } - pub fn value(&self) -> i64 { - self.value - } - pub fn export(&self) -> Option<&str> { - match self.export { - Some(ref ex) => Some(ex.as_str()), - None => None, - } - } -} - -pub enum Global { - Import(GlobalImport), - Def(GlobalDef), -} - -impl Global { - pub fn cton_type(&self) -> ir::Type { - match self { - &Global::Import(ref globalimport) => globalimport.cton_type(), - &Global::Def(ref globaldef) => globaldef.cton_type(), - } - } - - pub fn as_import(&self) -> Option<&GlobalImport> { - match self { - Global::Import(i) => Some(i), - Global::Def(_) => None, - } - } - - pub fn as_def(&self) -> Option<&GlobalDef> { - match self { - Global::Import(_) => None, - Global::Def(d) => Some(d), - } - } - - pub fn export(&self) -> Option<&str> { - match self { - Global::Import(i) => i.export(), - Global::Def(d) => d.export(), - } - } -} - -use lucet_module_data as data; - -impl Global { - pub fn to_spec(&self) -> data::GlobalSpec { - let global = match self { - Global::Import(i) => data::Global::Import { - module: i.module(), - field: i.field(), - }, - Global::Def(d) => data::Global::Def { - def: data::GlobalDef::new(d.value()), - }, - }; - let export = self.export(); - data::GlobalSpec::new(global, export) - } -} diff --git a/lucetc/src/program/init_expr.rs b/lucetc/src/program/init_expr.rs deleted file mode 100644 index 95ace328d..000000000 --- a/lucetc/src/program/init_expr.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::error::{LucetcError, LucetcErrorKind}; -use failure::{format_err, ResultExt}; -use parity_wasm::elements::Instruction; - -pub fn const_init_expr(opcodes: &[Instruction]) -> Result { - let len = opcodes.len(); - if !(len >= 1 && opcodes[len - 1] == Instruction::End) { - Err(format_err!( - "invalid init expr: must be terminated with End opcode, got {:?}", - opcodes - ))?; - } - - if len > 2 { - Err(format_err!( - "init expr is too long to be a single const, got {:?}", - opcodes - )) - .context(LucetcErrorKind::Unsupported( - "non-const init expr".to_owned(), - ))?; - } - - match opcodes[0] { - Instruction::I32Const(i32_const) => Ok(i32_const as i64), - Instruction::I64Const(i64_const) => Ok(i64_const), - _ => Err(format_err!( - "init expr is not a const integer expr, got {:?}", - opcodes - )) - .context(LucetcErrorKind::Unsupported("non-int init expr".to_owned()))?, - } -} diff --git a/lucetc/src/program/memory.rs b/lucetc/src/program/memory.rs deleted file mode 100644 index 81df83f9b..000000000 --- a/lucetc/src/program/memory.rs +++ /dev/null @@ -1,58 +0,0 @@ -use failure::{bail, Error}; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct MemorySpec { - pub initial_pages: u32, - pub max_pages: Option, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct HeapSettings { - pub min_reserved_size: u64, - pub max_reserved_size: u64, - pub guard_size: u64, -} - -impl Default for HeapSettings { - fn default() -> Self { - Self { - min_reserved_size: 4 * 1024 * 1024, - max_reserved_size: 6 * 1024 * 1024 * 1024, - guard_size: 4 * 1024 * 1024, - } - } -} - -pub use lucet_module_data::HeapSpec; -pub fn create_heap_spec(mem: &MemorySpec, heap: &HeapSettings) -> Result { - let wasm_page: u64 = 64 * 1024; - - let initial_size = mem.initial_pages as u64 * wasm_page; - - let reserved_size = std::cmp::max(initial_size, heap.min_reserved_size); - - if reserved_size > heap.max_reserved_size { - bail!( - "module reserved size ({}) exceeds max reserved size ({})", - initial_size, - heap.max_reserved_size, - ); - } - - // Find the max size permitted by the heap and the memory spec - let max_size = mem.max_pages.map(|pages| pages as u64 * wasm_page); - Ok(HeapSpec { - reserved_size, - guard_size: heap.guard_size, - initial_size: initial_size, - max_size: max_size, - }) -} -pub fn empty_heap_spec() -> HeapSpec { - HeapSpec { - reserved_size: 0, - guard_size: 0, - initial_size: 0, - max_size: None, - } -} diff --git a/lucetc/src/program/mod.rs b/lucetc/src/program/mod.rs deleted file mode 100644 index 80143c10e..000000000 --- a/lucetc/src/program/mod.rs +++ /dev/null @@ -1,393 +0,0 @@ -pub mod data; -pub mod function; -pub mod globals; -pub mod init_expr; -pub mod memory; -pub mod names; -pub mod runtime; -pub mod table; -pub mod types; - -pub use self::data::{module_data, DataInit}; -pub use self::function::{Function, FunctionDef, FunctionImport, FunctionRuntime}; -pub use self::globals::{Global, GlobalDef, GlobalImport}; -pub use self::memory::{create_heap_spec, empty_heap_spec, HeapSettings, HeapSpec, MemorySpec}; -pub use self::names::{module_names, ModuleNames}; -pub use self::runtime::Runtime; -pub use self::table::{TableBuilder, TableDef}; -pub use self::types::{CtonSignature, FunctionSig}; - -use crate::bindings::Bindings; -use crate::error::{LucetcError, LucetcErrorKind}; -use crate::program::init_expr::const_init_expr; -use failure::{format_err, ResultExt}; -use parity_wasm::elements::{External, FuncBody, MemoryType, Module, TableElementType, Type}; -use pwasm_validation::validate_module; -use std::collections::{hash_map::Entry, HashMap}; - -pub struct Program { - module: Module, - - globals: Vec, - tables: Vec, - runtime: Runtime, - heap_settings: HeapSettings, - - defined_funcs: Vec, - defined_memory: Option, - - import_functions: Vec, - import_memory: Option, -} - -impl Program { - pub fn new( - module: Module, - bindings: Bindings, - heap_settings: HeapSettings, - ) -> Result { - let module = module.parse_names().map_err(|(es, _)| { - format_err!("could not parse some of the name sections: {:?}", es) - })?; - - let module = validate_module(module)?.unwrap(); - let names = module_names(&module)?; - let imports = module_imports(&module, bindings, &names)?; - let defs = module_definitions(&module, &imports, &names)?; - let tables = module_tables(&module, imports.tables)?; - let globals = module_globals(imports.globals, defs.globals); - let runtime = Runtime::liblucet_runtime_c(); - Ok(Self { - module, - globals, - tables, - runtime, - heap_settings, - defined_funcs: defs.funcs, - defined_memory: defs.memory, - - import_functions: imports.functions, - import_memory: imports.memory, - }) - } - - pub fn module(&self) -> &Module { - &self.module - } - - pub fn get_function(&self, index: u32) -> Result<&Function, LucetcError> { - if let Some(fnindex) = index.checked_sub(self.import_functions.len() as u32) { - if let Some(def) = self.defined_funcs.get(fnindex as usize) { - Ok(def) - } else { - Err(format_err!("function index {} is out of bounds", index))? - } - } else { - Ok(self - .import_functions - .get(index as usize) - .expect("function import index was checked")) - } - } - - pub fn globals(&self) -> &[Global] { - self.globals.as_ref() - } - - pub fn get_signature(&self, index: u32) -> Result { - module_get_signature(&self.module, index) - } - - pub fn tables(&self) -> &[TableDef] { - &self.tables - } - - pub fn get_table(&self, index: u32) -> Result<&TableDef, LucetcError> { - let tbl = self - .tables - .get(index as usize) - .ok_or_else(|| format_err!("table out of range: {}", index))?; - Ok(tbl) - } - - pub fn runtime_functions(&self) -> &[FunctionRuntime] { - self.runtime.functions() - } - - pub fn get_runtime_function(&self, name: &str) -> Result<&FunctionRuntime, LucetcError> { - self.runtime.get_symbol(name) - } - - pub fn heap_spec(&self) -> Result { - if let Some(ref mem_spec) = self.import_memory { - Ok(create_heap_spec(mem_spec, &self.heap_settings) - .context(LucetcErrorKind::MemorySpecs)?) - } else if let Some(ref mem_spec) = self.defined_memory { - Ok(create_heap_spec(mem_spec, &self.heap_settings) - .context(LucetcErrorKind::MemorySpecs)?) - } else { - Ok(empty_heap_spec()) - } - } - - pub fn data_initializers(&self) -> Result, LucetcError> { - let v = module_data(&self.module)?; - Ok(v) - } - - pub fn function_body(&self, def: &FunctionDef) -> &FuncBody { - let bodies = self - .module - .code_section() - .map(|s| s.bodies()) - .unwrap_or(&[]); - let fn_index_base = self.import_functions.len(); - &bodies - .get(def.wasmidx as usize - fn_index_base) - .expect("functiondef points to valid body") - } - - pub fn defined_functions(&self) -> &[FunctionDef] { - self.defined_funcs.as_ref() - } - - pub fn import_functions(&self) -> &[FunctionImport] { - self.import_functions.as_ref() - } -} - -pub struct ModuleTables { - pub tables: Vec, -} - -enum TableDecl { - Import(TableDef), - Def(TableBuilder), -} - -fn memory_spec(mem: &MemoryType) -> MemorySpec { - let limits = mem.limits(); - MemorySpec { - initial_pages: limits.initial(), - max_pages: limits.maximum(), - } -} - -fn module_get_signature(module: &Module, index: u32) -> Result { - let type_entry = module - .type_section() - .ok_or_else(|| LucetcErrorKind::Other("no types in this module".to_owned()))? - .types() - .get(index as usize) - .ok_or_else(|| LucetcErrorKind::Other(format!("no signature for {}", index)))?; - match type_entry { - &Type::Function(ref ftype) => Ok(FunctionSig::new(index, ftype)), - } -} - -struct ModuleImports { - memory: Option, - functions: Vec, - globals: Vec, - tables: Vec, -} - -impl ModuleImports { - pub fn function_index_base(&self) -> u32 { - self.functions.len() as u32 - } - pub fn global_index_base(&self) -> u32 { - self.globals.len() as u32 - } -} - -fn module_imports( - module: &Module, - bindings: Bindings, - names: &ModuleNames, -) -> Result { - let mut memory = None; - let mut functions = Vec::new(); - let mut globals = Vec::new(); - let mut tables = Vec::new(); - if let Some(import_section) = module.import_section() { - for entry in import_section.entries().iter() { - match entry.external() { - &External::Function(typeix) => { - let functionix = functions.len() as u32; - let ftype = module_get_signature(&module, typeix)?; - functions.push(FunctionImport::new(functionix, entry, ftype, &bindings)?) - } - &External::Global(ref gty) => { - let globalix = globals.len() as u32; - globals.push(GlobalImport::new( - entry, - gty.clone(), - names.global_symbol(globalix), - )) - } - - &External::Table(ref tty) => { - let tableix = tables.len() as u32; - let builder = - TableBuilder::new(tableix, tty.limits().initial(), tty.limits().maximum())?; - tables.push(builder.finalize()); - } - &External::Memory(ref mem) => memory = Some(memory_spec(mem)), - } - } - } - Ok(ModuleImports { - memory, - functions, - globals, - tables, - }) -} - -struct ModuleDefs { - funcs: Vec, - globals: Vec, - memory: Option, -} - -fn module_definitions( - module: &Module, - imports: &ModuleImports, - names: &ModuleNames, -) -> Result { - let mut memory = None; - - let decls = module - .function_section() - .map(|s| s.entries()) - .unwrap_or(&[]); - let mut funcs = Vec::new(); - for (ix, decl) in decls.iter().enumerate() { - let funcindex = ix as u32 + imports.function_index_base(); - - funcs.push(FunctionDef::new( - funcindex, - module_get_signature(&module, decl.type_ref()).expect("signature for func must exist"), - names.function_exported(funcindex), - names.function_symbol(funcindex), - )) - } - - if let Some(memory_types) = module.memory_section().map(|s| s.entries()) { - if memory_types.len() > 1 { - Err(format_err!("multiple memories are not supported"))? - } - for memory_type in memory_types.iter() { - memory = Some(memory_spec(memory_type)) - } - } - - let mut globals = Vec::new(); - if let Some(global_decls) = module.global_section().map(|s| s.entries()) { - for (ix, decl) in global_decls.iter().enumerate() { - let globalindex = ix as u32 + imports.global_index_base(); - globals.push(GlobalDef::new( - decl.global_type().clone(), - decl.init_expr(), - names.global_symbol(globalindex), - )?) - } - } - Ok(ModuleDefs { - funcs, - globals, - memory, - }) -} - -fn module_tables( - module: &Module, - import_tables: Vec, -) -> Result, LucetcError> { - let mut tables = HashMap::new(); - for tbl in import_tables.iter() { - tables.insert(tbl.index(), TableDecl::Import(tbl.clone())); - } - if let Some(table_section) = module.table_section() { - for table_type in table_section.entries().iter() { - match table_type.elem_type() { - TableElementType::AnyFunc => { - let section_index = 0; - match tables.entry(section_index) { - Entry::Occupied(occ) => match occ.get() { - TableDecl::Import(_) => Err(format_err!( - "Cannot define table {}: already declared as import", - section_index - ))?, - TableDecl::Def(_) => Err(format_err!( - "Cannot define table {}: already defined", - section_index - ))?, - }, - Entry::Vacant(vac) => { - vac.insert(TableDecl::Def(TableBuilder::new( - section_index, - table_type.limits().initial(), - table_type.limits().maximum(), - )?)); - } - } - } - } - } - if let Some(element_section) = module.elements_section() { - for (segment_ix, element_segment) in element_section.entries().iter().enumerate() { - let table_ix = element_segment.index(); - let offs: i64 = const_init_expr( - element_segment - .offset() - .as_ref() - .ok_or(format_err!("Offset not found"))? - .code(), - ) - .context(LucetcErrorKind::Other(format!( - "in element segment offset for table {}, segment {}", - table_ix, segment_ix - )))?; - // Ensure its safe to make into an i32: - assert!(offs >= ::min_value() as i64 && offs <= ::max_value() as i64); - match tables.get_mut(&table_ix) { - Some(TableDecl::Def(ref mut builder)) => builder - .push_elements(offs as i32, element_segment.members().to_vec()) - .context(LucetcErrorKind::Other(format!( - "in elements for table {}, segment {}", - table_ix, segment_ix - )))?, - Some(TableDecl::Import(_)) => Err(format_err!( - "Cannot define element for imported table {}", - table_ix - ))?, - None => Err(format_err!( - "Cannot define element for undeclared table {}", - table_ix - ))?, - } - } - } - } - let tables = tables - .values() - .map(|decl| match decl { - TableDecl::Import(def) => def.clone(), - TableDecl::Def(builder) => builder.finalize(), - }) - .collect(); - Ok(tables) -} - -fn module_globals(imports: Vec, defs: Vec) -> Vec { - let mut globals = Vec::new(); - for g in imports { - globals.push(Global::Import(g.clone())); - } - for g in defs { - globals.push(Global::Def(g.clone())); - } - globals -} diff --git a/lucetc/src/program/names.rs b/lucetc/src/program/names.rs deleted file mode 100644 index aa14e5e6b..000000000 --- a/lucetc/src/program/names.rs +++ /dev/null @@ -1,135 +0,0 @@ -use crate::error::LucetcError; -use bimap::BiMap; -use parity_wasm::elements::{Internal, Module, NameSection, Section}; -use std::collections::HashMap; - -pub struct ModuleNames { - func_exports: Vec, - func_names: BiMap, - glob_exports: HashMap, -} - -impl ModuleNames { - pub fn function_symbol(&self, ix: u32) -> String { - let n = self.func_names.get_by_left(&ix); - if self.function_exported(ix) { - format!("guest_func_{}", n.expect("exported name must be defined")) - } else { - if let Some(n) = n { - format!("guest_internalfunc_{}", n) - } else { - format!("guest_internalfunc_{}", ix) - } - } - } - pub fn function_exported(&self, ix: u32) -> bool { - self.func_exports.contains(&ix) - } - pub fn global_symbol(&self, ix: u32) -> Option { - self.glob_exports.get(&(ix as usize)).cloned() - } -} - -fn define_unique_name(func_names: &mut BiMap, ix: u32, n: String) { - assert!(!func_names.contains_left(&ix)); - if func_names.contains_right(&n) { - // Name is not unique, search for one: - let mut suffix: usize = 1; - loop { - let n_uniq = format!("{}_{}", n, suffix); - if !func_names.contains_right(&n_uniq) { - func_names.insert(ix, n_uniq); - break; - } - suffix += 1; - } - } else { - func_names.insert(ix, n); - } -} - -pub fn module_names(module: &Module) -> Result { - let mut func_exports = Vec::new(); - let mut func_names = BiMap::new(); - let mut glob_exports = HashMap::new(); - - if let Some(export_entries) = module.export_section().map(|s| s.entries()) { - for entry in export_entries.iter() { - match *entry.internal() { - Internal::Function(idx) => { - func_exports.push(idx); - func_names.insert(idx, entry.field().to_owned()); - } - Internal::Global(idx) => { - glob_exports.insert(idx as usize, String::from(entry.field())); - } - Internal::Table(_) => {} // We do not do anything with exported tables - Internal::Memory(_) => {} // We do not do anything with exported memories - } - } - } - - for section in module.sections() { - match *section { - Section::Name(ref name_section) => match *name_section { - NameSection::Function(ref func_sect) => { - for (idx, name) in func_sect.names() { - if !func_names.contains_left(&idx) { - define_unique_name(&mut func_names, idx, name.clone()); - func_names.insert(idx, name.to_owned()); - } - } - } - _ => {} - }, - _ => {} - } - } - - Ok(ModuleNames { - func_exports, - glob_exports, - func_names, - }) -} - -#[cfg(test)] -mod tests { - use super::define_unique_name; - use bimap::BiMap; - #[test] - fn trivial() { - let mut func_names = BiMap::new(); - func_names.insert(0, "zero".to_owned()); - define_unique_name(&mut func_names, 1, "one".to_owned()); - assert_eq!(func_names.get_by_left(&1), Some(&"one".to_owned())); - } - - #[test] - fn one_dup() { - let mut func_names = BiMap::new(); - func_names.insert(0, "foo".to_owned()); - define_unique_name(&mut func_names, 1, "foo".to_owned()); - assert_eq!(func_names.get_by_left(&1), Some(&"foo_1".to_owned())); - } - - #[test] - fn two_dup() { - let mut func_names = BiMap::new(); - func_names.insert(0, "foo".to_owned()); - define_unique_name(&mut func_names, 1, "foo".to_owned()); - define_unique_name(&mut func_names, 2, "foo".to_owned()); - assert_eq!(func_names.get_by_left(&1), Some(&"foo_1".to_owned())); - assert_eq!(func_names.get_by_left(&2), Some(&"foo_2".to_owned())); - } - - #[test] - fn dup_of_base() { - let mut func_names = BiMap::new(); - func_names.insert(0, "foo_1".to_owned()); - define_unique_name(&mut func_names, 1, "foo".to_owned()); - define_unique_name(&mut func_names, 2, "foo".to_owned()); - assert_eq!(func_names.get_by_left(&1), Some(&"foo".to_owned())); - assert_eq!(func_names.get_by_left(&2), Some(&"foo_2".to_owned())); - } -} diff --git a/lucetc/src/program/runtime.rs b/lucetc/src/program/runtime.rs deleted file mode 100644 index c4b7de332..000000000 --- a/lucetc/src/program/runtime.rs +++ /dev/null @@ -1,45 +0,0 @@ -use super::function::FunctionRuntime; -use crate::error::LucetcError; -use failure::format_err; -use parity_wasm::elements::{FunctionType, ValueType}; - -#[derive(Debug, Clone)] -pub struct Runtime { - funcs: Vec, -} - -impl Runtime { - pub fn liblucet_runtime_c() -> Self { - let current_memory_type = FunctionType::new(Vec::new(), Some(ValueType::I32)); - let grow_memory_type = FunctionType::new(vec![ValueType::I32], Some(ValueType::I32)); - Self { - funcs: vec![ - FunctionRuntime::new(0, "lucet_vmctx_current_memory", current_memory_type), - FunctionRuntime::new(1, "lucet_vmctx_grow_memory", grow_memory_type), - ], - } - } - - pub fn functions(&self) -> &[FunctionRuntime] { - self.funcs.as_ref() - } - - pub fn get_index(&self, ix: u32) -> Result { - let rt = self - .funcs - .get(ix as usize) - .map(|f| f.clone()) - .ok_or(format_err!("runtime function {} out of bounds", ix))?; - Ok(rt) - } - - pub fn get_symbol(&self, symbol: &str) -> Result<&FunctionRuntime, LucetcError> { - let symbol = String::from(symbol); - for f in self.funcs.iter() { - if f.symbol() == symbol { - return Ok(f); - } - } - Err(format_err!("runtime function \"{}\" not found", symbol))? - } -} diff --git a/lucetc/src/program/table.rs b/lucetc/src/program/table.rs deleted file mode 100644 index 804e22ee9..000000000 --- a/lucetc/src/program/table.rs +++ /dev/null @@ -1,114 +0,0 @@ -use failure::{format_err, Error}; -use std::cmp; -use std::collections::HashMap; - -#[derive(Debug, Clone)] -pub struct TableBuilder { - index: u32, - min_size: u32, - max_size: Option, - /// Map from icall index to function - elems: HashMap, -} - -impl TableBuilder { - pub fn new(index: u32, min_size: u32, max_size: Option) -> Result { - if let Some(max_size) = max_size { - if min_size > max_size { - return Err(format_err!( - "table size max ({}) less than min ({})", - max_size, - min_size - )); - } - } - Ok(Self { - index: index, - min_size: min_size, - max_size: max_size, - elems: HashMap::new(), - }) - } - - pub fn push_elements(&mut self, offset: i32, elems: Vec) -> Result<(), Error> { - if offset < 0 { - return Err(format_err!( - "table elements given at negative offset {}", - offset - )); - } - - for (i, e) in elems.iter().enumerate() { - if i > ::max_value() as usize { - return Err(format_err!("table element at {} out-of-bounds", i)); - } - // Note: we do not validate that `e` is the index of a valid function. We count on - // `compiler::table` to check on this. - if let Some(max_size) = self.max_size { - if (i as u32) >= max_size { - return Err(format_err!( - "table element at {} beyond declared maximum size {}", - i, - max_size - )); - } - } - self.elems.insert(offset as usize + i, *e); - } - Ok(()) - } - - fn capacity(&self) -> usize { - // Guaranteed by `push_elements` to be <= (max - 1) if there is a max. - let highest_index = *self.elems.keys().max().unwrap_or(&0); - // Make table big enough to represent greatest index, or the given minimum size. - cmp::max(highest_index + 1, self.min_size as usize) - } - - pub fn finalize(&self) -> TableDef { - let capacity = self.capacity(); - let mut elems = Vec::with_capacity(capacity); - for index in 0..capacity { - match self.elems.get(&index) { - Some(value) => elems.push(TableElem::FunctionIx(*value)), - None => elems.push(TableElem::Empty), - } - } - - TableDef { - index: self.index, - elems: elems, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum TableElem { - Empty, - FunctionIx(u32), -} - -#[derive(Debug, Clone)] -pub struct TableDef { - index: u32, - elems: Vec, -} - -impl TableDef { - pub fn index(&self) -> u32 { - self.index - } - pub fn elements(&self) -> &[TableElem] { - &self.elems - } - pub fn symbol(&self) -> String { - format!("guest_table_{}", self.index) - } - pub fn len(&self) -> u64 { - assert!(self.elems.len() <= ::std::u32::MAX as usize); - self.elems.len() as u64 * (2 * 8) - } - pub fn len_symbol(&self) -> String { - format!("{}_len", self.symbol()) - } -} diff --git a/lucetc/src/program/types.rs b/lucetc/src/program/types.rs deleted file mode 100644 index d6e03d10b..000000000 --- a/lucetc/src/program/types.rs +++ /dev/null @@ -1,61 +0,0 @@ -use cranelift_codegen::{ir, isa}; -use parity_wasm::elements::{FunctionType, ValueType}; - -#[derive(Debug, Clone)] -pub struct FunctionSig { - pub sig_ix: u32, - ftype: FunctionType, -} - -impl FunctionSig { - pub fn new(sig_ix: u32, ftype: &FunctionType) -> Self { - Self { - sig_ix: sig_ix, - ftype: ftype.clone(), - } - } -} - -pub fn cton_valuetype(t: &ValueType) -> ir::Type { - match t { - &ValueType::I32 => ir::types::I32, - &ValueType::I64 => ir::types::I64, - &ValueType::F32 => ir::types::F32, - &ValueType::F64 => ir::types::F64, - &ValueType::V128 => unimplemented!(), - } -} - -pub trait CtonSignature { - fn cton_signature(&self) -> ir::Signature; -} - -impl CtonSignature for FunctionType { - fn cton_signature(&self) -> ir::Signature { - let mut sig = ir::Signature::new(isa::CallConv::SystemV); - sig.params.insert( - 0, - ir::AbiParam { - value_type: ir::types::I64, - purpose: ir::ArgumentPurpose::VMContext, - extension: ir::ArgumentExtension::None, - location: ir::ArgumentLoc::Unassigned, - }, - ); - sig.params.extend( - self.params() - .iter() - .map(|t| ir::AbiParam::new(cton_valuetype(t))), - ); - if let Some(t) = self.return_type() { - sig.returns.push(ir::AbiParam::new(cton_valuetype(&t))); - } - sig - } -} - -impl CtonSignature for FunctionSig { - fn cton_signature(&self) -> ir::Signature { - self.ftype.cton_signature() - } -} diff --git a/lucetc/src/runtime.rs b/lucetc/src/runtime.rs new file mode 100644 index 000000000..1b14e7d72 --- /dev/null +++ b/lucetc/src/runtime.rs @@ -0,0 +1,44 @@ +use cranelift_codegen::ir::{types, AbiParam, Signature}; +use cranelift_codegen::isa::TargetFrontendConfig; +use std::collections::HashMap; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] +pub enum RuntimeFunc { + MemSize, + MemGrow, +} + +pub struct Runtime { + pub functions: HashMap, +} + +impl Runtime { + pub fn lucet(target: TargetFrontendConfig) -> Self { + let mut functions = HashMap::new(); + functions.insert( + RuntimeFunc::MemSize, + ( + "lucet_vmctx_current_memory".to_owned(), + Signature { + params: vec![], + returns: vec![AbiParam::new(types::I32)], + call_conv: target.default_call_conv, + }, + ), + ); + functions.insert( + RuntimeFunc::MemGrow, + ( + "lucet_vmctx_grow_memory".to_owned(), + Signature { + params: vec![ + AbiParam::new(types::I32), // wasm pages to grow + ], + returns: vec![AbiParam::new(types::I32)], + call_conv: target.default_call_conv, + }, + ), + ); + Self { functions } + } +} diff --git a/lucetc/src/signature.rs b/lucetc/src/signature.rs new file mode 100644 index 000000000..97a2a2638 --- /dev/null +++ b/lucetc/src/signature.rs @@ -0,0 +1,94 @@ +use crate::error::Error; +use lucet_module::ModuleSignature; +pub use minisign::{KeyPair, PublicKey, SecretKey, SignatureBones, SignatureBox}; +use std::fs::File; +use std::io::{Cursor, Read, Write}; +use std::path::{Path, PathBuf}; + +pub const RAW_KEY_PREFIX: &str = "raw:"; + +fn raw_key_path>(path: P) -> Option { + let path = path.as_ref(); + if let Some(path) = path.to_str() { + if path.starts_with(RAW_KEY_PREFIX) { + return Some(PathBuf::from(&path[RAW_KEY_PREFIX.len()..])); + } + } + None +} + +pub fn sk_from_file>(sk_path: P) -> Result { + match raw_key_path(sk_path.as_ref()) { + None => SecretKey::from_file(sk_path, None).map_err(|e| { + let message = format!("Unable to read the secret key: {}", e); + Error::Signature(message) + }), + Some(sk_path) => { + let mut sk_bin: Vec = Vec::new(); + File::open(sk_path)?.read_to_end(&mut sk_bin)?; + SecretKey::from_bytes(&sk_bin).map_err(|e| { + let message = format!("Unable to read the secret key: {}", e); + Error::Signature(message) + }) + } + } +} + +fn signature_path>(path: P) -> Result { + let path = path.as_ref().to_str().ok_or_else(|| { + let message = format!("Invalid signature path {:?}", path.as_ref()); + Error::Input(message) + })?; + Ok(PathBuf::from(format!("{}.minisig", path))) +} + +pub fn signature_box_for_module_path>(path: P) -> Result { + let signature_path = signature_path(path)?; + SignatureBox::from_file(&signature_path).map_err(|e| { + let message = format!("Unable to load the signature file: {}", e); + Error::Signature(message) + }) +} + +pub fn keygen, Q: AsRef>(pk_path: P, sk_path: Q) -> Result { + match raw_key_path(&sk_path) { + None => { + let pk_writer = File::create(pk_path)?; + let sk_writer = File::create(sk_path)?; + KeyPair::generate_and_write_encrypted_keypair(pk_writer, sk_writer, None, None).map_err( + |e| { + let message = format!("Unable to generate the key pair: {}", e); + Error::Signature(message) + }, + ) + } + Some(sk_path_raw) => { + let kp = KeyPair::generate_unencrypted_keypair().map_err(|e| { + let message = format!("Unable to generate the key pair: {}", e); + Error::Signature(message) + })?; + let mut pk_writer = File::create(pk_path)?; + let mut sk_writer = File::create(sk_path_raw)?; + + pk_writer.write_all(&kp.pk.to_box().unwrap().to_bytes())?; + sk_writer.write_all(&kp.sk.to_bytes())?; + + Ok(kp) + } + } +} + +// Verify the source code (WASM / WAT) +pub fn verify_source_code( + buf: &[u8], + signature_box: &SignatureBox, + pk: &PublicKey, +) -> Result<(), Error> { + minisign::verify(pk, signature_box, Cursor::new(buf), false, false) + .map_err(|e| Error::Signature(e.to_string())) +} + +// Sign the compiled code +pub fn sign_module>(path: P, sk: &SecretKey) -> Result<(), Error> { + ModuleSignature::sign(path, sk).map_err(|e| e.into()) +} diff --git a/lucetc/src/sparsedata.rs b/lucetc/src/sparsedata.rs new file mode 100644 index 000000000..f228d8112 --- /dev/null +++ b/lucetc/src/sparsedata.rs @@ -0,0 +1,98 @@ +use crate::error::Error; +use crate::module::DataInitializer; +use lucet_module::owned::OwnedSparseData; +use lucet_module::HeapSpec; +use std::collections::hash_map::Entry; +use std::collections::HashMap; + +pub use lucet_module::SparseData; + +const PAGE_SIZE: usize = 4096; + +fn linear_memory_range<'a>(di: &DataInitializer<'a>, start: usize, end: usize) -> &'a [u8] { + let offs = di.offset as usize; + // The range of linear memory we're interested in is: + // valid: end is past the start + assert!(end >= start); + // in this initializer: starts at or past the offset of this initializer, + assert!(start >= offs); + // and before the end of this initializer, + assert!(start < offs + di.data.len()); + // ends past the offset of this initializer (redundant: implied by end >= start), + assert!(end >= offs); + // and ends before or at the end of this initializer. + assert!(end <= offs + di.data.len()); + &di.data[(start - offs)..(end - offs)] +} + +fn split<'a>(di: &DataInitializer<'a>) -> Vec<(usize, DataInitializer<'a>)> { + // Divide a data initializer for linear memory into a set of data initializers for pages, and + // the index of the page they cover. + // The input initializer can cover many pages. Each output initializer covers exactly one. + let start = di.offset as usize; + let end = start + di.data.len(); + let mut offs = start; + let mut out = Vec::new(); + + while offs < end { + let page = offs / PAGE_SIZE; + let page_offs = offs % PAGE_SIZE; + let next = usize::min((page + 1) * PAGE_SIZE, end); + + let subslice = linear_memory_range(di, offs, next); + out.push(( + page, + DataInitializer { + base: None, + offset: page_offs, + data: subslice, + }, + )); + offs = next; + } + out +} + +pub fn owned_sparse_data_from_initializers<'a>( + initializers: &[DataInitializer<'a>], + heap: &HeapSpec, +) -> Result { + let mut pagemap: HashMap> = HashMap::new(); + + for initializer in initializers { + if initializer.base.is_some() { + let message = + "cannot create sparse data: data initializer uses global as base".to_owned(); + Err(Error::Unsupported(message))?; + } + let chunks = split(initializer); + for (pagenumber, chunk) in chunks { + if pagenumber > heap.initial_size as usize / PAGE_SIZE { + Err(Error::InitData)?; + } + let base = chunk.offset as usize; + let page = match pagemap.entry(pagenumber) { + Entry::Occupied(occ) => occ.into_mut(), + Entry::Vacant(vac) => vac.insert(vec![0; PAGE_SIZE]), + }; + page[base..base + chunk.data.len()].copy_from_slice(chunk.data); + debug_assert!(page.len() == PAGE_SIZE); + } + } + + assert_eq!(heap.initial_size as usize % PAGE_SIZE, 0); + let highest_page = heap.initial_size as usize / PAGE_SIZE; + + let mut out = Vec::new(); + for page_ix in 0..highest_page { + if let Some(chunk) = pagemap.remove(&page_ix) { + assert!(chunk.len() == 4096); + out.push(Some(chunk)) + } else { + out.push(None) + } + } + assert_eq!(out.len() * 4096, heap.initial_size as usize); + let o = OwnedSparseData::new(out)?; + Ok(o) +} diff --git a/lucetc/src/compiler/stack_probe.rs b/lucetc/src/stack_probe.rs similarity index 67% rename from lucetc/src/compiler/stack_probe.rs rename to lucetc/src/stack_probe.rs index b7932fa8d..c062a3186 100644 --- a/lucetc/src/compiler/stack_probe.rs +++ b/lucetc/src/stack_probe.rs @@ -8,18 +8,23 @@ //! adding custom entries for it into the trap table, so that stack overflows in the probe will be //! treated like any other guest trap. +use crate::decls::ModuleDecls; +use crate::error::Error; +use crate::module::UniqueFuncIndex; use cranelift_codegen::binemit::TrapSink; use cranelift_codegen::ir; +use cranelift_codegen::ir::{types, AbiParam, Signature}; +use cranelift_codegen::isa::CallConv; use cranelift_faerie::traps::{FaerieTrapManifest, FaerieTrapSink}; use cranelift_faerie::FaerieProduct; +use cranelift_module::{Backend as ClifBackend, Linkage, Module as ClifModule}; use faerie::Decl; -use failure::Error; /// Stack probe symbol name pub const STACK_PROBE_SYM: &'static str = "lucet_probestack"; /// The binary of the stack probe. -const STACK_PROBE_BINARY: &'static [u8] = &[ +pub(crate) const STACK_PROBE_BINARY: &'static [u8] = &[ // 49 89 c3 mov %rax,%r11 // 48 81 ec 00 10 00 00 sub $0x1000,%rsp // 48 85 64 24 08 test %rsp,0x8(%rsp) @@ -35,12 +40,33 @@ const STACK_PROBE_BINARY: &'static [u8] = &[ 0x29, 0xdc, 0x48, 0x85, 0x64, 0x24, 0x08, 0x48, 0x01, 0xc4, 0xc3, ]; +pub fn declare_metadata<'a, B: ClifBackend>( + decls: &mut ModuleDecls<'a>, + clif_module: &mut ClifModule, +) -> Result { + Ok(decls + .declare_new_function( + clif_module, + STACK_PROBE_SYM.to_string(), + Linkage::Local, + Signature { + params: vec![], + returns: vec![AbiParam::new(types::I32)], + call_conv: CallConv::SystemV, // the stack probe function is very specific to x86_64, and possibly to SystemV ABI platforms? + }, + ) + .unwrap()) +} + pub fn declare_and_define(product: &mut FaerieProduct) -> Result<(), Error> { - product.artifact.declare_with( - STACK_PROBE_SYM, - Decl::function(), - STACK_PROBE_BINARY.to_vec(), - )?; + product + .artifact + .declare_with( + STACK_PROBE_SYM, + Decl::function(), + STACK_PROBE_BINARY.to_vec(), + ) + .map_err(|source| Error::Failure(source, "Stack probe error".to_owned()))?; add_sink( product .trap_manifest diff --git a/lucetc/src/table.rs b/lucetc/src/table.rs new file mode 100644 index 000000000..79130e9c5 --- /dev/null +++ b/lucetc/src/table.rs @@ -0,0 +1,154 @@ +use crate::decls::{ModuleDecls, TableDecl}; +use crate::error::Error; +use crate::module::UniqueFuncIndex; +use crate::name::Name; +use crate::pointer::NATIVE_POINTER_SIZE; +use byteorder::{LittleEndian, WriteBytesExt}; +use cranelift_codegen::entity::EntityRef; +use cranelift_module::{Backend as ClifBackend, DataContext, Module as ClifModule}; +use cranelift_wasm::{TableElementType, TableIndex}; +use faerie::{Artifact, Link}; +use std::io::Cursor; + +/// This symbol will be used to reference the `tables` field in `Module` - a sequence of tables. +/// At the moment it will either be one or no tables, but in the future may grow. +pub const TABLE_SYM: &str = "lucet_tables"; +/// This is functionally the size of `&[TableEntry]`, but defined here because it may not +/// necessarily have the same field ordering. +pub const TABLE_REF_SIZE: usize = NATIVE_POINTER_SIZE * 2; + +#[derive(Debug, Clone)] +enum Elem { + Func(UniqueFuncIndex), + Empty, +} + +fn table_elements(decl: &TableDecl<'_>) -> Result, Error> { + match decl.table.ty { + TableElementType::Func => Ok(()), + _ => { + let message = format!("table with non-function elements: {:?}", decl); + Err(Error::Unsupported(message)) + } + }?; + + let mut elems = Vec::new(); + + for initializer in decl.elems.iter() { + if initializer.base.is_some() { + let message = format!("table elements with global index base: {:?}", initializer); + Err(Error::Unsupported(message))? + } + let final_len = initializer.offset + initializer.elements.len(); + if final_len > elems.len() { + elems.resize(final_len, Elem::Empty); + } + for (ix, func_ix) in initializer.elements.iter().enumerate() { + elems[initializer.offset + ix] = Elem::Func(*func_ix); + } + } + + Ok(elems) +} + +pub fn link_tables(tables: &[Name], obj: &mut Artifact) -> Result<(), Error> { + for (idx, table) in tables.iter().enumerate() { + obj.link(Link { + from: TABLE_SYM, + to: table.symbol(), + at: (TABLE_REF_SIZE * idx) as u64, + }) + .map_err(|source| Error::Failure(source, "Table error".to_owned()))?; + } + Ok(()) +} + +pub fn write_table_data( + clif_module: &mut ClifModule, + decls: &ModuleDecls<'_>, +) -> Result, Error> { + let mut tables_vec = Cursor::new(Vec::new()); + let mut table_names: Vec = Vec::new(); + + if let Ok(table_decl) = decls.get_table(TableIndex::new(0)) { + // Indirect calls are performed by looking up the callee function and type in a table that + // is present in the same object file. + // The table is an array of pairs of (type index, function pointer). Both elements in the + // pair are the size of a pointer. + // This function creates that table as a section in the object. + let elements = table_elements(&table_decl)?; + + let mut table_data = + Cursor::new(Vec::with_capacity(elements.len() * 2 * NATIVE_POINTER_SIZE)); + fn putelem(table: &mut Cursor>, elem: u64) { + table.write_u64::(elem).unwrap() + } + + let mut table_ctx = DataContext::new(); + + // table.elems is a vector that gives every entry of the table, either specifying the + // wasm function index or that no element was given for that table entry. + for elem in elements.iter() { + match elem { + Elem::Func(func_index) => { + // Note: this is the only place we validate that the table entry points to a valid + // function. If this is ever removed, make sure this check happens elsewhere. + if let Some(func) = decls.get_func(*func_index) { + // First element in row is the SignatureIndex for the function + putelem(&mut table_data, func.signature_index.as_u32() as u64); + + // Second element in row is the pointer to the function. The Reloc is doing the work + // here. We put a 0 in the table data itself to be overwritten at link time. + let funcref = table_ctx.import_function(func.name.into()); + let position = table_data.position(); + assert!(position < ::max_value() as u64); + table_ctx.write_function_addr(position as u32, funcref); + putelem(&mut table_data, 0); + } else { + let message = format!("{:?}", func_index); + return Err(Error::FunctionIndexError(message)); + } + } + // EMPTY: + Elem::Empty => { + // First element is the signature index. These will always be 32 bits in wasm, so + // u64::max will always be out of bounds. + putelem(&mut table_data, ::max_value()); + // Second element is the function pointer. No relocation here, it will always be + // null. + putelem(&mut table_data, 0); + } + } + } + table_ctx.define(table_data.into_inner().into_boxed_slice()); + let table_id = table_decl + .contents_name + .as_dataid() + .expect("tables are data"); + clif_module.define_data(table_id, &table_ctx)?; + + // have to link TABLE_SYM, table_id, + // add space for the TABLE_SYM pointer + tables_vec.write_u64::(0).unwrap(); + + // Define the length of the table as a u64: + tables_vec + .write_u64::(elements.len() as u64) + .unwrap(); + table_names.push(table_decl.contents_name.clone()) + } + + let inner = tables_vec.into_inner(); + + let mut table_data_ctx = DataContext::new(); + table_data_ctx.define(inner.into_boxed_slice()); + + clif_module.define_data( + decls + .get_tables_list_name() + .as_dataid() + .expect("lucet_tables is declared as data"), + &table_data_ctx, + )?; + Ok(table_names) +} diff --git a/lucetc/src/traps.rs b/lucetc/src/traps.rs new file mode 100644 index 000000000..5cdd34e47 --- /dev/null +++ b/lucetc/src/traps.rs @@ -0,0 +1,73 @@ +use crate::error::Error; + +use cranelift_codegen::ir; +use cranelift_faerie::traps::FaerieTrapManifest; + +use faerie::{Artifact, Decl}; +use lucet_module::TrapSite; + +pub fn write_trap_tables(manifest: &FaerieTrapManifest, obj: &mut Artifact) -> Result<(), Error> { + for sink in manifest.sinks.iter() { + let func_sym = &sink.name; + let trap_sym = trap_sym_for_func(func_sym); + + obj.declare(&trap_sym, Decl::data()).map_err(|source| { + let message = format!("Trap table error declaring {}", trap_sym); + Error::ArtifactError(source, message) + })?; + + // write the actual function-level trap table + let traps: Vec = sink + .sites + .iter() + .map(|site| TrapSite { + offset: site.offset, + code: translate_trapcode(site.code), + }) + .collect(); + + let trap_site_bytes = unsafe { + std::slice::from_raw_parts( + traps.as_ptr() as *const u8, + traps.len() * std::mem::size_of::(), + ) + }; + + // and write the function trap table into the object + obj.define(&trap_sym, trap_site_bytes.to_vec()) + .map_err(|source| { + let message = format!("Trap table error defining {}", trap_sym); + Error::ArtifactError(source, message) + })?; + } + + Ok(()) +} + +pub(crate) fn trap_sym_for_func(sym: &str) -> String { + return format!("lucet_trap_table_{}", sym); +} + +// Trapcodes can be thought of as a tuple of (type, subtype). Each are +// represented as a 16-bit unsigned integer. These are packed into a u32 +// wherein the type occupies the low 16 bites and the subtype takes the +// high bits. +// +// Not all types have subtypes. Currently, only the user User type has a +// subtype. +fn translate_trapcode(code: ir::TrapCode) -> lucet_module::TrapCode { + match code { + ir::TrapCode::StackOverflow => lucet_module::TrapCode::StackOverflow, + ir::TrapCode::HeapOutOfBounds => lucet_module::TrapCode::HeapOutOfBounds, + ir::TrapCode::OutOfBounds => lucet_module::TrapCode::OutOfBounds, + ir::TrapCode::IndirectCallToNull => lucet_module::TrapCode::IndirectCallToNull, + ir::TrapCode::BadSignature => lucet_module::TrapCode::BadSignature, + ir::TrapCode::IntegerOverflow => lucet_module::TrapCode::IntegerOverflow, + ir::TrapCode::IntegerDivisionByZero => lucet_module::TrapCode::IntegerDivByZero, + ir::TrapCode::BadConversionToInteger => lucet_module::TrapCode::BadConversionToInteger, + ir::TrapCode::Interrupt => lucet_module::TrapCode::Interrupt, + ir::TrapCode::TableOutOfBounds => lucet_module::TrapCode::TableOutOfBounds, + ir::TrapCode::UnreachableCodeReached => lucet_module::TrapCode::Unreachable, + ir::TrapCode::User(_) => panic!("we should never emit a user trapcode"), + } +} diff --git a/lucetc/src/types.rs b/lucetc/src/types.rs new file mode 100644 index 000000000..ee4957b3c --- /dev/null +++ b/lucetc/src/types.rs @@ -0,0 +1,111 @@ +use cranelift_codegen::ir; +use lucet_module::Signature; +use lucet_module::ValueType; +use std::fmt::{self, Display}; +use thiserror::Error; + +#[derive(Debug)] +pub enum ValueError { + Unrepresentable, + InvalidVMContext, +} + +fn to_lucet_value(value: &ir::AbiParam) -> Result { + match value { + ir::AbiParam { + value_type: cranelift_ty, + purpose: ir::ArgumentPurpose::Normal, + extension: ir::ArgumentExtension::None, + location: ir::ArgumentLoc::Unassigned, + } => { + let size = cranelift_ty.bits(); + + if cranelift_ty.is_int() { + match size { + 32 => Ok(ValueType::I32), + 64 => Ok(ValueType::I64), + _ => Err(ValueError::Unrepresentable), + } + } else if cranelift_ty.is_float() { + match size { + 32 => Ok(ValueType::F32), + 64 => Ok(ValueType::F64), + _ => Err(ValueError::Unrepresentable), + } + } else { + Err(ValueError::Unrepresentable) + } + } + _ => Err(ValueError::Unrepresentable), + } +} + +#[derive(Debug, Error)] +pub enum SignatureError { + BadElement(ir::AbiParam, ValueError), + BadSignature, +} + +impl Display for SignatureError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt(&self, f) + } +} + +pub fn to_lucet_signature(value: &ir::Signature) -> Result { + let mut params: Vec = Vec::new(); + + let mut param_iter = value.params.iter(); + + // Enforce that the first parameter is VMContext, as Signature assumes. + // Even functions declared no-arg take VMContext in reality. + if let Some(param) = param_iter.next() { + match ¶m { + ir::AbiParam { + value_type: value, + purpose: ir::ArgumentPurpose::VMContext, + extension: ir::ArgumentExtension::None, + location: ir::ArgumentLoc::Unassigned, + } => { + if value.is_int() && value.bits() == 64 { + // this is VMContext, so we can move on. + } else { + return Err(SignatureError::BadElement( + param.to_owned(), + ValueError::InvalidVMContext, + )); + } + } + _ => { + return Err(SignatureError::BadElement( + param.to_owned(), + ValueError::InvalidVMContext, + )); + } + } + } else { + return Err(SignatureError::BadSignature); + } + + for param in param_iter { + let value_ty = + to_lucet_value(param).map_err(|e| SignatureError::BadElement(param.clone(), e))?; + + params.push(value_ty); + } + + let ret_ty: Option = match value.returns.as_slice() { + &[] => None, + &[ref ret_ty] => { + let value_ty = to_lucet_value(ret_ty) + .map_err(|e| SignatureError::BadElement(ret_ty.clone(), e))?; + + Some(value_ty) + } + _ => { + return Err(SignatureError::BadSignature); + } + }; + + Ok(Signature { params, ret_ty }) +} diff --git a/lucetc/tests/bindings/duplicate_imports_bindings.json b/lucetc/tests/bindings/duplicate_imports_bindings.json new file mode 100644 index 000000000..207089019 --- /dev/null +++ b/lucetc/tests/bindings/duplicate_imports_bindings.json @@ -0,0 +1,6 @@ +{ + "env": { + "read": "host_read", + "write": "host_write" + } +} diff --git a/lucetc/tests/harnesses/call.c b/lucetc/tests/harnesses/call.c deleted file mode 100644 index ca6692326..000000000 --- a/lucetc/tests/harnesses/call.c +++ /dev/null @@ -1,10 +0,0 @@ -#include "vm.h" - -void guest_func_main(struct vmctx *); - -int main() -{ - struct VM *vm = make_vm(); - guest_func_main(get_vmctx(vm)); - return 0; -} diff --git a/lucetc/tests/harnesses/current_memory.c b/lucetc/tests/harnesses/current_memory.c deleted file mode 100644 index d5aab68fc..000000000 --- a/lucetc/tests/harnesses/current_memory.c +++ /dev/null @@ -1,25 +0,0 @@ -#include "vm.h" -#include -#include - -void guest_func_main(struct vmctx *); - -bool got_call = false; - -uint32_t lucet_vmctx_current_memory(struct vmctx *unused) -{ - got_call = true; - return 1234; -} - -int main() -{ - struct VM *vm = make_vm(); - guest_func_main(get_vmctx(vm)); - - uint32_t res = *(uint32_t *) vm->heap; - assert(got_call); - assert(res == 1234); - - return 0; -} diff --git a/lucetc/tests/harnesses/data_segment.c b/lucetc/tests/harnesses/data_segment.c deleted file mode 100644 index f6fdf1486..000000000 --- a/lucetc/tests/harnesses/data_segment.c +++ /dev/null @@ -1,92 +0,0 @@ -#include "vm.h" -#include -#include -#include -#include - -// IMPORTANT: this is a copy of the definition of this struct in liblucet. -// If changes are made to this, make sure they are also made in liblucet! -struct wasm_data_segment { - uint32_t memory_index; - uint32_t offset; - uint32_t length; - char data[]; -}; - -void guest_func_main(struct vmctx *); - -extern char wasm_data_segments[]; -extern int wasm_data_segments_len; - -int main() -{ - // Run guest program and ensure the result is correct (see corresponding .wat - // for details) - struct VM *vm = make_vm(); - guest_func_main(get_vmctx(vm)); - - uint32_t output = ((uint32_t *) vm->heap)[0]; - uint32_t expected_output = 0; - if (output != expected_output) { - printf("Output was %u\n", output); - return 1; - } - - // Define expected data segment initialization data - - int n_expected = 3; - struct wasm_data_segment *expected[n_expected]; - - const char bytes_0[] = { '9', '9', '9', '9', '9' }; - struct wasm_data_segment *ds = malloc(sizeof(struct wasm_data_segment) + sizeof(bytes_0)); - memcpy(ds, - &(struct wasm_data_segment const){ - .memory_index = 0, .offset = 0, .length = sizeof(bytes_0) }, - sizeof(struct wasm_data_segment)); - memcpy((char *) ds + sizeof(struct wasm_data_segment), bytes_0, sizeof(bytes_0)); - expected[0] = ds; - - const char bytes_1[] = { 0xaa, 0xbb }; // see .wat for defn - ds = malloc(sizeof(struct wasm_data_segment) + sizeof(bytes_1)); - memcpy(ds, - &(struct wasm_data_segment const){ - .memory_index = 0, .offset = 0, .length = sizeof(bytes_1) }, - sizeof(struct wasm_data_segment)); - memcpy((char *) ds + sizeof(struct wasm_data_segment), bytes_1, sizeof(bytes_1)); - expected[1] = ds; - - const char bytes_2[] = { 0xcc, 0xdd }; // see .wat for defn - ds = malloc(sizeof(struct wasm_data_segment) + sizeof(bytes_2)); - memcpy(ds, - &(struct wasm_data_segment const){ - .memory_index = 0, .offset = 1, .length = sizeof(bytes_2) }, - sizeof(struct wasm_data_segment)); - memcpy((char *) ds + sizeof(struct wasm_data_segment), bytes_2, sizeof(bytes_2)); - expected[2] = ds; - - // Make sure data segment initialization data in ELF matches expectation - - int i = 0; // current data segment - int p = 0; // current position in wasm_data_segment - while (p < wasm_data_segments_len) { - struct wasm_data_segment *deserialized = - (struct wasm_data_segment *) &wasm_data_segments[p]; - assert(deserialized->length == expected[i]->length); - assert(deserialized->memory_index == expected[i]->memory_index); - assert(deserialized->offset == expected[i]->offset); - - int j; - for (j = 0; j < deserialized->length; j++) { - assert(deserialized->data[j] == expected[i]->data[j]); - } - p += sizeof(struct wasm_data_segment) + deserialized->length; - p += (8 - p % 8) % 8; - i += 1; - } - - for (i = 0; i < n_expected; i++) { - free(expected[i]); - } - - return 0; -} diff --git a/lucetc/tests/harnesses/fibonacci.c b/lucetc/tests/harnesses/fibonacci.c deleted file mode 100644 index 008d44148..000000000 --- a/lucetc/tests/harnesses/fibonacci.c +++ /dev/null @@ -1,18 +0,0 @@ -#include "vm.h" - -void guest_func_main(struct vmctx *); - -int main() -{ - struct VM *vm = make_vm(); - guest_func_main(get_vmctx(vm)); - - // fibonacci.wat writes the result of to mem location 0 as an i32. - uint32_t output = ((uint32_t *) vm->heap)[0]; - uint32_t expected = 21; - if (output != expected) { - printf("Output was %u\n", output); - return 1; - } - return 0; -} diff --git a/lucetc/tests/harnesses/globals.c b/lucetc/tests/harnesses/globals.c deleted file mode 100644 index 2090138b6..000000000 --- a/lucetc/tests/harnesses/globals.c +++ /dev/null @@ -1,21 +0,0 @@ - -#include "globals.h" -#include -#include - -void initialize_globals(struct VM *vm, struct global_table const *tbl) -{ - uint64_t num = tbl->num_descriptors; - assert(num < GLOBALS_SIZE); - - for (int i = 0; i < num; i++) { - struct global_description *descriptor = - (struct global_description *) ((uintptr_t) tbl + sizeof(uint64_t) + - (i * sizeof(struct global_description))); - if (!(descriptor->flags & GLOBALS_FLAG_IMPORT)) { - vm->globals[i] = descriptor->initial_value; - } else { - errx(1, "%s() unit testing of imports is not supported", __FUNCTION__); - } - } -} diff --git a/lucetc/tests/harnesses/globals.h b/lucetc/tests/harnesses/globals.h deleted file mode 100644 index f597373e8..000000000 --- a/lucetc/tests/harnesses/globals.h +++ /dev/null @@ -1,28 +0,0 @@ - -#ifndef GLOBALS_H -#define GLOBALS_H - -#include "vm.h" - -// Each global is either an internal definition (which has an initial value), or -// an import, whose initial value is determined elsewhere. The 0th bit is set -// when it is an import. -#define GLOBALS_FLAG_IMPORT (1 << 0) -// Each global that is an import, and some globals that are internally defined, -// have a name. This bit is set when the char* name field points to a valid -// name. -#define GLOBALS_FLAG_VALID_NAME (1 << 1) - -struct global_description { - uint64_t flags; - int64_t initial_value; - char * name; -}; - -struct global_table { - uint64_t num_descriptors; -}; - -void initialize_globals(struct VM *, struct global_table const *); - -#endif // GLOBALS_H diff --git a/lucetc/tests/harnesses/globals_definition.c b/lucetc/tests/harnesses/globals_definition.c deleted file mode 100644 index d9e4aa2ae..000000000 --- a/lucetc/tests/harnesses/globals_definition.c +++ /dev/null @@ -1,36 +0,0 @@ - -#include "globals.h" -#include "vm.h" -#include -#include - -extern const struct global_table lucet_globals_spec; - -void guest_func_main(struct vmctx *); - -int main() -{ - struct VM *vm = make_vm(); - - assert(vm->globals[0] == 0); - assert(vm->globals[1] == 0); - assert(vm->globals[2] == 0); - - initialize_globals(vm, &lucet_globals_spec); - - assert(vm->globals[0] == 4); - assert(vm->globals[1] == 5); - assert(vm->globals[2] == 6); - - guest_func_main(get_vmctx(vm)); - - assert(vm->globals[0] == 3); - assert(vm->globals[1] == 2); - - uint32_t *heap_globals = (uint32_t *) vm->heap; - assert(heap_globals[0] == 4); - assert(heap_globals[1] == 5); - assert(heap_globals[2] == 6); - - return 0; -} diff --git a/lucetc/tests/harnesses/grow_memory.c b/lucetc/tests/harnesses/grow_memory.c deleted file mode 100644 index 599de80f7..000000000 --- a/lucetc/tests/harnesses/grow_memory.c +++ /dev/null @@ -1,28 +0,0 @@ -#include "vm.h" -#include -#include - -void guest_func_main(struct vmctx *); - -uint32_t arg = 0; -bool got_call = false; - -uint32_t lucet_vmctx_grow_memory(struct VM *unused, uint32_t pages) -{ - got_call = true; - arg = pages; - return 1234; -} - -int main() -{ - struct VM *vm = make_vm(); - guest_func_main(get_vmctx(vm)); - - uint32_t res = *(uint32_t *) vm->heap; - assert(got_call); - assert(res == 1234); - assert(arg = 5678); - - return 0; -} diff --git a/lucetc/tests/harnesses/heap_spec.h b/lucetc/tests/harnesses/heap_spec.h deleted file mode 100644 index 2b47f5ae3..000000000 --- a/lucetc/tests/harnesses/heap_spec.h +++ /dev/null @@ -1,12 +0,0 @@ - -#include - -struct lucet_heap_spec { - uint64_t reserved_size; - uint64_t guard_size; - uint64_t initial_size; - uint64_t max_size; - uint64_t max_size_valid; // Just a boolean -}; - -extern struct lucet_heap_spec lucet_heap_spec; diff --git a/lucetc/tests/harnesses/heap_spec_definition.c b/lucetc/tests/harnesses/heap_spec_definition.c deleted file mode 100644 index 583fa5839..000000000 --- a/lucetc/tests/harnesses/heap_spec_definition.c +++ /dev/null @@ -1,14 +0,0 @@ -#include "heap_spec.h" -#include - -int main() -{ - // These constants should match up with the unit test in tests/wasm.rs - assert(lucet_heap_spec.reserved_size == 4 * 1024 * 1024); - assert(lucet_heap_spec.guard_size == 4 * 1024 * 1024); - assert(lucet_heap_spec.initial_size == 5 * 64 * 1024); - assert(lucet_heap_spec.max_size == 0); - assert(lucet_heap_spec.max_size_valid == 0); - - return 0; -} diff --git a/lucetc/tests/harnesses/heap_spec_import.c b/lucetc/tests/harnesses/heap_spec_import.c deleted file mode 100644 index 16284b4dc..000000000 --- a/lucetc/tests/harnesses/heap_spec_import.c +++ /dev/null @@ -1,16 +0,0 @@ -#include -#include - -#include "heap_spec.h" - -int main() -{ - // These constants should match up with the unit test in tests/wasm.rs - assert(lucet_heap_spec.reserved_size == 4 * 1024 * 1024); - assert(lucet_heap_spec.guard_size == (4 * 1024 * 1024)); - assert(lucet_heap_spec.initial_size == (6 * 64 * 1024)); - assert(lucet_heap_spec.max_size == (10 * 64 * 1024)); - assert(lucet_heap_spec.max_size_valid == 1); - - return 0; -} diff --git a/lucetc/tests/harnesses/icall.c b/lucetc/tests/harnesses/icall.c deleted file mode 100644 index 6212d7f3a..000000000 --- a/lucetc/tests/harnesses/icall.c +++ /dev/null @@ -1,43 +0,0 @@ -#include "vm.h" -#include -#include -#include - -uint32_t guest_func_foo(struct vmctx *, uint32_t icall_dest); - -struct table_elem { - uint64_t type_tag; - void * function_ptr; -}; -extern struct table_elem guest_table_0[]; -extern uint8_t guest_table_0_len[8]; - -int main() -{ - struct VM *vm = make_vm(); - uint32_t res_0 = guest_func_foo(get_vmctx(vm), 0); - uint32_t res_1 = guest_func_foo(get_vmctx(vm), 1); - assert(res_0 == 1); - assert(res_1 == 2); - - // Check table length - assert(guest_table_0_len[0] == 3 * 2 * 8); - assert(guest_table_0_len[1] == 0); - assert(guest_table_0_len[2] == 0); - assert(guest_table_0_len[3] == 0); - assert(guest_table_0_len[4] == 0); - assert(guest_table_0_len[5] == 0); - assert(guest_table_0_len[6] == 0); - assert(guest_table_0_len[7] == 0); - - // Table functions 0 and 1 have the same type. Function 2 has a different type. - assert(guest_table_0[0].type_tag == guest_table_0[1].type_tag); - assert(guest_table_0[0].type_tag != guest_table_0[2].type_tag); - - // All table functions point to a valid function - assert(guest_table_0[0].function_ptr != NULL); - assert(guest_table_0[1].function_ptr != NULL); - assert(guest_table_0[2].function_ptr != NULL); - - return 0; -} diff --git a/lucetc/tests/harnesses/icall_import.c b/lucetc/tests/harnesses/icall_import.c deleted file mode 100644 index 87596f568..000000000 --- a/lucetc/tests/harnesses/icall_import.c +++ /dev/null @@ -1,48 +0,0 @@ -#include "vm.h" -#include -#include -#include -#include - -uint32_t guest_func_launchpad(struct vmctx *, uint32_t icall_dest, uint32_t input_a, - uint32_t input_b); - -bool expect_icall_to_env = false; -struct vmctx *expected_vmctx = NULL; - -uint32_t icalltarget(struct vmctx *ctx, uint32_t input_a, uint32_t input_b) -{ - assert(expect_icall_to_env); - assert(ctx == expected_vmctx); - return input_a * input_b; -} - -int main() -{ - struct VM *vm = make_vm(); - - // Table entry 0 adds the two inputs - uint32_t res_0 = guest_func_launchpad(get_vmctx(vm), 0, 123, 456); - assert(res_0 == (123 + 456)); - - uint32_t res_1 = guest_func_launchpad(get_vmctx(vm), 0, 789, 10); - assert(res_1 == (789 + 10)); - - // Table entry 1 subtracts the two inputs - uint32_t res_2 = guest_func_launchpad(get_vmctx(vm), 1, 123, 456); - assert(res_2 == (123 - 456)); - - uint32_t res_3 = guest_func_launchpad(get_vmctx(vm), 1, 789, 10); - assert(res_3 == (789 - 10)); - - // table entry #2 has wrong type. - - // Table entry 3 should call `icalltarget` above. That function multiplies - // its two inputs. - expect_icall_to_env = true; - expected_vmctx = get_vmctx(vm); - uint32_t res_4 = guest_func_launchpad(get_vmctx(vm), 3, 123, 456); - assert(res_4 == (123 * 456)); - - return 0; -} diff --git a/lucetc/tests/harnesses/icall_sparse.c b/lucetc/tests/harnesses/icall_sparse.c deleted file mode 100644 index 26808b18c..000000000 --- a/lucetc/tests/harnesses/icall_sparse.c +++ /dev/null @@ -1,40 +0,0 @@ -#include "vm.h" -#include -#include -#include - -uint32_t guest_func_foo(struct vmctx *, uint32_t icall_dest); - -struct table_elem { - uint64_t type_tag; - void * function_ptr; -}; -extern struct table_elem guest_table_0[]; - -int main() -{ - struct VM *vm = make_vm(); - uint32_t res_0 = guest_func_foo(get_vmctx(vm), 1); - uint32_t res_1 = guest_func_foo(get_vmctx(vm), 2); - assert(res_0 == 1); - assert(res_1 == 2); - - // Table elements 0, 4, and 5 are empty. - assert(guest_table_0[0].type_tag == UINT64_MAX); - assert(guest_table_0[0].function_ptr == NULL); - assert(guest_table_0[4].type_tag == UINT64_MAX); - assert(guest_table_0[4].function_ptr == NULL); - assert(guest_table_0[5].type_tag == UINT64_MAX); - assert(guest_table_0[5].function_ptr == NULL); - - // Table functions 1 and 2 have the same type. Function 3 has a different type. - assert(guest_table_0[1].type_tag == guest_table_0[2].type_tag); - assert(guest_table_0[1].type_tag != guest_table_0[3].type_tag); - - // Non-empty table functions point to a valid function - assert(guest_table_0[1].function_ptr != NULL); - assert(guest_table_0[2].function_ptr != NULL); - assert(guest_table_0[3].function_ptr != NULL); - - return 0; -} diff --git a/lucetc/tests/harnesses/import_many.c b/lucetc/tests/harnesses/import_many.c deleted file mode 100644 index 7db33dd26..000000000 --- a/lucetc/tests/harnesses/import_many.c +++ /dev/null @@ -1,44 +0,0 @@ -#include "vm.h" -#include - -void guest_func_main(struct vmctx *); - -uint32_t stage; - -uint32_t imp_0(void) -{ - assert(stage == 0); - stage = 1; - return 1; -} - -uint32_t imp_1(void) -{ - assert(stage == 1); - stage = 2; - return 2; -} - -uint32_t imp_2(void) -{ - assert(stage == 2); - stage = 3; - return 3; -} - -uint32_t imp_3(void) -{ - assert(stage == 3); - stage = 4; - return 4; -} - -int main() -{ - struct VM *vm = make_vm(); - stage = 0; - guest_func_main(get_vmctx(vm)); - assert(stage == 4); - - return 0; -} diff --git a/lucetc/tests/harnesses/locals.c b/lucetc/tests/harnesses/locals.c deleted file mode 100644 index f5ecdc54c..000000000 --- a/lucetc/tests/harnesses/locals.c +++ /dev/null @@ -1,16 +0,0 @@ -#include "vm.h" - -uint32_t guest_func_main(struct vmctx *); - -int main() -{ - struct VM *vm = make_vm(); - uint32_t ret = guest_func_main(get_vmctx(vm)); - uint32_t expected = 74; - if (ret != expected) { - printf("Output was %u, expected %u\n", ret, expected); - return 1; - } - - return 0; -} diff --git a/lucetc/tests/harnesses/locals_csr.c b/lucetc/tests/harnesses/locals_csr.c deleted file mode 100644 index 5d575d88e..000000000 --- a/lucetc/tests/harnesses/locals_csr.c +++ /dev/null @@ -1,16 +0,0 @@ -#include "vm.h" - -uint32_t guest_func_main(struct vmctx *); - -int main() -{ - struct VM *vm = make_vm(); - uint32_t ret = guest_func_main(get_vmctx(vm)); - uint32_t expected = 34; - if (ret != expected) { - printf("Output was %u, expected %u\n", ret, expected); - return 1; - } - - return 0; -} diff --git a/lucetc/tests/harnesses/strstr.c b/lucetc/tests/harnesses/strstr.c deleted file mode 100644 index b11c8172e..000000000 --- a/lucetc/tests/harnesses/strstr.c +++ /dev/null @@ -1,11 +0,0 @@ -#include "vm.h" - -void guest_func_strstr(struct vmctx *); - -int main() -{ - struct VM *vm = make_vm(); - guest_func_strstr(get_vmctx(vm), 0, 0, 0); - - return 0; -} diff --git a/lucetc/tests/harnesses/vm.c b/lucetc/tests/harnesses/vm.c deleted file mode 100644 index 0f95e4e3f..000000000 --- a/lucetc/tests/harnesses/vm.c +++ /dev/null @@ -1,40 +0,0 @@ -#include "vm.h" -#include -#include -#include - -static struct VM vm; - -struct VM *make_vm(void) -{ - memset(vm.heap, 0, HEAP_SIZE); - memset(vm.heap, 0, GLOBALS_SIZE * sizeof(int64_t)); - vm.global_ptr = vm.globals; - return &vm; -} - -struct vmctx *get_vmctx(struct VM *the_vm) -{ - return (struct vmctx *) &the_vm->heap; -} - -struct VM *get_vm(struct vmctx *the_vmctx) -{ - assert(the_vmctx == get_vmctx(&vm)); - return &vm; -} - -uint32_t lucet_vmctx_grow_memory(struct vmctx *ctx, uint32_t pages) __attribute__((weak)); -uint32_t lucet_vmctx_grow_memory(struct vmctx *ctx, uint32_t pages) -{ - assert(get_vm(ctx) == &vm); - (void) pages; - return 0; -} - -uint32_t lucet_vmctx_current_memory(struct vmctx *ctx) __attribute__((weak)); -uint32_t lucet_vmctx_current_memory(struct vmctx *ctx) -{ - assert(get_vm(ctx) == &vm); - return 1; -} diff --git a/lucetc/tests/harnesses/vm.h b/lucetc/tests/harnesses/vm.h deleted file mode 100644 index 43e410d61..000000000 --- a/lucetc/tests/harnesses/vm.h +++ /dev/null @@ -1,24 +0,0 @@ - -#ifndef VM_H -#define VM_H - -#include - -#define GLOBALS_SIZE 128 -#define HEAP_SIZE (64 * 1024) - -struct VM { - int64_t globals[GLOBALS_SIZE]; - int64_t *global_ptr; // This should be initialized to point at &vm.globals - char heap[HEAP_SIZE]; -}; - -struct VM *make_vm(void); - -struct vmctx; - -struct vmctx *get_vmctx(struct VM *); - -struct VM *get_vm(struct vmctx *); - -#endif // VM_H diff --git a/lucetc/tests/wasi-sdk.rs b/lucetc/tests/wasi-sdk.rs deleted file mode 100644 index 88fe9acfa..000000000 --- a/lucetc/tests/wasi-sdk.rs +++ /dev/null @@ -1,120 +0,0 @@ -use failure::{Error, ResultExt}; -use lucet_wasi_sdk::Link; -use lucetc::bindings::Bindings; -use lucetc::load; -use parity_wasm::elements::Module; -use std::collections::HashMap; -use std::path::PathBuf; -use std::str; -use tempfile; - -fn module_from_c(cfiles: &[&str], exports: &[&str]) -> Result { - let cfiles: Vec = cfiles - .iter() - .map(|ref name| PathBuf::from(format!("tests/wasi-sdk/{}.c", name))) - .collect(); - let tempdir = tempfile::Builder::new() - .prefix("wasi-sdk-test") - .tempdir() - .context("tempdir creation")?; - - let mut wasm = PathBuf::from(tempdir.path()); - wasm.push("out.wasm"); - - let mut linker = Link::new(&cfiles) - .cflag("-nostartfiles") - .ldflag("--no-entry") - .ldflag("--allow-undefined"); - for export in exports { - linker.with_ldflag(&format!("--export={}", export)); - } - linker.link(wasm.clone())?; - - let m = load::read_module(&wasm).context(format!("loading module built from {:?}", cfiles))?; - Ok(m) -} - -fn b_only_test_bindings() -> Bindings { - let imports: HashMap = [ - ("a".into(), "a".into()), // b_only - ] - .iter() - .cloned() - .collect(); - - Bindings::env(imports) -} - -mod programs { - use super::{b_only_test_bindings, module_from_c}; - use cranelift_module::Linkage; - use lucetc::bindings::Bindings; - use lucetc::compile; - use lucetc::compiler::OptLevel; - use lucetc::program::{HeapSettings, Program}; - - fn num_import_globals(p: &Program) -> usize { - p.globals() - .iter() - .filter_map(|g| g.as_import()) - .collect::>() - .len() - } - - fn num_export_functions(p: &Program) -> usize { - p.defined_functions() - .iter() - .filter(|f| f.linkage() == Linkage::Export) - .collect::>() - .len() - } - - #[test] - fn empty() { - let m = module_from_c(&["empty"], &[]).expect("build module for empty"); - let b = Bindings::empty(); - let h = HeapSettings::default(); - let p = Program::new(m, b, h).expect("create program for empty"); - assert_eq!(p.import_functions().len(), 0, "import functions"); - assert_eq!(num_import_globals(&p), 0, "import globals"); - assert_eq!(num_export_functions(&p), 0, "export functions"); - let _c = compile(&p, "empty".into(), OptLevel::Best).expect("compile empty"); - } - - #[test] - fn just_a() { - let m = module_from_c(&["a"], &["a"]).expect("build module for a"); - let b = Bindings::empty(); - let h = HeapSettings::default(); - let p = Program::new(m, b, h).expect("create program for a"); - assert_eq!(p.import_functions().len(), 0, "import functions"); - assert_eq!(num_import_globals(&p), 0, "import globals"); - assert_eq!(num_export_functions(&p), 1, "export functions"); - let _c = compile(&p, "a_only".into(), OptLevel::Best).expect("compile a"); - } - - #[test] - fn just_b() { - let m = module_from_c(&["b"], &["b"]).expect("build module for b"); - let b = b_only_test_bindings(); - let h = HeapSettings::default(); - let p = Program::new(m, b, h).expect("create program for b"); - assert_eq!(p.import_functions().len(), 1, "import functions"); - assert_eq!(num_import_globals(&p), 0, "import globals"); - assert_eq!(num_export_functions(&p), 1, "export functions"); - let _c = compile(&p, "b_only".into(), OptLevel::Best).expect("compile b"); - } - - #[test] - fn a_and_b() { - let m = module_from_c(&["a", "b"], &["a", "b"]).expect("build module for a & b"); - let b = Bindings::empty(); - let h = HeapSettings::default(); - let p = Program::new(m, b, h).expect("create program for a & b"); - assert_eq!(p.import_functions().len(), 0, "import functions"); - assert_eq!(num_import_globals(&p), 0, "import globals"); - assert_eq!(num_export_functions(&p), 2, "export functions"); - let _c = compile(&p, "a_and_b".into(), OptLevel::Best).expect("compile a & b"); - } - -} diff --git a/lucetc/tests/wasi-sdk/b.c b/lucetc/tests/wasi-sdk/b.c deleted file mode 100644 index e0f9c0371..000000000 --- a/lucetc/tests/wasi-sdk/b.c +++ /dev/null @@ -1,7 +0,0 @@ - -extern int a(int); - -int b(int arg) -{ - return 3 * a(arg); -} diff --git a/lucetc/tests/wasi-sdk/main_returns.c b/lucetc/tests/wasi-sdk/main_returns.c deleted file mode 100644 index 0bbc1f069..000000000 --- a/lucetc/tests/wasi-sdk/main_returns.c +++ /dev/null @@ -1,5 +0,0 @@ - -void main(void) -{ - return; -} diff --git a/lucetc/tests/wasm.rs b/lucetc/tests/wasm.rs index bf896b390..02ad271cb 100644 --- a/lucetc/tests/wasm.rs +++ b/lucetc/tests/wasm.rs @@ -1,15 +1,24 @@ -use lucetc::bindings::Bindings; -use lucetc::load; -use parity_wasm::elements::Module; +use lucet_module::bindings::Bindings; use std::collections::HashMap; use std::path::PathBuf; -fn load(name: &str) -> Module { +fn load_wat_module(name: &str) -> Vec { + use std::fs::File; + use std::io::Read; + use wabt::Wat2Wasm; let watfile = PathBuf::from(&format!("tests/wasm/{}.wat", name)); - load::read_module(&watfile).expect(&format!("loading module from {:?}", watfile)) + let mut contents = Vec::new(); + let mut file = File::open(&watfile).expect("open module file"); + file.read_to_end(&mut contents).expect("read module file"); + Wat2Wasm::new() + .write_debug_names(true) + .convert(contents) + .expect("convert module to wasm binary format") + .as_ref() + .to_owned() } -fn test_bindings() -> Bindings { +pub fn test_bindings() -> Bindings { let imports: HashMap = [ ("icalltarget".into(), "icalltarget".into()), // icall_import ("inc".into(), "inc".into()), // import @@ -17,6 +26,7 @@ fn test_bindings() -> Bindings { ("imp_1".into(), "imp_1".into()), // import_many ("imp_2".into(), "imp_2".into()), // import_many ("imp_3".into(), "imp_3".into()), // import_many + ("imported_main".into(), "imported_main".into()), // exported_import ] .iter() .cloned() @@ -25,65 +35,207 @@ fn test_bindings() -> Bindings { Bindings::env(imports) } -mod programs { - /// Tests of the `Program` datastructure. - use super::load; - use lucetc::bindings::Bindings; - use lucetc::program::{table::TableElem, HeapSettings, Program}; - use parity_wasm::elements::ValueType; +mod module_data { + /// Tests of the `ModuleData` generated by the lucetc Compiler + use super::load_wat_module; + use lucet_module::bindings::Bindings; + use lucetc::{Compiler, CpuFeatures, HeapSettings, OptLevel}; use std::path::PathBuf; + use target_lexicon::Triple; #[test] - fn fibonacci() { - let m = load("fibonacci"); + fn exported_import() { + let m = load_wat_module("exported_import"); let b = super::test_bindings(); let h = HeapSettings::default(); - let p = Program::new(m, b, h).expect(&format!("instantiating program")); + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &None, + ) + .expect("compiling exported_import"); + let mdata = c.module_data().unwrap(); + assert_eq!(mdata.globals_spec().len(), 0); + + assert_eq!(mdata.import_functions().len(), 2); + assert_eq!(mdata.export_functions().len(), 2); + assert_eq!(mdata.function_info().len(), 4); + // This ordering is actually arbitrary. Cranelift hoists additional declaration modifiers + // up to the function declaration. This means inc comes first, and main second, in + // `exported_import.wat`. + assert_eq!(mdata.export_functions()[0].names, vec!["exported_inc"]); + assert_eq!(mdata.export_functions()[1].names, vec!["exported_main"]); + } - assert_eq!(p.import_functions().len(), 0); - assert_eq!(p.globals().len(), 0); - assert_eq!(p.defined_functions().len(), 1); - assert_eq!( - p.defined_functions().get(0).unwrap().symbol(), - "guest_func_main" - ); + #[test] + fn multiple_import() { + let m = load_wat_module("multiple_import"); + let b = super::test_bindings(); + let h = HeapSettings::default(); + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &None, + ) + .expect("compiling multiple_import"); + let mdata = c.module_data().unwrap(); + assert_eq!(mdata.globals_spec().len(), 0); + + assert_eq!(mdata.import_functions().len(), 2); + assert_eq!(mdata.export_functions().len(), 1); + assert_eq!(mdata.function_info().len(), 4); + assert_eq!(mdata.export_functions()[0].names, vec!["exported_inc"]); + } + + #[test] + fn globals_export() { + let m = load_wat_module("globals_export"); + let b = super::test_bindings(); + let h = HeapSettings::default(); + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &None, + ) + .expect("compiling globals_export"); + let mdata = c.module_data().unwrap(); + + assert_eq!(mdata.globals_spec().len(), 1); + assert_eq!(mdata.globals_spec()[0].export_names(), &["start", "dupe"]); + + assert_eq!(mdata.import_functions().len(), 0); + assert_eq!(mdata.export_functions().len(), 0); + assert_eq!(mdata.function_info().len(), 2); + } + + #[test] + fn fibonacci() { + let m = load_wat_module("fibonacci"); + let b = super::test_bindings(); + let h = HeapSettings::default(); + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &None, + ) + .expect("compiling fibonacci"); + let mdata = c.module_data().unwrap(); + assert_eq!(mdata.globals_spec().len(), 0); + + assert_eq!(mdata.import_functions().len(), 0); + assert_eq!(mdata.function_info().len(), 3); + assert_eq!(mdata.export_functions()[0].names, vec!["main"]); } #[test] fn arith() { - let m = load("arith"); + let m = load_wat_module("arith"); let b = Bindings::empty(); let h = HeapSettings::default(); - let p = Program::new(m, b, h).expect(&format!("instantiating program")); + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &None, + ) + .expect("compiling arith"); + let mdata = c.module_data().unwrap(); + assert_eq!(mdata.globals_spec().len(), 0); + + assert_eq!(mdata.import_functions().len(), 0); + assert_eq!(mdata.function_info().len(), 3); + assert_eq!(mdata.export_functions()[0].names, vec!["main"]); + } - assert_eq!(p.import_functions().len(), 0); - assert_eq!(p.globals().len(), 0); - assert_eq!(p.defined_functions().len(), 1); - assert_eq!( - p.defined_functions().get(0).unwrap().symbol(), - "guest_func_main" - ); + #[test] + fn duplicate_imports() { + let m = load_wat_module("duplicate_imports"); + let b = Bindings::from_file(&PathBuf::from( + "tests/bindings/duplicate_imports_bindings.json", + )) + .unwrap(); + let h = HeapSettings::default(); + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &None, + ) + .expect("compile duplicate_imports"); + let mdata = c.module_data().unwrap(); + + assert_eq!(mdata.import_functions().len(), 2); + assert_eq!(mdata.import_functions()[0].module, "env"); + assert_eq!(mdata.import_functions()[0].name, "read"); + assert_eq!(mdata.import_functions()[1].module, "env"); + assert_eq!(mdata.import_functions()[1].name, "write"); + assert_eq!(mdata.function_info().len(), 5); + assert_eq!(mdata.function_info()[0].name, Some("host_read")); + assert_eq!(mdata.function_info()[1].name, Some("host_write")); + assert_eq!(mdata.function_info()[2].name, Some("guest_func__start")); + assert_eq!(mdata.export_functions().len(), 3); + assert_eq!(mdata.export_functions()[0].names, ["read_2", "read"]); + assert_eq!(mdata.export_functions()[2].names, ["_start"]); + assert_eq!(mdata.globals_spec().len(), 0); } #[test] fn icall_import() { - let m = load("icall_import"); + let m = load_wat_module("icall_import"); let b = Bindings::from_file(&PathBuf::from( "tests/bindings/icall_import_test_bindings.json", )) .unwrap(); let h = HeapSettings::default(); - let p = Program::new(m, b, h).expect(&format!("instantiating program")); - - assert_eq!(p.import_functions().len(), 1); - assert_eq!(p.import_functions()[0].module(), "env"); - assert_eq!(p.import_functions()[0].field(), "icalltarget"); - assert_eq!(p.globals().len(), 0); - assert_eq!(p.defined_functions().len(), 4); - assert_eq!( - p.defined_functions().get(0).unwrap().symbol(), - "guest_func_launchpad" - ); + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &None, + ) + .expect("compile icall"); + let mdata = c.module_data().unwrap(); + + assert_eq!(mdata.import_functions().len(), 1); + assert_eq!(mdata.import_functions()[0].module, "env"); + assert_eq!(mdata.import_functions()[0].name, "icalltarget"); + assert_eq!(mdata.function_info().len(), 7); + assert_eq!(mdata.export_functions()[0].names, vec!["launchpad"]); + assert_eq!(mdata.globals_spec().len(), 0); + + /* TODO can't express these with module data assert_eq!( p.get_table(0).unwrap().elements().get(0), Some(&TableElem::FunctionIx(2)) @@ -101,15 +253,28 @@ mod programs { Some(&TableElem::FunctionIx(0)) ); // righttype_imported assert_eq!(p.get_table(0).unwrap().elements().get(4), None); + */ } #[test] fn icall() { - let m = load("icall"); + let m = load_wat_module("icall"); let b = Bindings::empty(); let h = HeapSettings::default(); - let p = Program::new(m, b, h).expect(&format!("instantiating program")); - + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &None, + ) + .expect("compile icall"); + let _module_data = c.module_data().unwrap(); + + /* TODO can't express these with module data assert_eq!( p.get_table(0).unwrap().elements().get(0), Some(&TableElem::FunctionIx(1)) @@ -123,15 +288,28 @@ mod programs { Some(&TableElem::FunctionIx(3)) ); // wrongtype assert_eq!(p.get_table(0).unwrap().elements().get(4), None); + */ } #[test] fn icall_sparse() { - let m = load("icall_sparse"); + let m = load_wat_module("icall_sparse"); let b = Bindings::empty(); let h = HeapSettings::default(); - let p = Program::new(m, b, h).expect(&format!("instantiating program")); - + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &None, + ) + .expect("compile icall_sparse"); + let _module_data = c.module_data().unwrap(); + + /* TODO can't express these with module data assert_eq!( p.get_table(0).unwrap().elements().get(0), Some(&TableElem::Empty) @@ -157,134 +335,261 @@ mod programs { Some(&TableElem::Empty) ); assert_eq!(p.get_table(0).unwrap().elements().get(6), None); + */ } #[test] fn globals_import() { - let m = load("globals_import"); + use lucet_module::Global as GlobalVariant; + let m = load_wat_module("globals_import"); let b = Bindings::empty(); let h = HeapSettings::default(); - let p = Program::new(m, b, h).expect(&format!("instantiating program")); - assert_eq!(p.globals().len(), 1); - let g = p.globals()[0].as_import().expect("global is an import"); - assert_eq!(g.module(), "env"); - assert_eq!(g.field(), "x"); - assert_eq!(g.global_type.content_type(), ValueType::I32); + + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &None, + ) + .expect("compile globals_import"); + let module_data = c.module_data().unwrap(); + let gspec = module_data.globals_spec(); + + assert_eq!(gspec.len(), 1); + let g = gspec.get(0).unwrap().global(); + match g { + GlobalVariant::Import { module, field } => { + assert_eq!(*module, "env"); + assert_eq!(*field, "x"); + } + _ => panic!("global should be an import"), + } } #[test] fn heap_spec_import() { - use lucetc::program::memory::HeapSpec; - let m = load("heap_spec_import"); + use lucet_module::HeapSpec; + let m = load_wat_module("heap_spec_import"); let b = Bindings::empty(); let h = HeapSettings::default(); - let p = Program::new(m, b, h).expect(&format!("instantiating program")); + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h.clone(), + false, + &None, + ) + .expect("compiling heap_spec_import"); + assert_eq!( - p.heap_spec().unwrap(), - HeapSpec { - // reserved and guard is liblucet_runtime_c standard - reserved_size: 4 * 1024 * 1024, - guard_size: 4 * 1024 * 1024, + c.module_data().unwrap().heap_spec(), + Some(&HeapSpec { + // reserved and guard are given by HeapSettings + reserved_size: h.min_reserved_size, + guard_size: h.guard_size, // initial size of import specified as 6 wasm pages initial_size: 6 * 64 * 1024, // max size of import is specified as 10 wasm pages max_size: Some(10 * 64 * 1024), - } + }) ); } #[test] fn heap_spec_definition() { - use lucetc::program::memory::HeapSpec; - let m = load("heap_spec_definition"); + use lucet_module::HeapSpec; + let m = load_wat_module("heap_spec_definition"); let b = Bindings::empty(); let h = HeapSettings::default(); - let p = Program::new(m, b, h).expect(&format!("instantiating program")); + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h.clone(), + false, + &None, + ) + .expect("compiling heap_spec_definition"); + assert_eq!( - p.heap_spec().unwrap(), - HeapSpec { - // reserved and guard is liblucet_runtime_c standard - reserved_size: 4 * 1024 * 1024, - guard_size: 4 * 1024 * 1024, + c.module_data().unwrap().heap_spec(), + Some(&HeapSpec { + // reserved and guard are given by HeapSettings + reserved_size: h.min_reserved_size, + guard_size: h.guard_size, // initial size defined as 5 wasm pages initial_size: 5 * 64 * 1024, // no max size defined max_size: None, - } + }) ); } #[test] fn heap_spec_none() { - use lucetc::program::memory::HeapSpec; - let m = load("heap_spec_none"); + let m = load_wat_module("heap_spec_none"); let b = Bindings::empty(); let h = HeapSettings::default(); - let p = Program::new(m, b, h).expect(&format!("instantiating program")); - assert_eq!( - p.heap_spec().unwrap(), - HeapSpec { - reserved_size: 0, - guard_size: 0, - initial_size: 0, - max_size: None, - } - ); + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &None, + ) + .expect("compiling heap_spec_none"); + assert_eq!(c.module_data().unwrap().heap_spec(), None,); } #[test] fn oversize_data_segment() { - let m = load("oversize_data_segment"); + use lucetc::Error as LucetcError; + let m = load_wat_module("oversize_data_segment"); let b = Bindings::empty(); let h = HeapSettings::default(); - let p = Program::new(m, b, h).expect("instantiating is ok"); + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &None, + ); assert!( - p.data_initializers().is_err(), - "data_initializers method returns error because data initializers are oversized" + c.is_err(), + "compilation error because data initializers are oversized" ); + assert!(if let LucetcError::InitData = c.err().unwrap() { + true + } else { + false + }); } // XXX adding more negative tests like the one above is valuable - lets do it - use lucetc::error::LucetcErrorKind; #[test] fn invalid_module() { + use lucetc::Error as LucetcError; + use std::fs::File; + use std::io::Read; // I used the `wast2json` tool to produce the file invalid.wasm from an assert_invalid part // of a spectest (call.wast) let wasmfile = PathBuf::from("tests/wasm/invalid.wasm"); - let m = load::read_module(&wasmfile).expect(&format!("loading module from {:?}", wasmfile)); + let mut m = Vec::new(); + let mut file = File::open(&wasmfile).expect("open module file"); + file.read_to_end(&mut m).expect("read contents of module"); + let b = Bindings::empty(); let h = HeapSettings::default(); - let p = Program::new(m, b, h); - assert!(p.is_err()); - assert_eq!(*p.err().unwrap().get_context(), LucetcErrorKind::Validation); + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &None, + ); + assert!( + c.is_err(), + "compilation error because wasm module is invalid" + ); + assert!(if let LucetcError::WasmValidation(_) = c.err().unwrap() { + true + } else { + false + }); } #[test] fn start_section() { - let m = load("start_section"); + let m = load_wat_module("start_section"); let b = Bindings::empty(); let h = HeapSettings::default(); - let p = Program::new(m, b, h).expect(&format!("instantiating program")); + let _c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &None, + ) + .expect("compile start_section"); + /* assert!( p.module().start_section().is_some(), "start section is defined" ); + */ + } + + #[test] + fn names_local() { + let m = load_wat_module("names_local"); + let b = super::test_bindings(); + let h = HeapSettings::default(); + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &None, + ) + .expect("compile names_local"); + let mdata = c.module_data().unwrap(); + + assert_eq!(mdata.import_functions().len(), 0); + assert_eq!(mdata.export_functions().len(), 0); + assert_eq!(mdata.function_info().len(), 3); + assert_eq!( + mdata.function_info().get(0).unwrap().name, + Some("func_name_0") + ) } } mod compile { // Tests for compilation completion - use super::load; - use lucetc::compile; - use lucetc::compiler::OptLevel; - use lucetc::program::{HeapSettings, Program}; + use super::load_wat_module; + use lucetc::{Compiler, CpuFeatures, HeapSettings, OptLevel}; + use target_lexicon::Triple; fn run_compile_test(file: &str) { - let m = load(file); + let m = load_wat_module(file); let b = super::test_bindings(); let h = HeapSettings::default(); - let p = Program::new(m, b, h).expect(&format!("make program for {}", file)); - compile(&p, file.into(), OptLevel::Best).expect(&format!("compile {}", file)); + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &None, + ) + .expect(&format!("compile {}", file)); + let _obj = c.object_file().expect(&format!("codegen {}", file)); } macro_rules! compile_test { ($base_name:ident) => { @@ -316,123 +621,203 @@ mod compile { compile_test!(start_section); } -mod execute { - // Tests for compilation correctness - use super::load; - use lucetc::compile; - use lucetc::compiler::OptLevel; - use lucetc::program::{HeapSettings, Program}; - use std::process::Command; - use std::str; - - fn run_execute_test(file: &str) { - // Compile the wasm file - let m = load(file); +mod validate { + use super::load_wat_module; + use lucet_validate::Validator; + use lucetc::{Compiler, CpuFeatures, HeapSettings, OptLevel}; + use target_lexicon::Triple; + + #[test] + fn validate_arith() { + let m = load_wat_module("arith"); let b = super::test_bindings(); let h = HeapSettings::default(); - let p = Program::new(m, b, h).expect(&format!("make program for {}", file)); - let comp = compile(&p, file, OptLevel::Best).expect(&format!("compile test for {}", file)); - - // Set up output fils - let tmp_dir = tempfile::Builder::new() - .prefix("execute_test_out") - .tempdir() - .expect("Failed to create temp dir"); - let obj_path = tmp_dir.path().join(format!("{}.o", file)); - let obj_file = obj_path - .to_str() - .expect("failed to convert obj_file pathbuf to str"); - - let exec_path = tmp_dir.path().join(format!("{}_test", file)); - let exec_file = exec_path - .to_str() - .expect("failed to convert exec_file pathbuf to str"); - - // Write the compiled object - let obj = comp.codegen().expect("generate code"); - obj.write(&obj_path).expect("write object file"); - - // Mark the wasm table as global, so we can inspect it in the tests. - let objcopy = Command::new("objcopy") - .arg("--globalize-symbol=guest_table_0") - .arg(obj_file) - .output() - .expect("failed to execute objcopy"); - if !objcopy.status.success() { - let stdout = str::from_utf8(&objcopy.stdout).unwrap(); - let stderr = str::from_utf8(&objcopy.stderr).unwrap(); - println!( - "objcopy status: {}", - objcopy.status.code().expect("status code error") - ); - println!("objcopy stdout: {}", stdout); - println!("objcopy stderr: {}", stderr); - } - assert!(objcopy.status.success(), "objcopy failed!"); - - // Invoke GCC to compile and link the harness and the object. - let harness_file = format!("tests/harnesses/{}.c", file); - let output = Command::new("gcc") - .arg("--std=c99") - .arg(harness_file) - .arg("tests/harnesses/vm.c") - .arg("tests/harnesses/globals.c") - .arg(obj_file) - .args(&["-o", exec_file]) - .output() - .expect("failed to execute GCC"); - - if !output.status.success() { - let stdout = str::from_utf8(&output.stdout).unwrap(); - let stderr = str::from_utf8(&output.stderr).unwrap(); - println!( - "GCC status: {}", - output.status.code().expect("status code error") - ); - println!("GCC stdout: {}", stdout); - println!("GCC stderr: {}", stderr); - } - assert!(output.status.success(), "GCC failed!"); - - // Finally, invoke the test itself. - let output = Command::new(exec_file) - .output() - .expect("failed to run execute test"); - - if !output.status.success() { - match output.status.code() { - Some(code) => println!("Test status: {}", code), - None => println!("Test terminated by signal"), - }; - let stdout = str::from_utf8(&output.stdout).unwrap(); - let stderr = str::from_utf8(&output.stderr).unwrap(); - println!("Test stdout: {}", stdout); - println!("Test stderr: {}", stderr); - } - assert!(output.status.success(), "Execute test failed!"); + + // Empty witx: arith module has no imports + let v = Validator::parse("") + .expect("empty witx validates") + .with_wasi_exe(false); + + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &Some(v), + ) + .expect("compile"); + let _obj = c.object_file().expect("codegen"); } - macro_rules! execute_test { - ($base_name:ident) => { - #[test] - pub fn $base_name() { - run_execute_test(stringify!($base_name)); - } - }; + #[test] + fn validate_import() { + let m = load_wat_module("import"); + let b = super::test_bindings(); + let h = HeapSettings::default(); + + let witx = " + (module $env + (@interface func (export \"inc\") + (result $r s32)))"; + let v = Validator::parse(witx) + .expect("witx validates") + .with_wasi_exe(false); + + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &Some(v), + ) + .expect("compile"); + let _obj = c.object_file().expect("codegen"); + } + + #[test] + fn validate_icall_import() { + let m = load_wat_module("icall_import"); + let b = super::test_bindings(); + let h = HeapSettings::default(); + + let witx = " + (module $env + (@interface func (export \"icalltarget\") + (param $a1 u32) + (param $a2 u32) + (result $r s32)))"; + let v = Validator::parse(witx) + .expect("witx validates") + .with_wasi_exe(false); + + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &Some(v), + ) + .expect("compile"); + let _obj = c.object_file().expect("codegen"); + } + + #[test] + fn validate_exported_import() { + let m = load_wat_module("exported_import"); + let b = super::test_bindings(); + let h = HeapSettings::default(); + + let witx = " + (module $env + (@interface func (export \"imported_main\")) + (@interface func (export \"inc\")))"; + let v = Validator::parse(witx) + .expect("witx validates") + .with_wasi_exe(false); + + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &Some(v), + ) + .expect("compile"); + let _obj = c.object_file().expect("codegen"); + } + + #[test] + fn validate_multiple_import() { + let m = load_wat_module("multiple_import"); + let b = super::test_bindings(); + let h = HeapSettings::default(); + + let witx = " + (module $env + (@interface func (export \"imported_main\")) + (@interface func (export \"inc\")))"; + let v = Validator::parse(witx) + .expect("witx validates") + .with_wasi_exe(false); + + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &Some(v), + ) + .expect("compile"); + let _obj = c.object_file().expect("codegen"); + } + + #[test] + fn validate_import_many() { + let m = load_wat_module("import_many"); + let b = super::test_bindings(); + let h = HeapSettings::default(); + + let witx = " + (module $env + (@interface func (export \"imp_0\") (result $r u32)) + (@interface func (export \"imp_1\") (result $r u32)) + (@interface func (export \"imp_2\") (result $r u32)) + (@interface func (export \"imp_3\") (result $r u32)))"; + let v = Validator::parse(witx) + .expect("witx validates") + .with_wasi_exe(false); + + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &Some(v), + ) + .expect("compile"); + let _obj = c.object_file().expect("codegen"); } - execute_test!(fibonacci); - execute_test!(call); - execute_test!(icall); - execute_test!(locals); - execute_test!(locals_csr); - execute_test!(data_segment); - execute_test!(import_many); - execute_test!(icall_import); - execute_test!(icall_sparse); - execute_test!(current_memory); - execute_test!(grow_memory); - execute_test!(heap_spec_import); - execute_test!(heap_spec_definition); - execute_test!(globals_definition); + #[test] + fn validate_wasi_exe() { + let m = load_wat_module("wasi_exe"); + let b = super::test_bindings(); + let h = HeapSettings::default(); + + let witx = ""; + let v = Validator::parse(witx) + .expect("witx validates") + .with_wasi_exe(true); + + let c = Compiler::new( + &m, + Triple::host(), + OptLevel::default(), + CpuFeatures::default(), + &b, + h, + false, + &Some(v), + ) + .expect("compile"); + let _obj = c.object_file().expect("codegen"); + } } diff --git a/lucetc/tests/wasm/duplicate_imports.wat b/lucetc/tests/wasm/duplicate_imports.wat new file mode 100644 index 000000000..ac2b3fb5b --- /dev/null +++ b/lucetc/tests/wasm/duplicate_imports.wat @@ -0,0 +1,34 @@ +(module + (type (func (param i32 i32 i32 i32) (result i32))) + (type (func)) + + ;; import fd_read, this is fine. + (func $read (import "env" "read") (type 0)) + + ;; import fd_write, this is also fine. + (func $write (import "env" "write") (type 0)) + + ;; import fd_read, again, under a different name! + ;; this is to test that we join together the imports. + ;; the .wat would be invalid if their types disagree, so there + ;; is no observable difference between $read and $read_2 + (func $read_2 (import "env" "read") (type 0)) + + ;; import fd_write again for grins. + (import "env" "write" (func (type 0))) + + (func $_setup (type 1) return) + + ;; declare that, actually, one of the imported functions is exported + (export "read_2" (func $read_2)) + ;; and declare that the *other* read function is also exported, by a + ;; different name. This lets us check that when we merge the functions, + ;; we also merge their export names properly. + (export "read" (func $read)) + + ;; and check that other exported functions still work, and are not affected + (export "write" (func $write)) + + ;; and that we can export local functions without issue + (export "_start" (func $_setup)) +) diff --git a/lucetc/tests/wasm/exported_import.wat b/lucetc/tests/wasm/exported_import.wat new file mode 100644 index 000000000..fce3d74c7 --- /dev/null +++ b/lucetc/tests/wasm/exported_import.wat @@ -0,0 +1,12 @@ +(module + (func (import "env" "inc")) + (func $main (export "exported_main") (import "env" "imported_main")) + + ;; cranelift_wasm bundles up import/export/declaration statements and + ;; declares them together. lucetc depends on function declaration + ;; components not being interwoven, so test that this is still bundled + ;; up by exporting after declaring a new function ("$main", above) + (export "exported_inc" (func 0)) + + (start $main) +) diff --git a/lucetc/tests/wasm/globals_export.wat b/lucetc/tests/wasm/globals_export.wat new file mode 100644 index 000000000..a17adbecb --- /dev/null +++ b/lucetc/tests/wasm/globals_export.wat @@ -0,0 +1,5 @@ +(module + (global (;0;) i32 (i32.const 0x1234)) + (export "start" (global 0)) + (export "dupe" (global 0)) +) diff --git a/lucetc/tests/wasm/multiple_import.wat b/lucetc/tests/wasm/multiple_import.wat new file mode 100644 index 000000000..be54dfcf5 --- /dev/null +++ b/lucetc/tests/wasm/multiple_import.wat @@ -0,0 +1,7 @@ +(module + (func $inc (import "env" "inc")) + (func $inc_duplicate (import "env" "inc")) + (func $foo (import "env" "imported_main")) + (func $inc_another_duplicate (export "exported_inc") (import "env" "inc")) + (start $inc) +) diff --git a/lucetc/tests/wasm/names_local.wat b/lucetc/tests/wasm/names_local.wat new file mode 100644 index 000000000..69cdddddb --- /dev/null +++ b/lucetc/tests/wasm/names_local.wat @@ -0,0 +1,4 @@ +(module + (func $func_name + ) +) diff --git a/lucetc/tests/wasm/wasi_exe.wat b/lucetc/tests/wasm/wasi_exe.wat new file mode 100644 index 000000000..0410646da --- /dev/null +++ b/lucetc/tests/wasm/wasi_exe.wat @@ -0,0 +1,12 @@ +(module + (memory 1) + (func $start (export "_start") (local i32) + (set_local 0 (i32.sub (i32.const 4) (i32.const 4))) + (if + (get_local 0) + (then unreachable) + (else (i32.store (i32.const 0) (i32.mul (i32.const 6) (get_local 0)))) + ) + ) + (data (i32.const 0) "abcdefgh") +) diff --git a/platform.info b/platform.info new file mode 100644 index 000000000..9fc6494b6 --- /dev/null +++ b/platform.info @@ -0,0 +1,2 @@ +integer size = 4 +pointer size = 4 diff --git a/pwasm-validation b/pwasm-validation deleted file mode 160000 index 1ce475f3a..000000000 --- a/pwasm-validation +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1ce475f3a19776c901752c54e2446409967bf21e diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 000000000..32b7211cb --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +1.40.0 diff --git a/sightglass b/sightglass index eb9706139..b409ba75a 160000 --- a/sightglass +++ b/sightglass @@ -1 +1 @@ -Subproject commit eb97061395e617671cfeaf4b3a44367f558c918e +Subproject commit b409ba75a7a89cbf4cbf9ec44880e4ae6509a85c diff --git a/wasi b/wasi new file mode 160000 index 000000000..db7391469 --- /dev/null +++ b/wasi @@ -0,0 +1 @@ +Subproject commit db7391469c136447af511c5d3000e0d55f09109e