diff --git a/Cargo.toml b/Cargo.toml index e667aabe7..5ad443271 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "nexus" version = "0.0.0" -edition = "2021" +edition = "2033" [dependencies] +c2pa = "0.21.0" yrs = "0.16.5" diff --git a/docs/runtimeverification-wasm-semantics/.github/actions/with-docker/action.yml b/docs/runtimeverification-wasm-semantics/.github/actions/with-docker/action.yml new file mode 100644 index 000000000..af140e85f --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/.github/actions/with-docker/action.yml @@ -0,0 +1,33 @@ +name: 'With Docker' +description: 'Run a given stage with Docker Image' +inputs: + container-name: + description: 'Docker container name to use' + required: true +runs: + using: 'composite' + steps: + - name: 'Set up Docker' + shell: bash {0} + env: + CONTAINER_NAME: ${{ inputs.container-name }} + run: | + set -euxo pipefail + + TAG=runtimeverificationinc/${CONTAINER_NAME} + K_COMMIT=$(cat ./deps/k_release) + + docker build . --tag ${TAG} --build-arg K_COMMIT=${K_COMMIT} + + docker run \ + --name ${CONTAINER_NAME} \ + --rm \ + --interactive \ + --tty \ + --detach \ + --user root \ + --workdir /home/user \ + ${TAG} + + docker cp . ${CONTAINER_NAME}:/home/user + docker exec ${CONTAINER_NAME} chown -R user:user /home/user diff --git a/docs/runtimeverification-wasm-semantics/.github/workflows/master-push.yml b/docs/runtimeverification-wasm-semantics/.github/workflows/master-push.yml new file mode 100644 index 000000000..0fbbfab33 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/.github/workflows/master-push.yml @@ -0,0 +1,26 @@ +name: 'Master Push' +on: + push: + branches: + - master + +jobs: + + release: + name: 'Publish Release' + runs-on: ubuntu-latest + environment: production + steps: + - name: 'Update dependents' + env: + GITHUB_TOKEN: ${{ secrets.JENKINS_GITHUB_PAT }} + run: | + set -x + version="${GITHUB_SHA}" + curl --fail \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/runtimeverification/devops/dispatches \ + -d '{"event_type":"on-demand-test","client_payload":{"repo":"runtimeverification/wasm-semantics","version":"'${version}'"}}' diff --git a/docs/runtimeverification-wasm-semantics/.github/workflows/test-pr.yml b/docs/runtimeverification-wasm-semantics/.github/workflows/test-pr.yml new file mode 100644 index 000000000..c979b623e --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/.github/workflows/test-pr.yml @@ -0,0 +1,123 @@ +name: 'Run Tests' +on: + pull_request: + workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + + pykwasm-code-quality-checks: + name: 'Code Quality Checks' + runs-on: ubuntu-latest + steps: + - name: 'Check out code' + uses: actions/checkout@v3 + - name: 'Install Poetry' + uses: Gr1N/setup-poetry@v8 + - name: 'Build and run code quality checks' + run: make -C pykwasm check + + pykwasm-unit-tests: + needs: pykwasm-code-quality-checks + name: 'Unit Tests' + runs-on: ubuntu-latest + steps: + - name: 'Check out code' + uses: actions/checkout@v3 + - name: 'Install Poetry' + uses: Gr1N/setup-poetry@v8 + - name: 'Build and run unit tests' + run: make -C pykwasm cov-unit + + pykwasm-integration-tests: + needs: pykwasm-code-quality-checks + name: 'Integration Tests' + runs-on: [self-hosted, linux, normal] + steps: + - name: 'Check out code' + uses: actions/checkout@v3 + - name: 'Set up Docker' + uses: ./.github/actions/with-docker + with: + container-name: kwasm-ci-pykwasm-${{ github.sha }} + - name: 'Build and run integration tests' + run: docker exec -u user kwasm-ci-pykwasm-${GITHUB_SHA} make -C pykwasm cov-integration + - name: 'Tear down Docker' + if: always() + run: | + docker stop --time=0 kwasm-ci-pykwasm-${GITHUB_SHA} + + parse-tests: + needs: [pykwasm-unit-tests, pykwasm-integration-tests] + name: 'Parser Tests' + runs-on: [self-hosted, linux, normal] + timeout-minutes: 18 + steps: + - name: 'Check out code' + uses: actions/checkout@v3 + with: + submodules: recursive + - name: 'Set up Docker' + uses: ./.github/actions/with-docker + with: + container-name: kwasm-ci-parse-${{ github.sha }} + - name: 'Build LLVM Backend and pykwasm' + run: docker exec -u user kwasm-ci-parse-${GITHUB_SHA} make -j2 build-llvm pykwasm-poetry-install + - name: 'Binary parse' + run: docker exec -u user kwasm-ci-parse-${GITHUB_SHA} make -j6 TEST_CONCRETE_BACKEND=llvm test-binary-parser + - name: 'Conformance parse' + run: docker exec -u user kwasm-ci-parse-${GITHUB_SHA} make -j4 test-conformance-parse + - name: 'Tear down Docker' + if: always() + run: | + docker stop --time=0 kwasm-ci-parse-${GITHUB_SHA} + + conformance-tests: + needs: [pykwasm-unit-tests, pykwasm-integration-tests] + name: 'Conformance Tests' + runs-on: [self-hosted, linux, normal] + timeout-minutes: 18 + steps: + - name: 'Check out code' + uses: actions/checkout@v3 + with: + submodules: recursive + - name: 'Set up Docker' + uses: ./.github/actions/with-docker + with: + container-name: kwasm-ci-conformance-${{ github.sha }} + - name: 'Build LLVM Backend' + run: docker exec -u user kwasm-ci-conformance-${GITHUB_SHA} make -j2 build-llvm + - name: 'Simple tests' + run: docker exec -u user kwasm-ci-conformance-${GITHUB_SHA} make -j6 TEST_CONCRETE_BACKEND=llvm test-simple + - name: 'Conformance run' + run: docker exec -u user kwasm-ci-conformance-${GITHUB_SHA} make -j6 TEST_CONCRETE_BACKEND=llvm test-conformance-supported + - name: 'Tear down Docker' + if: always() + run: | + docker stop --time=0 kwasm-ci-conformance-${GITHUB_SHA} + + prove-tests: + needs: [pykwasm-unit-tests, pykwasm-integration-tests] + name: 'Prover Tests' + runs-on: [self-hosted, linux, normal] + timeout-minutes: 18 + steps: + - name: 'Check out code' + uses: actions/checkout@v3 + with: + submodules: recursive + - name: 'Set up Docker' + uses: ./.github/actions/with-docker + with: + container-name: kwasm-ci-prove-${{ github.sha }} + - name: 'Build Haskell Backend' + run: docker exec -u user kwasm-ci-prove-${GITHUB_SHA} make -j2 build-haskell + - name: 'Prove' + run: docker exec -u user kwasm-ci-prove-${GITHUB_SHA} make -j6 test-prove + - name: 'Tear down Docker' + if: always() + run: | + docker stop --time=0 kwasm-ci-prove-${GITHUB_SHA} diff --git a/docs/runtimeverification-wasm-semantics/.github/workflows/update-version.yml b/docs/runtimeverification-wasm-semantics/.github/workflows/update-version.yml new file mode 100644 index 000000000..77ec8e704 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/.github/workflows/update-version.yml @@ -0,0 +1,45 @@ +name: 'Update Version' +on: + push: + branches: + - '_update-deps/runtimeverification/k' + - '_update-deps/runtimeverification/pyk' +# Stop in progress workflows on the same branch and same workflow to use latest committed code +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + + update-versions: + name: 'Update dependency versions' + runs-on: ubuntu-22.04 + steps: + - name: 'Check out code' + uses: actions/checkout@v3 + with: + submodules: recursive + token: ${{ secrets.JENKINS_GITHUB_PAT }} + - run: | + git config user.name devops + git config user.email devops@runtimeverification.com + - name: 'Update K submodule' + run: | + K_VERSION="$(cat deps/k_release)" + cd deps/k + git fetch --tags + git checkout "v${K_VERSION}" + cd - + git add deps/k && git commit -m "deps/k: sync submodule v${K_VERSION}" || true + - name: 'Update pyk Release tag' + run: | + curl -sSL https://install.python-poetry.org | python3 - --version 1.3.2 + poetry --version + pyk_version="$(cat deps/pyk_release)" + sed -i 's!pyk = { git = "https://github.com/runtimeverification/pyk.git", tag="[v0-9\.]*" }!pyk = { git = "https://github.com/runtimeverification/pyk.git", tag="'${pyk_version}'" }!' pykwasm/pyproject.toml + cd pykwasm + poetry update + cd - + git add pykwasm/ && git commit -m "pykwasm/: sync poetry files ${pyk_version}" || true + - name: 'Push updates' + run: git push diff --git a/docs/runtimeverification-wasm-semantics/.gitignore b/docs/runtimeverification-wasm-semantics/.gitignore new file mode 100644 index 000000000..ae615fb12 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/.gitignore @@ -0,0 +1,10 @@ +/.build/* +/tests/*/*-out + +*.pdf +*.sty + +.krun* +.kprove* +.kompile* + diff --git a/docs/runtimeverification-wasm-semantics/.gitmodules b/docs/runtimeverification-wasm-semantics/.gitmodules new file mode 100644 index 000000000..0de06ff7f --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/.gitmodules @@ -0,0 +1,12 @@ +[submodule "deps/k"] + path = deps/k + url = https://github.com/kframework/k + ignore = untracked +[submodule "tests/wasm-tests"] + path = tests/wasm-tests + url = https://github.com/webassembly/spec + ignore = untracked +[submodule "deps/py-wasm"] + path = deps/py-wasm + url = https://github.com/ethereum/py-wasm + ignore = untracked diff --git a/docs/runtimeverification-wasm-semantics/CONTRIBUTING.md b/docs/runtimeverification-wasm-semantics/CONTRIBUTING.md new file mode 100644 index 000000000..a1aa88960 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/CONTRIBUTING.md @@ -0,0 +1,63 @@ +Contributor License Agreement + +This Contributor License Agreement (“Agreement”) sets out the agreement between Runtime Verification, Inc. (“Runtime”) and the party signing below (“You”), and conveys certain license rights to Runtime with respect to Your contributions to Runtime open source activities. This Agreement is effective as of the date of your electronic signature. +This Agreement is legally binding on You upon execution, and shall be relied upon by Runtime without need for execution by Runtime, so please read it carefully before agreeing to it. + +1. *Definitions.* + + - “Contribution” means any work of authorship that is submitted by You to Runtime in which You own or assert ownership of the Copyright. + - “Submit” is the act of uploading, submitting, transmitting, or distributing, computer software code, whether in human-readable or machine-executable form (“Code”), or other content to Runtime, whether in electronic, verbal, or written communication and regardless of the form of media or manner, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by or on behalf of Runtime, for the purpose of discussing, addressing and/or improving any Runtime open source activities, excluding, however, any communication that is conspicuously marked or otherwise designated in writing by You as “Not a Submission.” + - “Submission” means all Code and other materials, including any associated comments and documentation, Submitted by You that are copyrightable, patentable or that are otherwise subject to any intellectual property rights protection. + +2. *Your Submission.* + You must agree to the terms of this Agreement before making a Submission to Runtime. + This Agreement covers any and all Submissions that You make to Runtime now or in the future (except as described in Section 4 below). + You acknowledge that Runtime is not obligated to use Your Submission, but may decide to include Your Submission in or with any other materials and in any manner it considers appropriate. + +3. *Original Work.* + You warrant that each of Your Submissions is entirely Your original work. + Should You wish to Submit materials that are not Your original work, You may Submit them separately if You: (a) retain all copyright and license information that was in the materials as You received them; (b) in the description accompanying Your Submission, include the statement “this Submission contains materials of a third party:” followed by the name(s) of any such third party(ies) and any licenses or other restrictions of which You are aware; and (c) follow any other instructions or guidelines from Runtime concerning Submissions. + +4. *Your Employer.* + References to “employer” in this Agreement include Your employer or anyone else for whom You are acting in making Your Submission, whether as a contractor, vendor, agent or otherwise. + If Your Submission is made in the course of Your work for an employer or Your employer has intellectual property rights in your Submission by contract or applicable law, You must secure permission from Your employer to make the submission before signing this Agreement, and the term “You” in this Agreement refers to You and the employer collectively. + If You change employers in the future and desire to Submit additional Submissions for the new employer, You agree to sign a new Agreement and secure permission from Your new employer before Submitting those Submissions. + +5. *Licenses; Moral Rights.* + + 5.1. *Copyright License.* + You grant Runtime, and those who receive the Submission directly or indirectly from Runtime, a perpetual, worldwide, non-exclusive, royalty-free, transferrable, irrevocable license in the Submission to reproduce, modify and prepare derivative works of, publicly display, publicly perform, and distribute the Submission and such derivative works, and to sublicense any or all of the foregoing rights to third parties under such terms as Runtime determines. + + 5.2. *Patent License.* + You grant Runtime, and those who receive the Submission directly or indirectly from Runtime, a perpetual, worldwide, non-exclusive, royalty-free, transferrable, irrevocable license under Your patent claims that are necessarily infringed by the Submission, or the combination of the Submission with other materials of Runtime with which it may be combined, to make, have made, use, offer to sell, sell and import or otherwise dispose of the Submission alone or with other materials and under such terms as Runtime determines. + + 5.3. *Moral Rights.* + If moral rights apply to the Submission, to the maximum extent permitted by law You waive and agree not to assert such moral rights against Runtime or our successors in interest, or any of our licensees or sublicensees, either direct or indirect. + + 5.4. *Other Rights Reserved.* + Each party reserves all rights not expressly granted in this Agreement. + Subject to the rights granted herein, You retain ownership of the copyright in Your Submission and have the same rights to use or license the Submission which You would have had without entering into the Agreement. + No additional licenses or rights whatsoever (including, without limitation, any implied licenses) are granted hereby, by implication or otherwise. + +6. *Representations and Warranties.* + You represent and warrant that: (a) You own all rights necessary to, and are legally entitled to, grant the above licenses; (b) Each of Your Submissions is entirely Your original work (except as You may have disclosed under Section 3); (c) You have the authority to make the Submission, and have secured permission from Your employer to make the Submission in cases where Your Submission is made in the course of Your work for Your employer or Your employer has intellectual property rights in Your Submission by contract or applicable law; (d) Your grant of rights under this Agreement does not violate any grant of rights which You have made to third parties, including Your employer; and (e) If Your employer has any rights with respect to any Submission, You have the necessary authority to bind Your employer to the obligations contained in this Agreement. + +7. *Disclaimer.* + You are not expected to provide support for Your Submission, unless You choose to do so. + UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, AND EXCEPT FOR THE WARRANTIES EXPRESSLY STATED IN SECTIONS 3, AND 6, YOUR SUBMISSION IS PROVIDED WITHOUT WARRANTY OF ANY KIND, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY OF NONINFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. + +8. *Notice to Runtime.* + You agree to notify Runtime in writing of any facts or circumstances of which You later become aware that would make Your representations and warranties in this Agreement inaccurate in any respect. + +9. *Information about Submissions.* + You agree that Submissions to Runtime and information about Submissions may be maintained indefinitely and disclosed publicly, including Your name and other information that You submit with Your Submission. + +10. *Governing Law/Jurisdiction.* + This Agreement is governed by the laws of the State of Illinois, and the parties consent to exclusive jurisdiction and venue in the state and federal courts sitting in Champaign County, Illinois. + The parties waive all defenses of lack of personal jurisdiction and forum non-conveniens. + +11. *Entire Agreement/Assignment.* + This Agreement is the entire agreement between the parties, and supersedes any and all prior agreements, understandings or communications, written or oral, between the parties relating to the subject matter hereof. + This Agreement may be assigned by Runtime. + +By signing, You accept and agree to the terms of this Contributor License Agreement for Your present and all future Submissions to Runtime. diff --git a/docs/runtimeverification-wasm-semantics/Dockerfile b/docs/runtimeverification-wasm-semantics/Dockerfile new file mode 100644 index 000000000..e8b961bfb --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/Dockerfile @@ -0,0 +1,43 @@ +ARG K_COMMIT +FROM runtimeverificationinc/kframework-k:ubuntu-jammy-${K_COMMIT} + +RUN apt-get update \ + && apt-get upgrade --yes \ + && apt-get install --yes \ + cmake \ + curl \ + pandoc \ + python3 \ + python3-pip + +RUN git clone 'https://github.com/z3prover/z3' --branch=z3-4.8.15 \ + && cd z3 \ + && python3 scripts/mk_make.py \ + && cd build \ + && make -j8 \ + && make install \ + && cd ../.. \ + && rm -rf z3 + +RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/usr python3 - \ + && poetry --version + +ARG USER_ID=1000 +ARG GROUP_ID=1000 +RUN groupadd -g $GROUP_ID user && useradd -m -u $USER_ID -s /bin/sh -g user user + +USER user:user +WORKDIR /home/user + +RUN pip3 install \ + cytoolz \ + numpy + +RUN git clone 'https://github.com/WebAssembly/wabt' --branch 1.0.13 --recurse-submodules wabt \ + && cd wabt \ + && mkdir build \ + && cd build \ + && cmake .. \ + && cmake --build . + +ENV PATH=/home/user/wabt/build:/home/user/.local/bin:$PATH diff --git a/docs/runtimeverification-wasm-semantics/LICENSE b/docs/runtimeverification-wasm-semantics/LICENSE new file mode 100644 index 000000000..0016cccd7 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/LICENSE @@ -0,0 +1,59 @@ +============================================================================== +The WebAssembly Semantics in K Release License +============================================================================== +University of Illinois/NCSA +Open Source License + +Copyright (c) 2009-2019 University of Illinois at Urbana-Champaign. +All rights reserved. + +Developed by: + + K Team (http://kframework.org) + with members from: + + * University of Illinois at Urbana-Champaign (http://fsl.cs.illinois.edu/) + * Runtime Verification, Inc (https://www.runtimeverification.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the K Team, Runtime Verification, the University of + Illinois at Urbana-Champaign, nor the names of its contributors may be + used to endorse or promote products derived from this Software without + specific prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. + +============================================================================== +Copyrights and Licenses for Third Party Software Distributed with the WebAssembly +Semantics in K: +============================================================================== +The WebAssembly Semantics in K software contains code written by third parties. +Licenses for this software can be found in the licenses directory +in the file as specified below. These files will describe the copyrights, +license, and restrictions which apply to that code. + +The disclaimer of warranty in the University of Illinois Open Source License +applies to all code in the WebAssembly Semantics in K Distribution, and nothing +in any of the other licenses gives permission to use the names of the K Team, +Runtime Verification, or the University of Illinois +to endorse or promote products derived from this Software. diff --git a/docs/runtimeverification-wasm-semantics/Makefile b/docs/runtimeverification-wasm-semantics/Makefile new file mode 100644 index 000000000..18f44a4bd --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/Makefile @@ -0,0 +1,246 @@ +# Settings +# -------- + +BUILD_DIR := .build +DEPS_DIR := deps +DEFN_DIR := $(BUILD_DIR)/defn + +K_SUBMODULE := $(DEPS_DIR)/k +ifneq (,$(wildcard deps/k/k-distribution/target/release/k/bin/*)) + K_RELEASE ?= $(abspath $(K_SUBMODULE)/k-distribution/target/release/k) +else + K_RELEASE ?= $(dir $(shell which kompile)).. +endif +K_BIN := $(K_RELEASE)/bin +K_LIB := $(K_RELEASE)/lib/kframework +export K_RELEASE + +ifneq ($(RELEASE),) + K_BUILD_TYPE := Release +else + K_BUILD_TYPE := Debug +endif + +PATH := $(K_BIN):$(PATH) +export PATH + +PYK_PATH := $(abspath $(K_SUBMODULE)/pyk/src/) +PYWASM_PATH := ./deps/py-wasm + +PYTHONPATH := $(PYK_PATH) +export PYTHONPATH + +.PHONY: all clean deps \ + build build-llvm build-haskell \ + test test-execution test-simple test-prove test-binary-parser \ + test-conformance test-conformance-parse test-conformance-supported \ + media presentations reports + +all: build + +clean: + rm -rf $(BUILD_DIR) + +# Build Dependencies (K Submodule) +# -------------------------------- + +K_JAR := $(K_SUBMODULE)/k-distribution/target/release/k/lib/java/kernel-1.0-SNAPSHOT.jar + +deps: $(K_JAR) $(TANGLER) + +$(K_JAR): + cd $(K_SUBMODULE) && mvn package -DskipTests -Dproject.build.type=$(K_BUILD_TYPE) + +# Building Definition +# ------------------- + +KOMPILE_OPTS := +LLVM_KOMPILE_OPTS := +HASKELL_KOMPILE_OPTS := + +tangle_selector := k + +SOURCE_FILES := data \ + kwasm-lemmas \ + numeric \ + test \ + wasm \ + wasm-text \ + wrc20 +EXTRA_SOURCE_FILES := +ALL_SOURCE_FILES := $(patsubst %, %.md, $(SOURCE_FILES)) $(EXTRA_SOURCE_FILES) + +build: build-llvm build-haskell + +ifneq (,$(RELEASE)) + KOMPILE_OPTS += -O2 +else + KOMPILE_OPTS += --debug +endif + +ifeq (,$(RELEASE)) + LLVM_KOMPILE_OPTS += -g +endif + +KOMPILE_LLVM := kompile --backend llvm --md-selector "$(tangle_selector)" \ + $(KOMPILE_OPTS) \ + $(addprefix -ccopt ,$(LLVM_KOMPILE_OPTS)) + +KOMPILE_HASKELL := kompile --backend haskell --md-selector "$(tangle_selector)" \ + $(KOMPILE_OPTS) \ + $(HASKELL_KOMPILE_OPTS) + +### LLVM + +llvm_dir := $(DEFN_DIR)/llvm +llvm_files := $(ALL_SOURCE_FILES) +llvm_main_module := WASM-TEST +llvm_syntax_module := $(llvm_main_module)-SYNTAX +llvm_main_file := test +llvm_kompiled := $(llvm_dir)/$(llvm_main_file)-kompiled/interpreter + +build-llvm: $(llvm_kompiled) + +$(llvm_kompiled): $(llvm_files) + $(KOMPILE_LLVM) $(llvm_main_file).md \ + --directory $(llvm_dir) -I $(CURDIR) \ + --main-module $(llvm_main_module) \ + --syntax-module $(llvm_syntax_module) + +### Haskell + +haskell_dir := $(DEFN_DIR)/haskell +haskell_files := $(ALL_SOURCE_FILES) +haskell_main_module := WASM-TEXT +haskell_syntax_module := $(haskell_main_module)-SYNTAX +haskell_main_file := test +haskell_kompiled := $(haskell_dir)/$(haskell_main_file)-kompiled/definition.kore + +build-haskell: $(haskell_kompiled) + +$(haskell_kompiled): $(haskell_files) + $(KOMPILE_HASKELL) $(haskell_main_file).md \ + --directory $(haskell_dir) -I $(CURDIR) \ + --main-module $(haskell_main_module) \ + --syntax-module $(haskell_syntax_module) + +# Testing +# ------- + +TEST := ./kwasm +CHECK := git --no-pager diff --no-index --ignore-all-space -R + +TEST_CONCRETE_BACKEND := llvm +TEST_SYMBOLIC_BACKEND := haskell + +KPROVE_MODULE := KWASM-LEMMAS +KPROVE_OPTS := + +tests/proofs/functions-spec.k.prove: KPROVE_MODULE = FUNCTIONS-LEMMAS +tests/proofs/functions-spec.k.prove: KPROVE_OPTS += --concrete-rules WASM-DATA.wrap-Positive,WASM-DATA.setRange-Positive,WASM-DATA.getRange-Positive +tests/proofs/memory-spec.k.prove: KPROVE_OPTS += --concrete-rules WASM-DATA.wrap-Positive,WASM-DATA.setRange-Positive,WASM-DATA.getRange-Positive +tests/proofs/wrc20-spec.k.prove: KPROVE_MODULE = WRC20-LEMMAS +tests/proofs/wrc20-spec.k.prove: KPROVE_OPTS += --concrete-rules WASM-DATA.wrap-Positive,WASM-DATA.setRange-Positive,WASM-DATA.getRange-Positive,WASM-DATA.get-Existing,WASM-DATA.set-Extend + +test: test-execution test-prove test-binary-parser + +# Generic Test Harnesses + +tests/%.run: tests/% $(llvm_kompiled) + $(TEST) run --backend $(TEST_CONCRETE_BACKEND) $< > tests/$*.$(TEST_CONCRETE_BACKEND)-out + $(CHECK) tests/$*.$(TEST_CONCRETE_BACKEND)-out tests/success-$(TEST_CONCRETE_BACKEND).out + rm -rf tests/$*.$(TEST_CONCRETE_BACKEND)-out + +tests/%.run-term: tests/% $(llvm_kompiled) + $(TEST) run --backend $(TEST_CONCRETE_BACKEND) $< > tests/$*.$(TEST_CONCRETE_BACKEND)-out + grep --after-context=2 "" tests/$*.$(TEST_CONCRETE_BACKEND)-out > tests/$*.$(TEST_CONCRETE_BACKEND)-out-term + $(CHECK) tests/$*.$(TEST_CONCRETE_BACKEND)-out-term tests/success-k.out + rm -rf tests/$*.$(TEST_CONCRETE_BACKEND)-out + rm -rf tests/$*.$(TEST_CONCRETE_BACKEND)-out-term + +tests/%.parse: tests/% $(llvm_kompiled) + $(TEST) kast --backend $(TEST_CONCRETE_BACKEND) $< kast > $@-out + rm -rf $@-out + +tests/%.prove: tests/% $(haskell_kompiled) + $(TEST) prove --backend $(TEST_SYMBOLIC_BACKEND) $< $(KPROVE_MODULE) --format-failures \ + $(KPROVE_OPTS) + +### Execution Tests + +test-execution: test-simple + +simple_tests := $(wildcard tests/simple/*.wast) +simple_tests_failing := $(shell cat tests/failing.simple) +simple_tests_passing := $(filter-out $(simple_tests_failing), $(simple_tests)) + +test-simple: $(simple_tests_passing:=.run) + +### Conformance Tests + +conformance_tests:=$(wildcard tests/wasm-tests/test/core/*.wast) +unsupported_conformance_tests:=$(patsubst %, tests/wasm-tests/test/core/%, $(shell cat tests/conformance/unsupported-$(TEST_CONCRETE_BACKEND).txt)) +unparseable_conformance_tests:=$(patsubst %, tests/wasm-tests/test/core/%, $(shell cat tests/conformance/unparseable.txt)) +parseable_conformance_tests:=$(filter-out $(unparseable_conformance_tests), $(conformance_tests)) +supported_conformance_tests:=$(filter-out $(unsupported_conformance_tests), $(parseable_conformance_tests)) + +test-conformance-parse: $(parseable_conformance_tests:=.parse) +test-conformance-supported: $(supported_conformance_tests:=.run-term) + +test-conformance: test-conformance-parse test-conformance-supported + +### Proof Tests + +proof_tests:=$(wildcard tests/proofs/*-spec.k) + +test-prove: $(proof_tests:=.prove) + +### Binary Parser Test + +# TODO pyk is not globally installed. use the poetry-installed pyk +# until binary-parser is migrated to pykwasm +BINARY:=poetry -C pykwasm run python3 binary-parser/test.py + +tests/binary/%.wasm: tests/binary/%.wat + wat2wasm $< --output=$@ + +.PHONY: pykwasm-poetry-install +pykwasm-poetry-install: + $(MAKE) -C pykwasm poetry-install + +tests/%.wasm.bparse: tests/%.wasm pykwasm-poetry-install + $(BINARY) $< + +binary_parser_tests:=$(wildcard tests/binary/*.wat) + +test-binary-parser: $(binary_parser_tests:.wat=.wasm.bparse) test-kwasm-ast + +test-kwasm-ast: pykwasm-poetry-install + poetry -C pykwasm run pytest binary-parser/test_kwasm_ast.py + +# Analysis +# -------- +json_build := $(haskell_dir)/$(haskell_main_file)-kompiled/parsed.json + +$(json_build): + $(MAKE) build-haskell -B KOMPILE_OPTS="--emit-json" + +graph-imports: $(json_build) + kpyk $(haskell_dir)/$(haskell_main_file)-kompiled graph-imports + +# Presentation +# ------------ + +media: presentations reports + +media/%.pdf: TO_FORMAT=beamer +presentations: $(patsubst %.md, %.pdf, $(wildcard media/*-presentation-*.md)) + +media/201903-report-chalmers.pdf: TO_FORMAT=latex +reports: media/201903-report-chalmers.pdf + +media/%.pdf: media/%.md media/citations.md + cat $^ | pandoc --from markdown-latex_macros --to $(TO_FORMAT) --filter pandoc-citeproc --output $@ + +media-clean: + rm media/*.pdf diff --git a/docs/runtimeverification-wasm-semantics/README.md b/docs/runtimeverification-wasm-semantics/README.md new file mode 100644 index 000000000..ae90d28d1 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/README.md @@ -0,0 +1,163 @@ +KWasm: Semantics of WebAssembly in K +==================================== + +### Want to Support KWasm Development? +[Contribute to our Gitcoin Grant.](https://gitcoin.co/grants/592/kewasm-and-kwasm) + +--- + +This repository presents a prototype formal semantics of [WebAssembly]. +It is currently under construction. +For examples of current capabilities, see the unit tests under the `tests/simple` directory. + +Repository Structure +-------------------- + +### Semantics Layout + +The following files constitute the KWasm semantics: + +- [data.md](data.md) provides the (functional) data of WebAssembly (basic types, type constructors, and values). +- [numeric.md](numeric.md) provides the functional rules for numeric operators. +- [wasm.md](wasm.md) is the main KWasm semantics, containing the configuration and transition rules of WebAssembly. + +These additional files extend the semantics to make the repository more useful: + +- [test.md](test.md) is an execution harness for KWasm, providing a simple language for describing tests/programs. + +### Example usage: `./kwasm` runner script + +After building the definition, you can run the definition using `./kwasm`. +The most up-to-date documentation will always be in `./kwasm help`. + +Run the file `tests/simple/arithmetic.wast`: + +```sh +./kwasm run tests/simple/arithmetic.wast +``` + +To run proofs, you can similarly use `./kwasm`, but must specify the module to use for proving. +For example, to prove the specification `tests/proofs/simple-arithmetic-spec.k`: + +```sh +./kwasm prove tests/proofs/simple-arithmetic-spec.k KWASM-LEMMAS +``` + +You can optionally override the default backend using the `--backend BACKEND` flag: + +```sh +./kwasm run --backend llvm tests/simple/arithmetic.wast +./kwasm prove --backend haskell tests/proofs/simple-arithmetic-spec.k KWASM-LEMMAS +``` + +Installing/Building +------------------- + +### K Backends + +There are two backends of K available, the LLVM backend for concrete execution, and the Haskell backend for symbolic reasoning and proofs. +This repository generates the build-products for the backends in `.build/defn/llvm` and `.build/defn/haskell`. + +### Dependencies + +The following are needed for building/running KWasm: + +- [git](https://git-scm.com/) +- [Pandoc >= 1.17](https://pandoc.org) is used to generate the `*.k` files from the `*.md` files. +- GNU [Bison](https://www.gnu.org/software/bison/), [Flex](https://github.com/westes/flex), and [Autoconf](http://www.gnu.org/software/autoconf/). +- GNU [libmpfr](http://www.mpfr.org/) and [libtool](https://www.gnu.org/software/libtool/). +- Java 8 JDK (eg. [OpenJDK](http://openjdk.java.net/)) +- [Haskell Stack](https://docs.haskellstack.org/en/stable/install_and_upgrade/#installupgrade). + Note that the version of the `stack` tool provided by your package manager might not be recent enough. + Please follow installation instructions from the Haskell Stack website linked above. +- [LLVM](https://llvm.org/) For building the LLVM backend. +- [Z3](https://github.com/Z3Prover/z3) version 4.8.15 + +On Ubuntu >= 15.04 (for example): + +```sh +sudo apt-get install --yes \ + autoconf bison clang++-8 clang-8 cmake curl flex gcc libboost-test-dev libffi-dev \ + libgmp-dev libjemalloc-dev libmpfr-dev libprocps-dev libprotobuf-dev libtool \ + libyaml-dev lld-8 llvm llvm-8 llvm-8-tools maven openjdk-8-jdk pandoc pkg-config \ + protobuf-compiler python3 python-pygments python-recommonmark python-sphinx time \ + zlib1g-dev +``` + +To upgrade `stack` (if needed): + +```sh +stack upgrade +export PATH=$HOME/.local/bin:$PATH +``` + +After installing the above dependencies, make sure the submodules are setup: + +```sh +git submodule update --init --recursive +``` + +Install repository specific dependencies: + +```sh +make deps +``` + +#### Installing Z3 + +Note that KWASM requires Z3 version 4.8.15, which you may need to install from a +source build if your package manager supplies a different version. To do so, +follow the instructions +[here](https://github.com/Z3Prover/z3#building-z3-using-make-and-gccclang) after +checking out the correct tag in the Z3 repository: + +```sh +git clone https://github.com/Z3Prover/z3.git +cd z3 +git checkout z3-4.8.15 +python scripts/mk_make.py +cd build +make +sudo make install +``` + +### Building + +And then build the semantics: + +```sh +make build +``` + +To only build a specific backend, you can do `make build-llvm` or `make build-haskell`. + +### Media and documents + +The `media/` directory contains presentations and reports about about KWasm. +The documents are named with an approximate date of presentation/submission, what type of document it is, and a brief contextual name, e.g., name of conference where it was held. + +[GhostScript](https://www.ghostscript.com/) is a dependency for building documents of type `report`. + +```sh +sudo apt install ghostscript +``` + +To build all documents in the media file: + +```sh +make media +``` + +Testing +------- + +The target `test` contains all the currently passing tests. + +```sh +make test +``` + +Resources +--------- + +[WebAssembly]: diff --git a/docs/runtimeverification-wasm-semantics/auxil.md b/docs/runtimeverification-wasm-semantics/auxil.md new file mode 100644 index 000000000..873a9afcb --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/auxil.md @@ -0,0 +1,37 @@ +Auxiliary Wasm Commands +======================= + +Generally useful commands that are not part of the actual Wasm semantics. + +```k +require "wasm.md" + +module WASM-AUXIL + imports WASM + + syntax Stmt ::= Auxil + // --------------------- + + syntax Auxil ::= "#clearConfig" + // ------------------------------- + rule #clearConfig => . ... + _ => .Int + _ => .ValStack + _ => .Map + _ => .Bag + _ => .Map + _ => 0 + _ => .Map + + _ => 0 + _ => .Bag + _ => 0 + _ => .Bag + _ => 0 + _ => .Bag + _ => 0 + _ => .Bag + + +endmodule +``` diff --git a/docs/runtimeverification-wasm-semantics/binary-parser/.gitignore b/docs/runtimeverification-wasm-semantics/binary-parser/.gitignore new file mode 100644 index 000000000..c18dd8d83 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/binary-parser/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/docs/runtimeverification-wasm-semantics/binary-parser/README.md b/docs/runtimeverification-wasm-semantics/binary-parser/README.md new file mode 100644 index 000000000..a3710f4b0 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/binary-parser/README.md @@ -0,0 +1,26 @@ +Binary Parser for Wasm Modules +============================== + +This python module converts a binary Wasm module into the Kast format accepted by KWasm. + + +Usage: +------ + +The entry point is the `wasm2kast` module. +Ensure you have `pyk` installed and available in your Python path. + +Import `wasm2kast` in your Python-script. +Pass the module as bytes to the `wasm2kast` function. +It will return the Kast representation in Wasm for that module. + +Example: + +```py +import wasm2kast + +filename = 'hello.wasm' +with open(filename, 'rb') as f: + kast = wasm2kast.wasm2kast(f) + print(kast) +``` diff --git a/docs/runtimeverification-wasm-semantics/binary-parser/__init__.py b/docs/runtimeverification-wasm-semantics/binary-parser/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/runtimeverification-wasm-semantics/binary-parser/kwasm_ast.py b/docs/runtimeverification-wasm-semantics/binary-parser/kwasm_ast.py new file mode 100644 index 000000000..77653625c --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/binary-parser/kwasm_ast.py @@ -0,0 +1,546 @@ +#!/usr/bin/env python3 + +""" +NOTE: The KLabels in this file must be kept up to date with the ones in the K semantics definition. +There is unfortunately no way to do this automatically. + +This library provides a convenient interface to create KWasm programs in Kast format. +It is a mirror of the abstract syntax in the K semantics. +""" + +from pyk.kast.inner import KSequence, KApply, KToken +from pyk.prelude.bytes import bytesToken +from pyk.utils import dequote_str + +########### +# KLabels # +########### + +MODULE = 'aModuleDecl' +MODULE_METADATA = 'moduleMeta' +TYPE = 'aTypeDefn' +FUNC_TYPE = 'aFuncType' +VEC_TYPE = 'aVecType' +VAL_TYPES = 'listValTypes' +VAL_TYPES_NIL = '.List{\"listValTypes\"}_ValTypes' +I32 = 'i32' +I64 = 'i32' +INTS = 'listInt' +INTS_NIL = '.List{\"listInt\"}_Ints' + +FUNC = 'aFuncDefn' +FUNC_METADATA = 'funcMeta' + +TABLE = 'aTableDefn' + +MEMORY = 'aMemoryDefn' + +GLOBAL = 'aGlobalDefn' +GLOBAL_TYPE = 'aGlobalType' + +ELEM = 'aElemDefn' + +DATA = 'aDataDefn' + +START = 'aStartDefn' + +IMPORT = 'aImportDefn' +FUNC_DESC = 'aFuncDesc' +GLOBAL_DESC = 'aGlobalDesc' +TABLE_DESC = 'aTableDesc' +MEMORY_DESC = 'aMemoryDesc' + +EXPORT = 'aExportDefn' + +DEFNS = '___WASM-COMMON-SYNTAX_Defns_Defn_Defns' +INSTRS = '___WASM-COMMON-SYNTAX_Instrs_Instr_Instrs' + +################### +# Basic Datatypes # +################### + +def KInt(value : int): + return KToken(str(value), 'Int') + +def KFloat(value : float): + return KToken(str(value), 'Float') + +def KString(value : str): + return KToken('"%s"' % value, 'String') + +def KBytes(bs : bytes): + # Change from python bytes repr to bytes repr in K. + return bytesToken(dequote_str(str(bs))[2:-1]) + +########### +# Strings # +########### + +def wasm_string(s : str): + return KToken('\"%s\"' % s, 'WasmStringToken') + +######### +# Lists # +######### + +def KNamedList(klabel, empty_klabel, items): + tail = KApply(empty_klabel, []) + while not items == []: + last = items.pop() + tail = KApply(klabel, [last, tail]) + return tail + +def defns(items): + return KNamedList(DEFNS, EMPTY_STMTS, items) + +def instrs(items): + return KNamedList(INSTRS, EMPTY_STMTS, items) + +def val_types(items): + return KNamedList(VAL_TYPES, VAL_TYPES_NIL, items) + +def ints(iis : [int]): + kis = [KInt(x) for x in iis] + return KNamedList(INTS, INTS_NIL, kis) + +########### +# Empties # +########### + +EMPTY_ID = KApply('.Identifier', []) +EMPTY_STMTS = '.List{\"listStmt\"}_EmptyStmts' +EMPTY_MAP = KApply('.Map', []) +EMPTY_OPT_STRING = KApply('.String', []) +EMPTY_DEFNS = KApply(EMPTY_STMTS, []) +EMPTY_FUNC_METADATA = KApply(FUNC_METADATA, [EMPTY_ID, EMPTY_MAP]) + +######### +# Types # +######### + +i32 = KApply('i32', []) +i64 = KApply('i64', []) +f32 = KApply('f32', []) +f64 = KApply('f64', []) + +MUT_CONST = KApply('mutConst', []) +MUT_VAR = KApply('mutVar', []) + +def vec_type(valtypes): + return KApply(VEC_TYPE, [valtypes]) + +def func_type(params, results): + return KApply(FUNC_TYPE, [params, results]) + +def limits(tup): + i = tup[0] + j = tup[1] + if j is None: + return KApply('limitsMin', [KInt(i)]) + return KApply('limitsMinMax', [KInt(i), KInt(j)]) + +def global_type(mut, valtype): + return KApply(GLOBAL_TYPE, [mut, valtype]) + +########## +# Instrs # +########## + + ######################## + # Control Instructions # + ######################## + +NOP = KApply('aNop', []) +UNREACHABLE = KApply('aUnreachable', []) + +def BLOCK(vec_type, instrs, block_info): + return KApply('aBlock', [vec_type, instrs, block_info]) + +def IF(vec_type, then_instrs, else_instrs, block_info): + return KApply('aIf', [vec_type, then_instrs, else_instrs, block_info]) + +def LOOP(vec_type, instrs, block_info): + return KApply('aLoop', [vec_type, instrs, block_info]) + +RETURN = KApply('aReturn', []) + +def BR(idx : int): + return KApply('aBr', [KInt(idx)]) + +def BR_IF(idx : int): + return KApply('aBr_if', [KInt(idx)]) + +def BR_TABLE(idxs : [int], default): + return KApply('aBr_table', [ints(idxs + (default,))]) + +def CALL(function_idx : int): + return KApply('aCall', [KInt(function_idx)]) + +def CALL_INDIRECT(type_idx : int): + return KApply('aCall_indirect', [KInt(type_idx)]) + + ########################## + # Parametric Instruction # + ########################## + +DROP = KApply('aDrop', []) +SELECT = KApply('aSelect', []) + + ############## + # Float UnOp # + ############## + +F32_ABS = KApply('aFUnOp', [f32, KApply('aAbs', [])]) +F32_CEIL = KApply('aFUnOp', [f32, KApply('aCeil', [])]) +F32_FLOOR = KApply('aFUnOp', [f32, KApply('aFloor', [])]) +F32_NEAREST = KApply('aFUnOp', [f32, KApply('aNearest', [])]) +F32_NEG = KApply('aFUnOp', [f32, KApply('aNeg', [])]) +F32_SQRT = KApply('aFUnOp', [f32, KApply('aSqrt', [])]) +F32_TRUNC = KApply('aFUnOp', [f32, KApply('aTrunc', [])]) +F64_ABS = KApply('aFUnOp', [f64, KApply('aAbs', [])]) +F64_CEIL = KApply('aFUnOp', [f64, KApply('aCeil', [])]) +F64_FLOOR = KApply('aFUnOp', [f64, KApply('aFloor', [])]) +F64_NEAREST = KApply('aFUnOp', [f64, KApply('aNearest', [])]) +F64_NEG = KApply('aFUnOp', [f64, KApply('aNeg', [])]) +F64_SQRT = KApply('aFUnOp', [f64, KApply('aSqrt', [])]) +F64_TRUNC = KApply('aFUnOp', [f64, KApply('aTrunc', [])]) + + ############ + # Int UnOp # + ############ + +I32_CLZ = KApply('aIUnOp', [i32, KApply('aClz', [])]) +I32_CTZ = KApply('aIUnOp', [i32, KApply('aCtz', [])]) +I32_POPCNT = KApply('aIUnOp', [i32, KApply('aPopcnt', [])]) +I64_CLZ = KApply('aIUnOp', [i64, KApply('aClz', [])]) +I64_CTZ = KApply('aIUnOp', [i64, KApply('aCtz', [])]) +I64_POPCNT = KApply('aIUnOp', [i64, KApply('aPopcnt', [])]) + + ############### + # Float BinOp # + ############### + +F32_ADD = KApply('aFBinOp', [f32, KApply('floatAdd', [])]) +F32_SUB = KApply('aFBinOp', [f32, KApply('floatSub', [])]) +F32_MUL = KApply('aFBinOp', [f32, KApply('floatMul', [])]) +F32_DIV = KApply('aFBinOp', [f32, KApply('floatDiv', [])]) +F32_MIN = KApply('aFBinOp', [f32, KApply('floatMin', [])]) +F32_MAX = KApply('aFBinOp', [f32, KApply('floatMax', [])]) +F32_COPYSIGN = KApply('aFBinOp', [f32, KApply('floatCopysign', [])]) +F64_ADD = KApply('aFBinOp', [f64, KApply('floatAdd', [])]) +F64_SUB = KApply('aFBinOp', [f64, KApply('floatSub', [])]) +F64_MUL = KApply('aFBinOp', [f64, KApply('floatMul', [])]) +F64_DIV = KApply('aFBinOp', [f64, KApply('floatDiv', [])]) +F64_MIN = KApply('aFBinOp', [f64, KApply('floatMin', [])]) +F64_MAX = KApply('aFBinOp', [f64, KApply('floatMax', [])]) +F64_COPYSIGN = KApply('aFBinOp', [f64, KApply('floatCopysign', [])]) + + ############# + # Int BinOp # + ############# + +I32_ADD = KApply('aIBinOp', [i32, KApply('intAdd', [])]) +I32_AND = KApply('aIBinOp', [i32, KApply('intAnd', [])]) +I32_DIV_S = KApply('aIBinOp', [i32, KApply('intDiv_s', [])]) +I32_DIV_U = KApply('aIBinOp', [i32, KApply('intDiv_u', [])]) +I32_MUL = KApply('aIBinOp', [i32, KApply('intMul', [])]) +I32_OR = KApply('aIBinOp', [i32, KApply('intOr', [])]) +I32_REM_S = KApply('aIBinOp', [i32, KApply('intRem_s', [])]) +I32_REM_U = KApply('aIBinOp', [i32, KApply('intRem_u', [])]) +I32_ROTL = KApply('aIBinOp', [i32, KApply('intRotl', [])]) +I32_ROTR = KApply('aIBinOp', [i32, KApply('intRotr', [])]) +I32_SHL = KApply('aIBinOp', [i32, KApply('intShl', [])]) +I32_SHR_S = KApply('aIBinOp', [i32, KApply('intShr_s', [])]) +I32_SHR_U = KApply('aIBinOp', [i32, KApply('intShr_u', [])]) +I32_SUB = KApply('aIBinOp', [i32, KApply('intSub', [])]) +I32_XOR = KApply('aIBinOp', [i32, KApply('intXor', [])]) +I64_ADD = KApply('aIBinOp', [i64, KApply('intAdd', [])]) +I64_AND = KApply('aIBinOp', [i64, KApply('intAnd', [])]) +I64_DIV_S = KApply('aIBinOp', [i64, KApply('intDiv_s', [])]) +I64_DIV_U = KApply('aIBinOp', [i64, KApply('intDiv_u', [])]) +I64_MUL = KApply('aIBinOp', [i64, KApply('intMul', [])]) +I64_OR = KApply('aIBinOp', [i64, KApply('intOr', [])]) +I64_REM_S = KApply('aIBinOp', [i64, KApply('intRem_s', [])]) +I64_REM_U = KApply('aIBinOp', [i64, KApply('intRem_u', [])]) +I64_ROTL = KApply('aIBinOp', [i64, KApply('intRotl', [])]) +I64_ROTR = KApply('aIBinOp', [i64, KApply('intRotr', [])]) +I64_SHL = KApply('aIBinOp', [i64, KApply('intShl', [])]) +I64_SHR_S = KApply('aIBinOp', [i64, KApply('intShr_s', [])]) +I64_SHR_U = KApply('aIBinOp', [i64, KApply('intShr_u', [])]) +I64_SUB = KApply('aIBinOp', [i64, KApply('intSub', [])]) +I64_XOR = KApply('aIBinOp', [i64, KApply('intXor', [])]) + + ########## + # TestOp # + ########## + +I32_EQZ = KApply('aTestOp', [i32, KApply('aEqz', [])]) +I64_EQZ = KApply('aTestOp', [i64, KApply('aEqz', [])]) + + ############# + # Int RelOp # + ############# + +I32_EQ = KApply('aIRelOp', [i32, KApply('intEq', [])]) +I32_NE = KApply('aIRelOp', [i32, KApply('intNe', [])]) +I32_LT_U = KApply('aIRelOp', [i32, KApply('intLt_u', [])]) +I32_GT_U = KApply('aIRelOp', [i32, KApply('intGt_u', [])]) +I32_LT_S = KApply('aIRelOp', [i32, KApply('intLt_s', [])]) +I32_GT_S = KApply('aIRelOp', [i32, KApply('intGt_s', [])]) +I32_LE_U = KApply('aIRelOp', [i32, KApply('intLe_u', [])]) +I32_GE_U = KApply('aIRelOp', [i32, KApply('intGe_u', [])]) +I32_LE_S = KApply('aIRelOp', [i32, KApply('intLe_s', [])]) +I32_GE_S = KApply('aIRelOp', [i32, KApply('intGe_s', [])]) +I64_EQ = KApply('aIRelOp', [i64, KApply('intEq', [])]) +I64_NE = KApply('aIRelOp', [i64, KApply('intNe', [])]) +I64_LT_U = KApply('aIRelOp', [i64, KApply('intLt_u', [])]) +I64_GT_U = KApply('aIRelOp', [i64, KApply('intGt_u', [])]) +I64_LT_S = KApply('aIRelOp', [i64, KApply('intLt_s', [])]) +I64_GT_S = KApply('aIRelOp', [i64, KApply('intGt_s', [])]) +I64_LE_U = KApply('aIRelOp', [i64, KApply('intLe_u', [])]) +I64_GE_U = KApply('aIRelOp', [i64, KApply('intGe_u', [])]) +I64_LE_S = KApply('aIRelOp', [i64, KApply('intLe_s', [])]) +I64_GE_S = KApply('aIRelOp', [i64, KApply('intGe_s', [])]) + + ############### + # Float RelOp # + ############### + +F32_LT = KApply('aFRelOp', [f32, KApply('floatLt', [])]) +F32_GT = KApply('aFRelOp', [f32, KApply('floatGt', [])]) +F32_LE = KApply('aFRelOp', [f32, KApply('floatLe', [])]) +F32_GE = KApply('aFRelOp', [f32, KApply('floatGe', [])]) +F32_EQ = KApply('aFRelOp', [f32, KApply('floatEq', [])]) +F32_NE = KApply('aFRelOp', [f32, KApply('floatNe', [])]) +F64_LT = KApply('aFRelOp', [f64, KApply('floatLt', [])]) +F64_GT = KApply('aFRelOp', [f64, KApply('floatGt', [])]) +F64_LE = KApply('aFRelOp', [f64, KApply('floatLe', [])]) +F64_GE = KApply('aFRelOp', [f64, KApply('floatGe', [])]) +F64_EQ = KApply('aFRelOp', [f64, KApply('floatEq', [])]) +F64_NE = KApply('aFRelOp', [f64, KApply('floatNe', [])]) + + ############## + # Convert Op # + ############## + +I64_EXTEND_U_I32 = KApply('aCvtOp', [i64, KApply('aExtend_i32_u', [])]) +I64_EXTEND_S_I32 = KApply('aCvtOp', [i64, KApply('aExtend_i32_s', [])]) +I32_WRAP_I64 = KApply('aCvtOp', [i32, KApply('aWrap_i64', [])]) + +F64_PROMOTE_F32 = KApply('aCvtOp', [f64, KApply('aPromote_f32', [])]) +F32_DEMOTE_F64 = KApply('aCvtOp', [f32, KApply('aDemote_f64', [])]) + +F32_CONVERT_U_I32 = KApply('aCvtOp', [f32, KApply('aConvert_i32_u', [])]) +F64_CONVERT_U_I32 = KApply('aCvtOp', [f64, KApply('aConvert_i32_u', [])]) +F32_CONVERT_U_I64 = KApply('aCvtOp', [f32, KApply('aConvert_i64_u', [])]) +F64_CONVERT_U_I64 = KApply('aCvtOp', [f64, KApply('aConvert_i64_u', [])]) +F32_CONVERT_S_I32 = KApply('aCvtOp', [f32, KApply('aConvert_i32_s', [])]) +F64_CONVERT_S_I32 = KApply('aCvtOp', [f64, KApply('aConvert_i32_s', [])]) +F32_CONVERT_S_I64 = KApply('aCvtOp', [f32, KApply('aConvert_i64_s', [])]) +F64_CONVERT_S_I64 = KApply('aCvtOp', [f64, KApply('aConvert_i64_s', [])]) + +I32_TRUNC_U_F32 = KApply('aCvtOp', [i32, KApply('aTrunc_f32_u', [])]) +I32_TRUNC_U_F64 = KApply('aCvtOp', [i32, KApply('aTrunc_f64_u', [])]) +I64_TRUNC_U_F32 = KApply('aCvtOp', [i64, KApply('aTrunc_f32_u', [])]) +I64_TRUNC_U_F64 = KApply('aCvtOp', [i64, KApply('aTrunc_f64_u', [])]) +I32_TRUNC_S_F32 = KApply('aCvtOp', [i32, KApply('aTrunc_f32_s', [])]) +I32_TRUNC_S_F64 = KApply('aCvtOp', [i32, KApply('aTrunc_f64_s', [])]) +I64_TRUNC_S_F32 = KApply('aCvtOp', [i64, KApply('aTrunc_f32_s', [])]) +I64_TRUNC_S_F64 = KApply('aCvtOp', [i64, KApply('aTrunc_f64_s', [])]) + + ######### + # Const # + ######### + +def F32_CONST(f: float): + return KApply('aFConst', [f32, KFloat(f)]) + +def F64_CONST(f: float): + return KApply('aFConst', [f64, KFloat(f)]) + +def I32_CONST(i : int): + return (KApply('aIConst', [i32, KInt(i)])) + +def I64_CONST(i : int): + return (KApply('aIConst', [i64, KInt(i)])) + + ####################### + # Memory Instructions # + ####################### + +def F32_STORE(offset : int): + return KApply('aStore', [f32, KApply('storeOpStore', []), KInt(offset)]) + +def F64_STORE(offset : int): + return KApply('aStore', [f64, KApply('storeOpStore', []), KInt(offset)]) + +def I32_STORE(offset : int): + return KApply('aStore', [i32, KApply('storeOpStore', []), KInt(offset)]) + +def I64_STORE(offset : int): + return KApply('aStore', [i64, KApply('storeOpStore', []), KInt(offset)]) + +def I32_STORE8(offset : int): + return KApply('aStore', [i32, KApply('storeOpStore8', []), KInt(offset)]) + +def I64_STORE8(offset : int): + return KApply('aStore', [i64, KApply('storeOpStore8', []), KInt(offset)]) + +def I32_STORE16(offset : int): + return KApply('aStore', [i32, KApply('storeOpStore16', []), KInt(offset)]) + +def I64_STORE16(offset : int): + return KApply('aStore', [i64, KApply('storeOpStore16', []), KInt(offset)]) + +def I64_STORE32(offset : int): + return KApply('aStore', [i64, KApply('storeOpStore32', []), KInt(offset)]) + +def F32_LOAD(offset : int): + return KApply('aLoad', [f32, KApply('loadOpLoad', []), KInt(offset)]) + +def F64_LOAD(offset : int): + return KApply('aLoad', [f64, KApply('loadOpLoad', []), KInt(offset)]) + +def I32_LOAD(offset : int): + return KApply('aLoad', [i32, KApply('loadOpLoad', []), KInt(offset)]) + +def I64_LOAD(offset : int): + return KApply('aLoad', [i64, KApply('loadOpLoad', []), KInt(offset)]) + +def I32_LOAD16_S(offset : int): + return KApply('aLoad', [i32, KApply('loadOpLoad16_s', []), KInt(offset)]) + +def I32_LOAD16_U(offset : int): + return KApply('aLoad', [i32, KApply('loadOpLoad16_u', []), KInt(offset)]) + +def I64_LOAD16_S(offset : int): + return KApply('aLoad', [i64, KApply('loadOpLoad16_s', []), KInt(offset)]) + +def I64_LOAD16_U(offset : int): + return KApply('aLoad', [i64, KApply('loadOpLoad16_u', []), KInt(offset)]) + +def I32_LOAD8_S(offset : int): + return KApply('aLoad', [i32, KApply('loadOpLoad8_s', []), KInt(offset)]) + +def I32_LOAD8_U(offset : int): + return KApply('aLoad', [i32, KApply('loadOpLoad8_u', []), KInt(offset)]) + +def I64_LOAD8_S(offset : int): + return KApply('aLoad', [i64, KApply('loadOpLoad8_s', []), KInt(offset)]) + +def I64_LOAD8_U(offset : int): + return KApply('aLoad', [i64, KApply('loadOpLoad8_u', []), KInt(offset)]) + +def I64_LOAD32_U(offset : int): + return KApply('aLoad', [i64, KApply('loadOpLoad32_u', []), KInt(offset)]) + +def I64_LOAD32_S(offset : int): + return KApply('aLoad', [i64, KApply('loadOpLoad32_s', []), KInt(offset)]) + +MEMORY_GROW = KApply('aGrow', []) +MEMORY_SIZE = KApply('aSize', []) + + ####################### + # Global Instructions # + ####################### + +def GET_GLOBAL(idx : int): + return KApply('aGlobal.get', [KInt(idx)]) + +def SET_GLOBAL(idx : int): + return KApply('aGlobal.set', [KInt(idx)]) + + ###################### + # Local Instructions # + ###################### + +def GET_LOCAL(idx : int): + return KApply('aLocal.get', [KInt(idx)]) + +def SET_LOCAL(idx : int): + return KApply('aLocal.set', [KInt(idx)]) + +def TEE_LOCAL(idx : int): + return KApply('aLocal.tee', [KInt(idx)]) + +####################### +# Import Descriptions # +####################### + +def func_desc(type : int, id=EMPTY_ID): + return KApply(FUNC_DESC, [id, KInt(type)]) + +def global_desc(global_type, id=EMPTY_ID): + return KApply(GLOBAL_DESC, [id, global_type]) + +def table_desc(lim, id=EMPTY_ID): + return KApply(TABLE_DESC, [id, limits(lim)]) + +def memory_desc(lim, id=EMPTY_ID): + return KApply(MEMORY_DESC, [id, limits(lim)]) + +################ +# Declarations # +################ + +def type(func_type, metadata=EMPTY_ID): + return KApply(TYPE, [func_type, metadata]) + +def func(type, locals, body, metadata=EMPTY_FUNC_METADATA): + return KApply(FUNC, [type, locals, body, metadata]) + +def table(lim, metadata=EMPTY_ID): + return KApply(TABLE, [limits(lim), metadata]) + +def memory(lim, metadata=EMPTY_ID): + return KApply(MEMORY, [limits(lim), metadata]) + +def glob(type, init, metadata=EMPTY_ID): + return KApply(GLOBAL, [type, init, metadata]) + +def elem(table_idx : int, offset, init : [int]): + return KApply(ELEM, [KInt(table_idx), offset, ints(init)]) + +def data(memory_idx : int, offset, data : bytes): + return KApply(DATA, [KInt(memory_idx), offset, KBytes(data)]) + +def start(start_idx : int): + return KApply(START, [KInt(start_idx)]) + +def imp(mod_name, name, import_desc): + return KApply(IMPORT, [mod_name, name, import_desc]) + +def export(name, index): + return KApply(EXPORT, [name, KInt(index)]) + +def module_metadata(mid=None, fids=None, filename=None): + # TODO: Implement module id and function ids metadata transformation. + kfilename = EMPTY_OPT_STRING if filename is None else KString(filename) + return KApply(MODULE_METADATA, [EMPTY_ID, EMPTY_MAP, kfilename]) + +EMPTY_MODULE_METADATA = module_metadata() + +def module(types=EMPTY_DEFNS, + funcs=EMPTY_DEFNS, + tables=EMPTY_DEFNS, + mems=EMPTY_DEFNS, + globs=EMPTY_DEFNS, + elem=EMPTY_DEFNS, + data=EMPTY_DEFNS, + start=EMPTY_DEFNS, + imports=EMPTY_DEFNS, + exports=EMPTY_DEFNS, + metadata=EMPTY_MODULE_METADATA): + """Construct a Kast of a module.""" + return KApply(MODULE, + [types, + funcs, + tables, + mems, + globs, + elem, + data, + start, + imports, + exports, + metadata]) diff --git a/docs/runtimeverification-wasm-semantics/binary-parser/test.py b/docs/runtimeverification-wasm-semantics/binary-parser/test.py new file mode 100644 index 000000000..34fb4526c --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/binary-parser/test.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 + +import wasm2kast +import json +import pyk +import sys +import subprocess +import tempfile + +WASM_definition_llvm_no_coverage_dir = '.build/defn/llvm' + +def config_to_kast_term(config): + return { 'format' : 'KAST', 'version': 2, 'term': config.to_dict() } + +def run_module(parsed_module): + input_json = config_to_kast_term(parsed_module) + krun_args = [ '--term', '--debug'] + + with tempfile.NamedTemporaryFile(mode = 'w') as tempf: + tempf.write(json.dumps(input_json)) + tempf.flush() + + command = ['krun-legacy', '--directory', + WASM_definition_llvm_no_coverage_dir, '--term', tempf.name, + '--parser', 'cat', '--output', 'json'] + process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = process.communicate(input=None) + rc = process.returncode + + if rc != 0: + raise Exception(f'Received error while running: {str(stderr)}') + +def pykPrettyPrint(module): + WASM_definition_llvm_no_coverage_dir = '.build/defn/llvm' + WASM_definition_main_file = 'test' + WASM_definition_llvm_no_coverage = pyk.readKastTerm(WASM_definition_llvm_no_coverage_dir + '/' + WASM_definition_main_file + '-kompiled/compiled.json') + WASM_symbols_llvm_no_coverage = pyk.buildSymbolTable(WASM_definition_llvm_no_coverage) + print(pyk.prettyPrintKast(module, WASM_symbols_llvm_no_coverage)) + +sys.setrecursionlimit(1500000000) + +if __name__ == "__main__": + module = wasm2kast.main() + run_module(module) diff --git a/docs/runtimeverification-wasm-semantics/binary-parser/test_kwasm_ast.py b/docs/runtimeverification-wasm-semantics/binary-parser/test_kwasm_ast.py new file mode 100644 index 000000000..1c836f13e --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/binary-parser/test_kwasm_ast.py @@ -0,0 +1,23 @@ +import pytest + +from kwasm_ast import KBytes + +KBYTES_TEST_DATA = ( + (bytes([0x0, 0x41, 0xff]), 'b"\x00\x41\xff"'), + (bytes([]), 'b""'), + (b'WASM', 'b"WASM"'), + (b'foo\xAA\x01barbaz', 'b"foo\xAA\x01barbaz"'), + (b'foo\xAAbar\x01baz', 'b"foo\xAAbar\x01baz"'), + (b'abcdefghijklmnopqrstuvwxyz', 'b"abcdefghijklmnopqrstuvwxyz"'), + (0x11223344556677889900aabbccddeeff.to_bytes(length=16, byteorder='big'), + 'b"\x11\x22\x33\x44\x55\x66\x77\x88\x99\x00\xaa\xbb\xcc\xdd\xee\xff"') +) + +@pytest.mark.parametrize(('input', 'expected'), KBYTES_TEST_DATA) +def test_kbytes(input, expected) -> None: + # When + t = KBytes(input) + + # Then + assert t.token == expected + assert t.sort.name == 'Bytes' diff --git a/docs/runtimeverification-wasm-semantics/binary-parser/wasm b/docs/runtimeverification-wasm-semantics/binary-parser/wasm new file mode 120000 index 000000000..b8e61c3ee --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/binary-parser/wasm @@ -0,0 +1 @@ +../deps/py-wasm/wasm \ No newline at end of file diff --git a/docs/runtimeverification-wasm-semantics/binary-parser/wasm2kast.py b/docs/runtimeverification-wasm-semantics/binary-parser/wasm2kast.py new file mode 100644 index 000000000..abf03ad8e --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/binary-parser/wasm2kast.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python3 + +""" +This library provides a translation from the Wasm binary format to Kast. +""" + +import sys +import json +import kwasm_ast as a + +from wasm.parsers.module import parse_module, Module +from wasm.datatypes import ValType, TypeIdx, FunctionType, Function, Table, TableType, Memory, MemoryType, Limits, Global, GlobalType, Mutability, ElementSegment, DataSegment, StartFunction, Import, Export +from wasm.opcodes import BinaryOpcode + +def main(): + if len(list(sys.argv)) == 1: + infile = sys.stdin + else: + infile = open(sys.argv[1], 'rb') + module = wasm2kast(infile) + infile.close() + return module + +def wasm2kast(wasm_bytes : bytes, filename=None) -> dict: + """Returns a dictionary representing the Kast JSON.""" + ast = parse_module(wasm_bytes) + return ast2kast(ast, filename=filename) + +def ast2kast(wasm_ast : Module, filename=None) -> dict: + """Returns a dictionary representing the Kast JSON.""" + types = a.defns([typ(x) for x in wasm_ast.types]) + funcs = a.defns([func(x) for x in wasm_ast.funcs]) + tables = a.defns([table(x) for x in wasm_ast.tables]) + mems = a.defns([memory(x) for x in wasm_ast.mems]) + globs = a.defns([glob(x) for x in wasm_ast.globals]) + elems = a.defns([elem(x) for x in wasm_ast.elem]) + datas = a.defns([data(x) for x in wasm_ast.data]) + starts = a.defns(start(wasm_ast.start)) + imports = a.defns([imp(x) for x in wasm_ast.imports]) + exports = a.defns([export(x) for x in wasm_ast.exports]) + meta = a.module_metadata(filename=filename) + return a.module(types=types, funcs=funcs, tables=tables, mems=mems, globs=globs, elem=elems, data=datas, start=starts, imports=imports, exports=exports, metadata=meta) + +######### +# Defns # +######### + +def typ(t : FunctionType): + return a.type(func_type(t.params, t.results)) + +def func(f : Function): + type = a.KInt(f.type_idx) + ls_list = [val_type(x) for x in f.locals] + locals = a.vec_type(a.val_types(ls_list)) + body = instrs(f.body) + return a.func(type, locals, body) + +def table(t : Table): + ls = limits(t.type.limits) + return a.table(ls) + +def memory(m : Memory): + ls = limits(m.type) + return a.memory(ls) + +def glob(g : Global): + t = global_type(g.type) + init = instrs(g.init) + return a.glob(t, init) + +def elem(e : ElementSegment): + offset = instrs(e.offset) + return a.elem(e.table_idx, offset, e.init) + +def data(d : DataSegment): + offset = instrs(d.offset) + return a.data(d.memory_idx, offset, d.init) + +def start(s : StartFunction): + if s is None: + return [] + return [a.start(s.function_idx)] + +def imp(i : Import): + mod_name = a.wasm_string(i.module_name) + name = a.wasm_string(i.as_name) + t = type(i.desc) + if t is TypeIdx: + desc = a.func_desc(i.desc) + elif t is GlobalType: + desc = a.global_desc(global_type(i.desc)) + elif t is TableType: + desc = a.table_desc(limits(i.desc.limits)) + elif t is MemoryType: + desc = a.memory_desc(limits(i.desc)) + return a.imp(mod_name, name, desc) + +def export(e : Export): + name = a.wasm_string(e.name) + idx = e.desc + return a.export(name, idx) + +########## +# Instrs # +########## + +block_id = 0 + +def instrs(iis): + """Turn a list of instructions into KAST.""" + # We ignore `END`. + # The AST supplied by py-wasm has already parsed these and terminated the blocks. + # We also ignore `ELSE`. + # The AST supplied by py-wasm includes the statements in the else-branch as part of the `IF` instruction. + return a.instrs([instr(i) for i in iis + if not i.opcode == BinaryOpcode.END + and not i.opcode == BinaryOpcode.ELSE]) + +def instr(i): + B = BinaryOpcode + global block_id + if i.opcode == B.BLOCK: + cur_block_id = block_id + block_id += 1 + iis = instrs(i.instructions) + res = vec_type(i.result_type) + return a.BLOCK(res, iis, a.KInt(cur_block_id)) + if i.opcode == B.BR: + return a.BR(i.label_idx) + if i.opcode == B.BR_IF: + return a.BR_IF(i.label_idx) + if i.opcode == B.BR_TABLE: + return a.BR_TABLE(i.label_indices, i.default_idx) + if i.opcode == B.CALL: + return a.CALL(i.function_idx) + if i.opcode == B.CALL_INDIRECT: + return a.CALL_INDIRECT(i.type_idx) + if i.opcode == B.ELSE: + raise(ValueError("ELSE opcode: should have been filtered out.")) + if i.opcode == B.END: + raise(ValueError("End opcode: should have been filtered out.")) + if i.opcode == B.F32_CONST: + return a.F32_CONST(i.value) + if i.opcode == B.F64_CONST: + return a.F64_CONST(i.value) + if i.opcode == B.F32_REINTERPRET_I32: + raise(ValueError('Reinterpret instructions not implemented.')) + if i.opcode == B.F64_REINTERPRET_I64: + raise(ValueError('Reinterpret instructions not implemented.')) + if i.opcode == B.GET_GLOBAL: + return a.GET_GLOBAL(i.global_idx) + if i.opcode == B.GET_LOCAL: + return a.GET_LOCAL(i.local_idx) + if i.opcode == B.I32_CONST: + return a.I32_CONST(i.value) + if i.opcode == B.I64_CONST: + return a.I64_CONST(i.value) + if i.opcode == B.I32_REINTERPRET_F32: + raise(ValueError('Reinterpret instructions not implemented.')) + if i.opcode == B.I64_REINTERPRET_F64: + raise(ValueError('Reinterpret instructions not implemented.')) + if i.opcode == B.IF: + cur_block_id = block_id + block_id += 1 + thens = instrs(i.instructions) + els = instrs(i.else_instructions) + res = vec_type(i.result_type) + return a.IF(res, thens, els, a.KInt(cur_block_id)) + if i.opcode == B.F32_STORE: + return a.F32_STORE(i.memarg.offset) + if i.opcode == B.F64_STORE: + return a.F64_STORE(i.memarg.offset) + if i.opcode == B.I32_STORE: + return a.I32_STORE(i.memarg.offset) + if i.opcode == B.I64_STORE: + return a.I64_STORE(i.memarg.offset) + if i.opcode == B.I32_STORE16: + return a.I32_STORE16(i.memarg.offset) + if i.opcode == B.I64_STORE16: + return a.I64_STORE16(i.memarg.offset) + if i.opcode == B.I32_STORE8: + return a.I32_STORE8(i.memarg.offset) + if i.opcode == B.I64_STORE8: + return a.I64_STORE8(i.memarg.offset) + if i.opcode == B.I64_STORE32: + return a.I64_STORE32(i.memarg.offset) + if i.opcode == B.F32_LOAD: + return a.F32_LOAD(i.memarg.offset) + if i.opcode == B.F64_LOAD: + return a.F64_LOAD(i.memarg.offset) + if i.opcode == B.I32_LOAD: + return a.I32_LOAD(i.memarg.offset) + if i.opcode == B.I64_LOAD: + return a.I64_LOAD(i.memarg.offset) + if i.opcode == B.I32_LOAD16_S: + return a.I32_LOAD16_S(i.memarg.offset) + if i.opcode == B.I32_LOAD16_U: + return a.I32_LOAD16_U(i.memarg.offset) + if i.opcode == B.I64_LOAD16_S: + return a.I64_LOAD16_S(i.memarg.offset) + if i.opcode == B.I64_LOAD16_U: + return a.I64_LOAD16_U(i.memarg.offset) + if i.opcode == B.I32_LOAD8_S: + return a.I32_LOAD8_S(i.memarg.offset) + if i.opcode == B.I32_LOAD8_U: + return a.I32_LOAD8_U(i.memarg.offset) + if i.opcode == B.I64_LOAD8_S: + return a.I64_LOAD8_S(i.memarg.offset) + if i.opcode == B.I64_LOAD8_U: + return a.I64_LOAD8_U(i.memarg.offset) + if i.opcode == B.I64_LOAD32_S: + return a.I64_LOAD32_S(i.memarg.offset) + if i.opcode == B.I64_LOAD32_U: + return a.I64_LOAD32_U(i.memarg.offset) + if i.opcode == B.LOOP: + cur_block_id = block_id + block_id += 1 + iis = instrs(i.instructions) + res = vec_type(i.result_type) + return a.LOOP(res, iis, a.KInt(cur_block_id)) + if i.opcode == B.SET_GLOBAL: + return a.SET_GLOBAL(i.global_idx) + if i.opcode == B.SET_LOCAL: + return a.SET_LOCAL(i.local_idx) + if i.opcode == B.TEE_LOCAL: + return a.TEE_LOCAL(i.local_idx) + # Catch all for operations without direct arguments. + op = i.opcode + return eval('a.' + i.opcode.name) + +######## +# Data # +######## + +def val_type(t : ValType): + if t == ValType.i32: + return a.i32 + if t == ValType.i64: + return a.i64 + if t == ValType.f32: + return a.f32 + if t == ValType.f64: + return a.f64 + +def vec_type(ts : [ValType]): + _ts = [val_type(x) for x in ts] + return a.vec_type(a.val_types(_ts)) + +def func_type(params, results): + pvec = vec_type(params) + rvec = vec_type(results) + return a.func_type(pvec, rvec) + +def limits(l : Limits): + return (l.min, l.max) + +def global_type(t : GlobalType): + vt = val_type(t.valtype) + if t.mut is Mutability.const: + return a.global_type(a.MUT_CONST, vt) + return a.global_type(a.MUT_VAR, vt) + +######## +# Main # +######## + +if __name__ == "__main__": + main() diff --git a/docs/runtimeverification-wasm-semantics/convert.py b/docs/runtimeverification-wasm-semantics/convert.py new file mode 100644 index 000000000..bfebd0f0b --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/convert.py @@ -0,0 +1,39 @@ +# This script is not used during runtime. +# It just helps converting some conformances test to a test that we can use by removing unsupported functions and converting hexfloat to normal float. +# However it does not support multi-line assertions, function definitions, etc. +# The test files under directory tests/simple/float were generated by this script. +# example usage: python convert.py f32.wast + +import re +import sys + + +def hex2float(h): + print(h) + if "nan" in h: + return h.replace("nan", "NaN") + elif "inf" in h: + return h.replace("inf", "Infinity") + elif "0x" not in h: + return h + else: + return h.split()[0] + " " + "%e" % (float.fromhex(h.split()[1])) + + +def main(): + filename = sys.argv[1] + infile = "tests/wasm-tests/test/core/%s" % filename + outfile = open("tests/simple/%s-c.%s" % tuple(filename.split(".")), "w") + unsupported = ["nan:", "-nan", "reinterpret", + "assert_return_canonical_nan", "assert_return_arithmetic_nan", "assert_invalid", "assert_malformed"] + for line in (open(infile).readlines()): + if any(x in line for x in unsupported): + outfile.write(";; "+line) + else: + outfile.write(re.sub(r"(?:(?:f32|f64)\.const )([^\)]+)", + lambda m: hex2float(m.group()), line)) + outfile.write("\n#clearConfig\n") + + +if __name__ == "__main__": + main() diff --git a/docs/runtimeverification-wasm-semantics/data.md b/docs/runtimeverification-wasm-semantics/data.md new file mode 100644 index 000000000..10334132b --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/data.md @@ -0,0 +1,606 @@ +WebAssembly Data +================ + +```k +module WASM-DATA-SYNTAX + imports WASM-DATA-COMMON-SYNTAX +endmodule +``` + +Common Syntax +------------- + +```k +module WASM-DATA-COMMON-SYNTAX + imports INT-SYNTAX + imports FLOAT-SYNTAX +``` + +### WASM Token Sorts + +```k + syntax IdentifierToken [token] + syntax WasmIntToken [token] + syntax #Layout [token] + syntax WasmStringToken [token] + // ------------------------------ +``` + +### Identifiers + +And we use `OptionalId` to handle the case where an identifier could be omitted. + +```k + syntax Identifier ::= IdentifierToken + syntax OptionalId ::= "" [klabel(.Identifier), symbol] + | Identifier + // -------------------------------- +``` + +### Strings + +Wasm binary data can sometimes be specified as a string. +The string considered to represent the sequence of UTF-8 bytes that encode it. +The exception is for characters that are explicitly escaped which can represent bytes in hexadecimal form. + +```k + syntax WasmString ::= ".WasmString" + | WasmStringToken + // ------------------------------------- + + syntax String ::= #parseWasmString ( WasmStringToken ) [function, total, hook(STRING.token2string)] + // ---------------------------------------------------------------------------------------------------------- + + syntax DataString ::= List{WasmString, ""} [klabel(listWasmString)] + // ------------------------------------------------------------------- +``` + +### Indices + +Many constructions in Wasm, such a functions, labels, locals, types, etc., are referred to by their index. +In the abstract syntax of Wasm, indices are 32 bit unsigned integers. +However, we extend the `Index` sort with another subsort, `Identifier`, in the text format. + +```k + syntax Index ::= WasmInt + // ------------------------ +``` + +### ElemSegment + +Element Segment is a list of indices. +It is used when initializing a WebAssembly table, or used as the parameter of the `br_table` function. + +```k + syntax ElemSegment ::= List{Index, ""} [klabel(listIndex)] + // ---------------------------------------------------------- +``` + +### WebAssembly Types + +#### Base Types + +WebAssembly has four basic types, for 32 and 64 bit integers and floats. + +```k + syntax IValType ::= "i32" [klabel(i32), symbol] | "i64" [klabel(i64), symbol] + syntax FValType ::= "f32" [klabel(f32), symbol] | "f64" [klabel(f64), symbol] + syntax ValType ::= IValType | FValType + // --------------------------------------- +``` + +#### Type Constructors + +There are two basic type-constructors: sequencing (`[_]`) and function spaces (`_->_`). + +```k + syntax ValTypes ::= List{ValType, ""} [klabel(listValTypes), symbol] + // -------------------------------------------------------------------- +``` + +### Integers + +Here we define the rules about what integer formats can be used in Wasm. +For the core language, only regular integers are allowed. + +```k + syntax WasmInt ::= Int + // ---------------------- +``` +### Type Mutability + +```k + syntax Mut ::= "const" [klabel(mutConst), symbol] + | "var" [klabel(mutVar), symbol] + // ----------------------------------------------- +``` + + +### Basic Values + +WebAssembly values are either integers or floating-point numbers, of 32- or 64-bit widths. + +```k + syntax Number ::= Int | Float + // ----------------------------- +``` + +### External Values + +An `external value` is the runtime representation of an entity that can be `imported` or `exported`. +It is an `address` denoting either a `function instance`, `table instance`, `memory instance`, or `global instances` in the shared store. + +```k + syntax AllocatedKind ::= "func" | "table" | "memory" | "global" + syntax Externval ::= AllocatedKind Index + // -------------------------------------------- +``` + +```k +endmodule +``` + +`WASM-DATA` module +------------------ + +```k +module WASM-DATA + imports WASM-DATA-COMMON-SYNTAX + + imports INT + imports BOOL + imports STRING + imports LIST + imports MAP + imports FLOAT + imports BYTES + imports K-EQUAL +``` + +### Identifiers + +In `KWASM` rules, we use `#freshId ( Int )` to generate a fresh identifier based on the element index in the current module. +```k + syntax Identifier ::= #freshId ( Int ) + // -------------------------------------- +``` + +In KWasm we store identifiers in maps from `Identifier` to `Int`, the `Int` being an index. +This rule handles adding an `OptionalId` as a map key, but only when it is a proper identifier. + +```k + syntax Map ::= #saveId (Map, OptionalId, Int) [function] + // ------------------------------------------------------- + rule #saveId (MAP, ID:OptionalId, _) => MAP requires notBool isIdentifier(ID) + rule #saveId (MAP, ID:Identifier, IDX) => MAP [ID <- IDX] +``` + +`Int` is the basic form of index, and indices always need to resolve to integers. +In the text format, `Index` is extended with the `Identifier` subsort, and there needs to be a way to resolve it to an `Int`. +In Wasm, the current "context" contains a mapping from identifiers to indices. +The `#ContextLookup` function provides an extensible way to get an `Int` from an index. +Any extension of the `Index` type requires that the function `#ContextLookup` is suitably extended. +For `Int`, however, a the context is irrelevant and the index always just resolves to the integer. + +```k + syntax Int ::= #ContextLookup ( Map , Index ) [function] + // -------------------------------------------------------- + rule #ContextLookup(_IDS:Map, I:Int) => I +``` + +### ElemSegment + +```k + syntax Ints ::= List{Int, ""} [klabel(listInt), symbol] + // ------------------------------------------------------- + + syntax Int ::= #lenElemSegment (ElemSegment) [function] + syntax Index ::= #getElemSegment (ElemSegment, Int) [function] + syntax Int ::= #lenInts (Ints) [function] + syntax Int ::= #getInts (Ints, Int) [function] + // -------------------------------------------------------------- + rule #lenElemSegment(.ElemSegment) => 0 + rule #lenElemSegment(_TFIDX ES) => 1 +Int #lenElemSegment(ES) + + rule #getElemSegment(E _ES, 0) => E + rule #getElemSegment(_E ES, I) => #getElemSegment(ES, I -Int 1) requires I >Int 0 + + rule #lenInts(.Ints) => 0 + rule #lenInts(_TFIDX ES) => 1 +Int #lenInts(ES) + + rule #getInts(E _ES, 0) => E + rule #getInts(_E ES, I) => #getInts(ES, I -Int 1) requires I >Int 0 + + syntax Ints ::= elemSegment2Ints ( ElemSegment ) [function] + // ----------------------------------------------------------- + rule elemSegment2Ints(.ElemSegment) => .Ints + rule elemSegment2Ints(E:Int ES) => E elemSegment2Ints(ES) +``` + +### OptionalInt + +In some cases, an integer is optional, such as when either giving or omitting the max bound when defining a table or memory. +The sort `OptionalInt` provides this potentially "undefined" `Int`. + +```k + syntax OptionalInt ::= Int | ".Int" + // ----------------------------------- +``` + +### Limits + +Tables and memories have limits, defined as either a single `Int` or two `Int`s, representing min and max bounds. + +```k + syntax Limits ::= #limitsMin(Int) [klabel(limitsMin), symbol] + | #limits(Int, Int) [klabel(limitsMinMax), symbol] + // ------------------------------------------------------------------ +``` + +### Type Constructors + +There are two basic type-constructors: sequencing (`[_]`) and function spaces (`_->_`). + +```k + syntax VecType ::= "[" ValTypes "]" [klabel(aVecType), symbol] + // --------------------------------------------------------------- + + syntax FuncType ::= VecType "->" VecType [klabel(aFuncType), symbol] + // -------------------------------------------------------------------- + + syntax Int ::= lengthValTypes ( ValTypes ) [function, total] + // ----------------------------------------------------------------- + rule lengthValTypes(.ValTypes) => 0 + rule lengthValTypes(_V VS) => 1 +Int lengthValTypes(VS) +``` + +All told, a `Type` can be a value type, vector of types, or function type. + +```k + syntax Type ::= ".Type" | ValType | VecType | FuncType + // ------------------------------------------------------ +``` + +We can append two `ValTypes`s with the `_+_` operator. + +```k + syntax ValTypes ::= ValTypes "+" ValTypes [function, total] + // ---------------------------------------------------------------- + rule .ValTypes + VTYPES' => VTYPES' + rule (VT VTYPES) + VTYPES' => VT (VTYPES + VTYPES') +``` + +Also we can reverse a `ValTypes` with `#revt` + +```k + syntax ValTypes ::= #revt ( ValTypes ) [function, total] + | #revt ( ValTypes , ValTypes ) [function, total, klabel(#revtAux)] + // ------------------------------------------------------------------------------------------ + rule #revt(VT) => #revt(VT, .ValTypes) + + rule #revt(.ValTypes, VT') => VT' + rule #revt(V VT , VT') => #revt(VT, V VT') +``` + +### Type Information + +The `#width` function returns the bit-width of a given `IValType`. + +```k + syntax Int ::= #width ( IValType ) [function, total] + syntax Int ::= #numBytes ( IValType ) [function, total, smtlib(numBytes)] + // ------------------------------------------------------------------------------ + rule #width(i32) => 32 + rule #width(i64) => 64 + + rule #numBytes(ITYPE) => #width(ITYPE) /Int 8 [concrete] +``` + +`2 ^Int 32` and `2 ^Int 64` are used often enough to warrant providing helpers for them. + +```k + syntax Int ::= #pow ( IValType ) [function, total, smtlib(pow )] /* 2 ^Int #width(T) */ + | #pow1 ( IValType ) [function, total, smtlib(pow1)] /* 2 ^Int (#width(T) -Int 1) */ + // ------------------------------------------------------------------------------------------------------ + rule #pow1(i32) => 2147483648 + rule #pow (i32) => 4294967296 + rule #pow1(i64) => 9223372036854775808 + rule #pow (i64) => 18446744073709551616 +``` + +### Type Mutability + +```k + syntax Mut ::= ".Mut" + // ---------------------- +``` + +### Values + +Proper values are numbers annotated with their types. + +```k + syntax IVal ::= "<" IValType ">" Int [klabel(<_>_)] + syntax FVal ::= "<" FValType ">" Float [klabel(<_>_)] + syntax Val ::= "<" ValType ">" Number [klabel(<_>_)] + | IVal | FVal + // --------------------------- +``` + +We also add `undefined` as a value, which makes many partial functions in the semantics total. + +```k + syntax Val ::= "undefined" + // -------------------------- +``` + +#### Value Operations + +The `#chop` function will ensure that an integer value is wrapped to the correct bit-width. +The `#wrap` function wraps an integer to a given byte width. +The `#get` function extracts the underlying K integer from a WASM `IVal`. + +```k + syntax IVal ::= #chop ( IVal ) [function, total] + // ----------------------------------------------------- + rule #chop(< ITYPE > N) => < ITYPE > (N modInt #pow(ITYPE)) + + syntax Int ::= #wrap ( Int , Int ) [function, total] + // --------------------------------------------------------- + rule [wrap-Positive] : #wrap(WIDTH, N) => N &Int ((1 < 0 requires notBool 0 N) => N +``` + +In `K` all `Float` numbers are of 64-bits width by default, so we need to downcast a `f32` float to 32-bit manually. +The `#round` function casts a `f64` float to a `f32` float. +`f64` floats has 1 bit for the sign, 53 bits for the value and 11 bits for exponent. +`f32` floats has 1 bit for the sign, 23 bits for the value and 8 bits for exponent. + +```k + syntax FVal ::= #round ( FValType , Number ) [function] + // ------------------------------------------------------- + rule #round(f64 , N:Float) => < f64 > roundFloat(N, 53, 11) [concrete] + rule #round(f32 , N:Float) => < f32 > roundFloat(N, 24, 8) [concrete] + rule #round(f64 , N:Int ) => < f64 > Int2Float(N, 53, 11) [concrete] + rule #round(f32 , N:Int ) => < f32 > Int2Float(N, 24, 8) [concrete] +``` + +#### Signed Interpretation + +Functions `#signed` and `#unsigned` allow for easier operation on twos-complement numbers. +These functions assume that the argument integer is in the valid range of signed and unsigned values of the respective type, so they will not correctly map arbitrary integers into the corret range. +Some operations extend integers from 1, 2, or 4 bytes, so a special function with a bit width argument helps with the conversion. + +```k + syntax Int ::= #signed ( IValType , Int ) [function] + | #unsigned ( IValType , Int ) [function] + | #signedWidth ( Int , Int ) [function] + // --------------------------------------------------------- + rule #signed(ITYPE, N) => N requires 0 <=Int N andBool N N -Int #pow(ITYPE) requires #pow1(ITYPE) <=Int N andBool N N +Int #pow(ITYPE) requires N N requires 0 <=Int N + + rule #signedWidth(1, N) => N requires 0 <=Int N andBool N N -Int 256 requires 128 <=Int N andBool N N requires 0 <=Int N andBool N N -Int 65536 requires 32768 <=Int N andBool N #signed(i32, N) +``` + +#### Boolean Interpretation + +Function `#bool` converts a `Bool` into an `Int`. + +```k + syntax Int ::= #bool ( Bool ) [function, total] + // ---------------------------------------------------- + rule #bool( B:Bool ) => 1 requires B + rule #bool( B:Bool ) => 0 requires notBool B +``` + +### Data Structures + +WebAssembly is a stack-machine, so here we provide the stack to operate over. +Operator `_++_` implements an append operator for sort `ValStack`. + +```k + syntax ValStack ::= ".ValStack" + | Val ":" ValStack + | ValStack "++" ValStack [function, total] + // ----------------------------------------------------------------- + rule .ValStack ++ VALSTACK' => VALSTACK' + rule (SI : VALSTACK) ++ VALSTACK' => SI : (VALSTACK ++ VALSTACK') +``` + +`#zero` will create a specified stack of zero values in a given type. +`#take` will take the prefix of a given stack. +`#drop` will drop the prefix of a given stack. +`#revs` will reverse a given stack. + +**NOTE**: `#take` and `#drop` are _total_, so in case they could not take/drop enough values before running out, they just return the empty `.ValStack`. +Each call site _must_ ensure that this is desired behavior before using these functions. + +```k + syntax ValStack ::= #zero ( ValTypes ) [function, total] + | #take ( Int , ValStack ) [function, total] + | #drop ( Int , ValStack ) [function, total] + | #revs ( ValStack ) [function, total] + | #revs ( ValStack , ValStack ) [function, total, klabel(#revsAux)] + // ------------------------------------------------------------------------------------------ + rule #zero(.ValTypes) => .ValStack + rule #zero(ITYPE:IValType VTYPES) => < ITYPE > 0 : #zero(VTYPES) + rule #zero(FTYPE:FValType VTYPES) => < FTYPE > 0.0 : #zero(VTYPES) + + rule #take(N, _) => .ValStack requires notBool N >Int 0 + rule #take(N, .ValStack) => .ValStack requires N >Int 0 + rule #take(N, V : VS) => V : #take(N -Int 1, VS) requires N >Int 0 + + rule #drop(N, VS) => VS requires notBool N >Int 0 + rule #drop(N, .ValStack) => .ValStack requires N >Int 0 + rule #drop(N, _ : VS) => #drop(N -Int 1, VS) requires N >Int 0 + + rule #revs(VS) => #revs(VS, .ValStack) + + rule #revs(.ValStack, VS') => VS' + rule #revs(V : VS , VS') => #revs(VS, V : VS') +``` + +### Strings + +Wasm uses a different character escape rule with K, so we need to define the `unescape` function ourselves. + +```k + syntax String ::= unescape(String) [function] + | unescape(String, Int, String) [function, klabel(unescapeAux)] + // ------------------------------------------------------------------------------- + rule unescape(S ) => unescape(S, 1, "") + rule unescape(S, IDX, SB) => SB requires IDX ==Int lengthString(S) -Int 1 +``` + +Unescaped characters just directly gets added to the output. +The escaped character starts with a "\" and followed by 2 hexdigits will be converted to a unescaped character before stored. + +```k + rule unescape(S, IDX, SB) => unescape(S, IDX +Int 1, SB +String substrString(S, IDX, IDX +Int 1)) + requires IDX unescape(S, IDX +Int 3, SB +String chrChar(String2Base(substrString(S, IDX +Int 1, IDX +Int 3), 16))) + requires IDX unescape(S, IDX +Int 2, SB +String chrChar(String2Base("09", 16))) + requires IDX unescape(S, IDX +Int 2, SB +String chrChar(String2Base("0A", 16))) + requires IDX unescape(S, IDX +Int 2, SB +String chrChar(String2Base("0D", 16))) + requires IDX unescape(S, IDX +Int 2, SB +String chrChar(String2Base("22", 16))) + requires IDX unescape(S, IDX +Int 2, SB +String chrChar(String2Base("27", 16))) + requires IDX unescape(S, IDX +Int 2, SB +String chrChar(String2Base("5C", 16))) + requires IDX +}`. +The implementation is not correct for now because the UTF-8 encoding is not implemented. + +```k + syntax Int ::= #idxCloseBracket ( String, Int ) [function] + // ---------------------------------------------------------- + rule #idxCloseBracket ( S, I ) => I requires substrString(S, I, I +Int 1) ==String "}" + rule #idxCloseBracket ( S, I ) => #idxCloseBracket ( S, I +Int 1 ) requires substrString(S, I, I +Int 1) =/=String "}" + + syntax Bytes ::= #encodeUTF8 ( Int ) [function] + // ----------------------------------------------- + rule #encodeUTF8 (I) => Int2Bytes(I, BE, Unsigned) requires I <=Int 127 + rule #encodeUTF8 (I) => Int2Bytes(((((I &Int 1984) >>Int 6) +Int 192) <=Int 128 andBool I <=Int 2047 + rule #encodeUTF8 (I) => Int2Bytes(((((I &Int 61440) >>Int 12) +Int 224) <>Int 6) +Int 128) <=Int 2048 andBool I <=Int 65535 + rule #encodeUTF8 (I) => Int2Bytes(((((I &Int 1835008) >>Int 18) +Int 240) <>Int 12) +Int 128) <>Int6) +Int 128) <=Int 65536 andBool I <=Int 1114111 + + rule unescape(S, IDX, SB) => unescape(S, #idxCloseBracket(S, IDX) +Int 1, SB +String Bytes2String(#encodeUTF8(String2Base(substrString(S, IDX +Int 3, #idxCloseBracket(S, IDX +Int 3)), 16)))) + requires substrString(S, IDX, IDX +Int 1) ==K "\\" + andBool substrString(S, IDX +Int 1, IDX +Int 2) ==K "u" +``` + +`DataString`, as is defined in the wasm semantics, is a list of `WasmString`s. +`#concatDS` concatenates them together into a single string. +The strings to connect needs to be unescaped before concatenated, because the `unescape` function removes the quote sign `"` before and after each substring. + +```k + syntax String ::= #concatDS ( DataString ) [function] + | #concatDS ( DataString, String ) [function, klabel(#concatDSAux)] + // ----------------------------------------------------------------------------------- + rule #concatDS ( DS ) => #concatDS ( DS, "" ) + rule #concatDS ( .DataString , S ) => S + rule #concatDS ( WS:WasmStringToken DS , S ) => #concatDS ( DS , S +String unescape(#parseWasmString(WS)) ) +``` + +`#DS2Bytes` converts a `DataString` to a K builtin `Bytes`. + +```k + syntax Bytes ::= #DS2Bytes (DataString) [function] + // -------------------------------------------------- + rule #DS2Bytes(DS) => String2Bytes(#concatDS(DS)) +``` + +### Linear Memory + +Wasm memories are byte arrays, sized in pages of 65536 bytes, initialized to be all zero bytes. + +- `#setBytesRange(BM, START, BS)` assigns a contiguous chunk of `BS` to `BM` starting at position `N`. +- `#setRange(BM, START, VAL, WIDTH)` writes the integer `I` to memory as bytes (little-endian), starting at index `N`. + +```k + syntax Bytes ::= #setBytesRange ( Bytes , Int , Bytes ) [function, total] + // ------------------------------------------------------------------------------ + rule #setBytesRange(BM, START, BS) => replaceAtBytes(padRightBytes(BM, START +Int lengthBytes(BS), 0), START, BS) + requires 0 <=Int START + + rule #setBytesRange(_, START, _ ) => .Bytes + requires notBool (0 <=Int START) + + syntax Bytes ::= #setRange ( Bytes , Int , Int , Int ) [function, total, smtlib(setRange)] + // ----------------------------------------------------------------------------------------------- + rule #setRange(BM, ADDR, VAL, WIDTH) => BM + requires notBool (0 #setBytesRange(BM, ADDR, Int2Bytes(WIDTH, VAL, LE)) + requires 0 .Bytes + requires notBool (0 <=Int START andBool 0 <=Int WIDTH) + + rule #getBytesRange(BM, START, WIDTH) => substrBytes(padRightBytes(BM, START +Int WIDTH, 0), START, START +Int WIDTH) + requires (0 <=Int START andBool 0 <=Int WIDTH) + andBool START padRightBytes(.Bytes, WIDTH, 0) + requires (0 <=Int START andBool 0 <=Int WIDTH) + andBool notBool (START 0 + requires notBool (0 Bytes2Int(#getBytesRange(BM, ADDR, WIDTH), LE, Unsigned) + requires 0 &2 ; } +fatal() { echo "[FATAL] $@" ; exit 1 ; } + +kwasm_dir="${KWASM_DIR:-$(dirname $0)}" +build_dir="$kwasm_dir/.build" +defn_dir="${KWASM_DEFN_DIR:-$build_dir/defn}" +lib_dir="$build_dir/local/lib" +k_release_dir="${K_RELEASE:-$kwasm_dir/deps/k/k-distribution/target/release/k}" +if [[ ! -f "${k_release_dir}/bin/kompile" ]]; then + if which kompile &> /dev/null; then + k_release_dir="$(dirname $(which kompile))/.." + else + fatal "Cannot find K Installation!" + fi +fi + +export PATH="$k_release_dir/lib/native/linux:$k_release_dir/lib/native/linux64:$k_release_dir/bin/:$PATH" +export LD_LIBRARY_PATH="$k_release_dir/lib/native/linux64:$lib_dir:${LD_LIBRARY_PATH:-}" + +test_logs="$build_dir/logs" +mkdir -p "$test_logs" +test_log="$test_logs/tests.log" + +export K_OPTS="${K_OPTS:--Xmx16G -Xss512m}" + +# Utilities +# --------- + +preprocess() { + local this_script_dir tmp_dir tmp_input + this_script_dir="$(dirname $0)" + tmp_dir="$(mktemp -d)" + tmp_input="$tmp_dir/$(basename $run_file))" + touch "$tmp_input" + python3 "$this_script_dir/preprocessor.py" "$run_file" > "$tmp_input" + run_file="$tmp_input" +} + +# Runners +# ------- + +run_krun() { + local additional_run_args + + additional_run_args=() + ! $bug_report || additional_run_args+=(--haskell-backend-command "kore-exec --bug-report $bug_report_name") + + preprocess + krun --directory "$backend_dir" "$run_file" "${additional_run_args[@]}" "$@" +} + +run_krun_legacy() { + local additional_run_args + + additional_run_args=() + ! $bug_report || additional_run_args+=(--haskell-backend-command "kore-exec --bug-report $bug_report_name") + + preprocess + krun-legacy --directory "$backend_dir" "$run_file" "${additional_run_args[@]}" "$@" +} + +run_kast() { + local output_mode + + preprocess + output_mode="${1:-kast}" ; shift + kast --directory "$backend_dir" "$run_file" --output "$output_mode" "$@" +} + +run_prove() { + local additional_proof_args + + def_module="$1" ; shift + + additional_proof_args=() + ! $repl || additional_proof_args+=(--debugger) + ! $bug_report || additional_proof_args+=(--haskell-backend-command "kore-exec --bug-report $bug_report_name") + + kprove-legacy --directory "$backend_dir" -I "$kwasm_dir" "$run_file" --def-module "$def_module" "${additional_proof_args[@]}" "$@" +} + +# Main +# ---- + +usage() { + echo " + usage: $0 run [--backend (llvm|haskell)] [--bug-report] * + $0 run-legacy [--backend (llvm|haskell)] [--bug-report] * + $0 kast [--backend (llvm|haskell)] * + $0 prove [--backend (haskell)] [--repl|--bug-report] * + + $0 [help|--help|version|--version] + + $0 run : Run a single WebAssembly program + $0 run-legacy : Run a single WebAssembly program using krun-legacy + $0 kast : Parse a single WebAssembly program and output it in supported format + $0 prove : Run a WebAssembly K proof + + $0 help : Display this help message. + $0 version : Display the KWasm, K, Kore, and Z3 versions in use. + + Note: is a path to a file containing a WebAssembly program. + is a K specification to be proved. + are any arguments you want to pass to K when executing/proving. + is the format for Kast to output the term in. + is the module to take as axioms for verification. +" +} + +usage_fatal() { + usage + fatal "$@" +} + +[[ ! -z ${1:-} ]] || usage_fatal "Must supply a command to run." +if [[ "$1" == '--help' ]] || [[ "$1" == 'help' ]]; then + usage + exit 0 +fi + +if [[ "$1" == 'version' ]] || [[ "$1" == '--version' ]]; then + notif "KWasm Version" + git rev-parse --short HEAD + notif "K Version" + kompile --version + notif "Kore Version" + kore-exec --version + notif "Z3 Version" + z3 --version + exit 0 +fi + +run_command="$1"; shift + +[[ ! -z ${1:-} ]] || usage_fatal "Must supply a file to work on." + +backend="llvm" +repl=false +bug_report=false +[[ ! "$run_command" == 'prove' ]] || backend='haskell' +args=() +while [[ $# -gt 0 ]]; do + arg="$1" + case $arg in + --backend) backend="$2" ; shift 2 ;; + --repl) repl=true ; shift ;; + --bug-report) bug_report=true ; shift ;; + *) args+=("$1") ; shift ;; + esac +done +set -- "${args[@]}" + +! $repl || [[ "$backend" == "haskell" ]] || fatal "--repl option only available for Haskell backend." +! $bug_report || [[ "$backend" == "haskell" ]] || fatal "--bug-report option only available for Haskell backend." + +backend_dir="$defn_dir/$backend" + +# get the run file +[[ ! -z ${1:-} ]] || usage_fatal "Must supply a file to run on." +run_file="$1" ; shift +if [[ "$run_file" == '-' ]]; then + tmp_input="$(mktemp)" + trap "rm -rf $tmp_input" INT TERM EXIT + cat - > "$tmp_input" + run_file="$tmp_input" +fi +[[ -f "$run_file" ]] || fatal "File does not exist: $run_file" +bug_report_name="kwasm-bug-$(basename "$run_file")" + +case "$run_command-$backend" in + run-@(llvm|haskell) ) run_krun "$@" ;; + run-legacy-@(llvm|haskell) ) run_krun_legacy "$@" ;; + kast-@(llvm|haskell) ) run_kast "$@" ;; + prove-@(haskell) ) run_prove "$@" ;; + *) usage_fatal "Unknown command on '$backend' backend: $run_command" ;; +esac diff --git a/docs/runtimeverification-wasm-semantics/kwasm-lemmas.md b/docs/runtimeverification-wasm-semantics/kwasm-lemmas.md new file mode 100644 index 000000000..e05f317c4 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/kwasm-lemmas.md @@ -0,0 +1,344 @@ +KWASM Lemmas +============ + +These lemmas aid in verifying WebAssembly programs behavior. +They are part of the *trusted* base, and so should be scrutinized carefully. + +```k +module KWASM-LEMMAS [symbolic] + imports WASM-TEXT + imports BYTES-KORE + imports INT-SYMBOLIC +``` + +Basic logic +----------- + +```k + rule #bool(P) ==Int 0 => notBool P [simplification] +``` + +Basic arithmetic +---------------- + +// TODO: Upstream the lemmas in this section into K. + +### Modular Arithmetic + +Z3 is slow and unreliable in reasoning about modular arithmetic. +Therefore we want to make structural simplifications wherever possible. + +`X modInt N` is undefined for `N ==Int 0`, so we must take care to check that `N =/=Int 0`. +At the same time, we don't want to impose unnecessary side-conditions, so the non-zero condition can be implied by the other conditions. +For example, `X modInt N ==Int Y` implies `N =/=Int 0`, because only non-zero `N` could have the right-hand side resolve to an `Int`. +For simplicity, we impose that `N >Int 0`. +Not however that K defines `X modInt N ==Int X modInt (-N)`. + +#### Rules for Expressions With Only Modulus + +These are given in pure modulus form, and in form with `#wrap`, which is modulus with a power of 2 for positive `N`. + +```k + rule #wrap(N, X) => X + requires 0 <=Int N + andBool 0 <=Int X + andBool X 0 + [simplification] +``` + +`modInt` selects the least non-negative representative of a congruence class modulo `N`. + +```k + rule (X modInt M) modInt N => X modInt M + requires M >Int 0 + andBool M <=Int N + [simplification] + + rule #wrap(N, #wrap(M, X)) => #wrap(M, X) + requires M <=Int N + [simplification] +``` + +Proof: + +``` +Since 0 <= x mod m < m <= n, (x mod m) mod n = x mod m +``` + +```k + rule (X modInt M) modInt N => X modInt N + requires M >Int 0 + andBool N >Int 0 + andBool M modInt N ==Int 0 + [simplification] + + rule #wrap(N, #wrap(M, X)) => #wrap(N, X) + requires notBool (M <=Int N) + [simplification] +``` + +Proof: + +``` +Assume m = n * k for some k > 0. +x = m * q + r, for a unique q and r s.t. 0 <= r < m +(x mod m) mod n + = r mod n + = (n * ( k * q) + r) mod n + = m * q + r mod n + = x mod n +``` + +#### Modulus Over Addition + +```k + rule (_X *Int M +Int Y) modInt N => Y modInt N + requires M >Int 0 + andBool N >Int 0 + andBool M modInt N ==Int 0 + [simplification] + + rule (Y +Int _X *Int M) modInt N => Y modInt N + requires M >Int 0 + andBool N >Int 0 + andBool M modInt N ==Int 0 + [simplification] + + rule #wrap(N, (_X < #wrap(N, Y) + requires 0 <=Int M + andBool (N *Int 8) <=Int M + [simplification] + + rule #wrap(N, Y +Int (_X < #wrap(N, Y) + requires 0 <=Int M + andBool (N *Int 8) <=Int M + [simplification] +``` + +Proof: + +``` +Assume m = k * n for some k (side condition). +x * m + y mod n = x * (k * n) + y mod n = y mod n +``` + +```k + rule ((X modInt M) +Int Y) modInt N => (X +Int Y) modInt N + requires M >Int 0 + andBool N >Int 0 + andBool M modInt N ==Int 0 + [simplification] + + rule (X +Int (Y modInt M)) modInt N => (X +Int Y) modInt N + requires M >Int 0 + andBool N >Int 0 + andBool M modInt N ==Int 0 + [simplification] + + rule #wrap(N, #wrap(M, X) +Int Y) => #wrap(N, X +Int Y) + requires N <=Int M + [simplification] + + rule #wrap(N, X +Int #wrap(M, Y)) => #wrap(N, X +Int Y) + requires N <=Int M + [simplification] +``` + +Proof: + +``` +Assume m = l * n +x = k * m + r, r < m +x mod m + y = r + y +(x + y) mod n + = k * m + r + y mod n + = k * l * n + r + y mod n + = r + y mod n + = (x mod m + y) mod n +``` + +#### Bit Shifting + +We want K to understand what a bit-shift is. + +```k + rule (_X < 0 + requires (2 ^Int N) modInt M ==Int 0 + [simplification] + + rule #wrap(M, _X < 0 + requires (M *Int 8) <=Int N + [simplification] + + rule (X >>Int N) => 0 requires X 0 requires M >Int N) >>Int M => X >>Int (N +Int M) [simplification] + rule (X < X <>Int M => X <=Int M [simplification] + rule (X <>Int M => X >>Int (M -Int N) requires notBool (N >=Int M) [simplification] +``` + +```k + rule ((X <>Int N => (X <>Int N) requires M >=Int N [simplification] + rule (Y +Int (X <>Int N => (X <>Int N) requires M >=Int N [simplification] +``` + +Proof: + +``` +Let x' = x << m +=> The least m bits of x' are 0. +=> The least m bits of x' + y are the same as the least m bits of y, and there can be no carry in adding the least m bits. +=> The least (m-n) bits of (x' + y) >> n are the same as the least (m-n) bits of (y >> n), or y[n..], and there can be no carry in adding the least (m-n) bits. +=> ((x << m) + y) >> n is the same as adding x to the m'th and higher bits of y, and then concatenating the lesat (m-n) bytes of y[n..] +=> ((x << m) + y) >> n = y[n..(m-1)] : (x + y[m..]) +=> ((x << m) + y) >> n + = ((x + y[m..]) << (m-n)) + y[n..(m-1)] + = (x << (m-n)) + (y[m..] << (m-n)) + y[n..(m-1)] + = (x << (m-n)) + (y[n..(m-1)] : y[m..]) + = (x << (m-n)) + (y >> n) +``` + +The following rules decrease the modulus by rearranging it around a shift. + +```k + rule (X modInt POW) >>Int N => (X >>Int N) modInt (POW /Int (2 ^Int N)) + requires N >=Int 0 + andBool POW >Int 0 + andBool POW modInt (2 ^Int N) ==Int 0 + [simplification] + + rule (X < (X modInt (POW /Int (2 ^Int N))) <=Int 0 + andBool POW >Int 0 + andBool POW modInt (2 ^Int N) ==Int 0 + [simplification] +``` + +Proof sketch: Taking modulo `2^n * k` can only affect the the `n`-th and higher bits. +A shift right by `n` bits can only erase bits up to the `n-1`-th bit. +Therefore, we may as well shift first and then take the modulus, only we need to make sure the modulus acts on the shifted bits, by taking modulo `k` instead. +The argument for the left shift is similar. + +```k + rule (X +Int (_Y < X modInt POW + requires N >=Int 0 + andBool POW >Int 0 + andBool (2 ^Int N) modInt POW ==Int 0 + [simplification] + + rule ((_Y < X modInt POW + requires N >=Int 0 + andBool POW >Int 0 + andBool (2 ^Int N) modInt POW ==Int 0 + [simplification] +``` + +Proof: These follow from the fact that shifting left by `n` bits is simply multiplying by `2^n`, and from previously proven rules of modular arithmetic. + +### Basic Operations + +When reasoning about `#chop`, it's often the case that the precondition to the proof contains the information needed to indicate no overflow. +In this case, it's simpler (and safe) to simply discard the `#chop`, instead of evaluating it. + +```k + rule X modInt #pow(ITYPE) => #unsigned(ITYPE, X) + requires #inSignedRange (ITYPE, X) + [simplification] + + syntax Bool ::= #inUnsignedRange (IValType, Int) [function] + syntax Bool ::= #inSignedRange (IValType, Int) [function] + // ----------------------------------------------------------- + rule #inUnsignedRange (ITYPE, I) => 0 <=Int I andBool I #minSigned(ITYPE) <=Int I andBool I 0 -Int #pow1(ITYPE) +``` + +Lookups +------- + +```k + rule (_MAP:Map [KEY <- VAL])[KEY] => VAL [simplification] +``` + +Memory +------ + +Bounds on `#getRange`: + +```k + rule 0 <=Int #getRange(_, _, _) => true [simplification] + rule 0 <=Int VAL < true requires 0 <=Int VAL andBool 0 <=Int SHIFT [simplification] + rule 0 <=Int VAL1 +Int VAL2 => true requires 0 <=Int VAL1 andBool 0 <=Int VAL2 [simplification] + + rule #getRange(_, _, WIDTH) true requires 2 ^Int (8 *Int WIDTH) <=Int MAX [simplification] + rule #getRange(_, _, WIDTH) < true requires 2 ^Int ((8 *Int WIDTH) +Int SHIFT) <=Int MAX [simplification] + rule VAL1 +Int VAL2 true requires VAL1 true requires WIDTH *Int 8 <=Int SHIFT + [simplification] +``` + +`#wrap` over `#getRange`: + +```k + rule #wrap(MAX_WIDTH, #getRange(BM, ADDR, WIDTH)) => #getRange(BM, ADDR, minInt(MAX_WIDTH, WIDTH)) + [simplification] +``` + +Arithmetic over `#getRange`: + +```k + rule #getRange(BM, ADDR, WIDTH) >>Int SHIFT => #getRange(BM, ADDR +Int 1, WIDTH -Int 1) >>Int (SHIFT -Int 8) + requires 0 <=Int ADDR + andBool 0 #getRange(BM, ADDR, WIDTH -Int 1) modInt MAX + requires 0 #getRange(BM, ADDR', WIDTH') requires ADDR' +Int WIDTH' <=Int ADDR [simplification] + rule #getRange(#setRange(BM, ADDR, _VAL, WIDTH), ADDR', WIDTH') => #getRange(BM, ADDR', WIDTH') requires ADDR +Int WIDTH <=Int ADDR' [simplification] + rule #getRange(#setRange(_BM, ADDR, VAL, WIDTH), ADDR', WIDTH') => VAL + requires 0 <=Int ADDR + andBool 0 BM [simplification] + rule #setRange(BM, ADDR, (#getRange(_, _, WIDTH1) #as VAL1) +Int (VAL2 < #setRange(#setRange(BM, ADDR, VAL1, minInt(WIDTH1, WIDTH)), ADDR +Int WIDTH1, VAL2, WIDTH -Int WIDTH1) + requires 0 <=Int ADDR + andBool 0 Exp "+" Exp // looser binding + + syntax Stmt ::= Id ":=" Exp + | Stmt ";" Stmt + | "return" Exp +``` + +. . . + +This would allow correctly parsing programs like: + +```imp + a := 3 * 2; + b := 2 * a + 5; + return b +``` + +\K{} Specifications: Configuration +---------------------------------- + +Tell \K{} about the structure of your execution state. +For example, a simple imperative language might have: + +```k + configuration $PGM:Program + .Map + .Map +``` + +. . . + +> - `` will contain the initial parsed program +> - `` contains bindings of variable names to store locations +> - `` conaints bindings of store locations to integers + +\K{} Specifications: Transition Rules +------------------------------------- + +Using the above grammar and configuration: + +. . . + +### Variable lookup + +```k + rule X:Id => V ... + ... X |-> SX ... + ... SX |-> V ... +``` + +. . . + +### Variable assignment + +```k + rule X := I:Int => . ... + ... X |-> SX ... + ... SX |-> (V => I) ... +``` + +Example Execution +----------------- + +### Program + +```imp + a := 3 * 2; + b := 2 * a + 5; + return b +``` + +### Initial Configuration + +```k + a := 3 * 2 ; b := 2 * a + 5 ; return b + a |-> 0 b |-> 1 + 0 |-> 0 1 |-> 0 +``` + +Example Execution (cont.) +------------------------- + +### Variable assignment + +```k + rule X := I:Int => . ... + ... X |-> SX ... + ... SX |-> (V => I) ... +``` + +### Next Configuration + +```k + a := 6 ~> b := 2 * a + 5 ; return b + a |-> 0 b |-> 1 + 0 |-> 0 1 |-> 0 +``` + +Example Execution (cont.) +------------------------- + +### Variable assignment + +```k + rule X := I:Int => . ... + ... X |-> SX ... + ... SX |-> (V => I) ... +``` + +### Next Configuration + +```k + b := 2 * a + 5 ; return b + a |-> 0 b |-> 1 + 0 |-> 6 1 |-> 0 +``` + +Example Execution (cont.) +------------------------- + +### Variable lookup + +```k + rule X:Id => V ... + ... X |-> SX ... + ... SX |-> V ... +``` + +### Next Configuration + +```k + a ~> b := 2 * [] + 5 ; return b + a |-> 0 b |-> 1 + 0 |-> 6 1 |-> 0 +``` + +Example Execution (cont.) +------------------------- + +### Variable lookup + +```k + rule X:Id => V ... + ... X |-> SX ... + ... SX |-> V ... +``` + +### Next Configuration + +```k + 6 ~> b := 2 * [] + 5 ; return b + a |-> 0 b |-> 1 + 0 |-> 6 1 |-> 0 +``` + +Example Execution (cont.) +------------------------- + +### Variable lookup + +```k + rule X:Id => V ... + ... X |-> SX ... + ... SX |-> V ... +``` + +### Next Configuration + +```k + b := 2 * 6 + 5 ; return b + a |-> 0 b |-> 1 + 0 |-> 6 1 |-> 0 +``` + +Example Execution (cont.) +------------------------- + +### Variable assignment + +```k + rule X := I:Int => . ... + ... X |-> SX ... + ... SX |-> (V => I) ... +``` + +### Next Configuration + +```k + b := 17 ~> return b + a |-> 0 b |-> 1 + 0 |-> 6 1 |-> 0 +``` + +Example Execution (cont.) +------------------------- + +### Variable assignment + +```k + rule X := I:Int => . ... + ... X |-> SX ... + ... SX |-> (V => I) ... +``` + +### Next Configuration + +```k + return b + a |-> 0 b |-> 1 + 0 |-> 6 1 |-> 17 +``` + +KWASM Design +============ + +WASM Specification +------------------ + +Available at . + +- Fairly unambiguous[^betterThanEVM]. +- Well written with procedural description of execution accompanied by small-step semantic rules. + +. . . + +Example rule: + +1. Let $L$ be the label whose arity is 0 and whose continuation is the start of the loop. +2. `Enter` the block $\instr^\ast$ with label $L$. + +. . . + +\vspace{-2em} +$$ + \LOOP~[t^?]~\instr^\ast~\END + \quad \stepto \quad + \LABEL_0\{\LOOP~[t^?]~\instr^\ast~\END\}~\instr^\ast~\END +$$ + +[^betterThanEVM]: At least, better than the [YellowPaper](https://github.com/ethereum/yellowpaper). + +Translation to \K{} +------------------- + +### WASM Spec + +\vspace{-1em} +$$ + \LOOP~[t^?]~\instr^\ast~\END + \quad \stepto \quad + \LABEL_0\{\LOOP~[t^?]~\instr^\ast~\END\}~\instr^\ast~\END +$$ + +. . . + +### In \K{} + +```k + syntax Instr ::= "loop" Type Instrs "end" + // ----------------------------------------- + rule loop TYPE IS end + => IS + ~> label [ .ValTypes ] { + loop TYPE IS end + } STACK + ... + + STACK +``` + +Design Difference: 1 or 2 Stacks? +--------------------------------- + +. . . + +### WASM Specification + +One stack mixing values and instructions. + +- Confusing control-flow semantics (with `label`s). +- Use meta-level context operator to describe semantics of `br`. +- Correct label is buried in the value stack (which we must also access the top of). +- Section 4.4.5 of the WASM spec. + +. . . + +### KWASM + +Uses two stacks, values in `` cell and instructions in `` cell. + +- Can access both cells simultaneously, without backtracking/remembering one stack. +- Cleaner semantics, no meta-level context operator needed. + +Design Choice: Incremental Semantics +------------------------------------ + +- KWASM semantics are given incrementally. +- Makes it possible to execute program fragments. +- Allows users to quickly experiment with WASM using KWASM. + +. . . + +For example, KWASM will happily execute the following fragment (without an enclosing `module`): + +```wast + (i32.const 4) + (i32.const 5) + (i32.add) +``` + +Early Stage Benefits of KWASM +----------------------------- + +> - Discovered bugs in spec (one filed on Github, some to come). +> - Suggests clearer presentation of control-flow semantics. +> - Interpreter allowing direct experimentation with program fragments. + +Using KWASM (Psuedo-Demo) +========================= + +Getting/Building +---------------- + +Clone the repository: + +```sh +git clone 'https://github.com/kframework/wasm-semantics' +cd wasm-semantics +``` + +Build the dependencies, then the KWASM semantics: + +```sh +make deps +make build +``` + +`kwasm` Script +-------------- + +The file `./kwasm` is the main runner for KWASM. + +### Running `./kwasm help` + +```sh +usage: ./kwasm * + + # Running + # ------- + ./kwasm run Run a single WASM program + ./kwasm debug Run a single WASM program in the debugger + ... +``` + +Running a Program +----------------- + +### WASM Program `pgm1.wast` + +```wasm +(i32.const 4) +(i32.const 5) +(i32.add) +``` + +### Result of `./kwasm run pgm1.wast` + +```k + + + . + + + < i32 > 9 : .Stack + + +``` + +Debugging a Program +------------------- + +### Run `./kwasm debug pgm1.wast` + +```k +== debugging: pgm1.wast +KDebug> s +1 Step(s) Taken. +KDebug> p + + + i32 . const 4 ~> i32 . const 5 i32 . add .Instrs + + + .Stack + + +``` + +Debugging a Program (cont.) +--------------------------- + +### Take a `s`tep then `p`eek at state + +```k +KDebug> s +1 Step(s) Taken. +KDebug> p + + + i32 . const 5 i32 . add .Instrs + + + < i32 > 4 : .Stack + + +``` + +Debugging a Program (cont.) +--------------------------- + +### Take a `s`tep then `p`eek at state + +```k +KDebug> s +1 Step(s) Taken. +KDebug> p + + + i32 . const 5 ~> i32 . add .Instrs + + + < i32 > 4 : .Stack + + +``` + +Debugging a Program (cont.) +--------------------------- + +### Take 10 `s`teps then `p`eek at state + +```k +KDebug> s 10 +Attempted 10 step(s). Took 4 steps(s). +Final State Reached +KDebug> p + + + . + + + < i32 > 9 : .Stack + + +``` + +Future Directions +================= + +Finish KWASM +------------ + +The semantics are fairly early-stage. + +### In progress (1 week out) + +- Frame/locals semantics, `call*` and `return` opcodes. + +### To be done (1-2 months out) + +- Some bitwise operators. +- Everything floating point. +- Tables. +- Memories. +- Modules. + +Fork KeWASM +----------- + +- eWASM adds the gas metering contract to WASM, but otherwise largely leaves the semantics alone. +- Could we give a direct semantics to gas metering? +- Possibly, perhaps then we could verify that the gas metering contract and the direct gas metering agree. + +Verify eWASM Programs +--------------------- + +- KEVM currently has many verified smart contracts at . +- We similarly would like to build a repository of verified code using KeWASM. + +Conclusion +========== + +Benefits of \K{} Approach +------------------------- + +- Many tools that a PL designer/user would want already exist. +- Powerful (and generic) verification engine "for free". +- Executable specification clears up issues in the existing WASM specification[^similarKEVM]. + +[^similarKEVM]: Similarly for KEVM, we found several issues/ambiguities in the [YellowPaper](https://github.com/ethereum/yellowpaper). + +Questions? +---------- + +Thanks for listening! + +References +---------- + +\tiny + diff --git a/docs/runtimeverification-wasm-semantics/media/201903-presentation-chalmers.md b/docs/runtimeverification-wasm-semantics/media/201903-presentation-chalmers.md new file mode 100644 index 000000000..54255bcf7 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/media/201903-presentation-chalmers.md @@ -0,0 +1,773 @@ +--- +title: 'Semantics of WebAssembly in the K framework' +subtitle: 'What is KWasm?' +author: +- Rikard Hjort \tiny supervised by Magnus Myreen \normalsize +- Everett Hildenbrandt +abstract: + WebAssembly is a low-ish-level language designed to run efficiently on all modern platforms. The Ethereum blockchain currently runs on its own virtual machine (the EVM) but is expected to move to use WebAssembly in the future. The K framework definition of the EVM has become the de facto standard for verifying properties of smart contracts, most notably the Dai coin contracts, currently handling over 100 million USD of value. Since we want to verify Ethereum contracts compiled to WebAssembly -- as well as other WebAssembly programs -- we need to have a K formalization of WebAssembly. That is what I've been working on. + + I will be giving a hands-on talk on WebAssembly, how to define languages in K, and how we have been translating the official WebAssembly specification into K." +date: March 25, 2019 +institute: +- Chalmers University of Technology +- Runtime Verification, Inc. +theme: metropolis +fontsize: 8pt +header-includes: +- \newcommand{\instr}{instr} +- \newcommand{\STORE}{\textit{S}} +- \newcommand{\FRAME}{\textit{F}} +- \newcommand{\CONST}{\texttt{const~}} +- \newcommand{\DATA}{\texttt{data}} +- \newcommand{\FUNCS}{\texttt{funcs}} +- \newcommand{\GLOBALS}{\texttt{globals}} +- \newcommand{\GROW}{\texttt{grow}} +- \newcommand{\ITHREETWO}{\texttt{i32}} +- \newcommand{\LOCALS}{\texttt{locals}} +- \newcommand{\MEMADDRS}{\texttt{memaddrs}} +- \newcommand{\MEMORY}{\texttt{memory}} +- \newcommand{\MEMS}{\texttt{mems}} +- \newcommand{\MODULE}{\texttt{module}} +- \newcommand{\SIZE}{\texttt{size}} +- \newcommand{\TABLES}{\texttt{tables}} +- \newcommand{\with}{\textit{~with~}} +- \newcommand{\stepto}{~\hookrightarrow~} +- \newcommand{\wif}[1]{\text{if}~#1} +- \newcommand{\diminish}[1]{\begin{footnotesize}#1\end{footnotesize}} +--- + +Video +----- + +This talk was recoreded, and is available in full at + +Overview +-------- + +1. Background +2. Introduction to K +3. Introduction to WebAssembly (Wasm) +4. Demo: implement a Wasm subset +5. (Proving things) + +Background +========== + +Smart contracts and formal methods +------------ + +![](media/img/ethereum.png){ width=65%} + +- Blockchain technology, **smart contracts** in particular, caught my interest. +- Public, immutable code handling lots of money? Great area of application for formal methods! + +Existing projects +------- + +![](media/img/maker.png){ width=20% align=center style="margin-bottom:40px"} + +- Contacted friends at MakerDAO. +- They have verified the core contracts of their "stablecoin", Dai. + +. . . + +![](media/img/dapphub.png){ width=40% align=center style="margin-bottom:40px"} + +- The verification was largely done by a related organization, DappHub ... + +. . . + +![](media/img/k.png){ height=15% hspace=30px }     +![](media/img/RV-logo-blue.eps){ height=15% hspace=30px }     + + +- ... using the K framework. + + + +The KEVM +-------- + +![](media/img/kevm-paper.png) + +- Verification made possible by KEVM [@hildenbrandt-saxena-zhu-rosu-k-evm], modelling the EVM. +- The EVM is a stack machine with $\approx$ 120 opcodes. +- Everett had begun work on a K specification of another low-level languge: Wasm. + +ewasm +---- + +**Rationale** + +How would ewasm be better than EVM? + +. . . + +- Speed +- Size +- Security +- Write contracts in C/C++, go, or rust +- Static analysis +- Optional metering +- Portability: ewasm contracts will be compatibile with any standard Wasm environment, including IoT and mobile devices + + +(Brief) Introduction to K +========================== + +The Vision: Language Independence +--------------------------------- + +![K Tooling Overview](media/img/k-overview.png) + + +K Tooling/Languages +------------------- + +### Tools + +- Parser +- Interpreter +- Debugger +- Reachability Logic Prover [@stefanescu-park-yuwen-li-rosu-reachability-prover] + +. . . + +### Languages + +- Java 1.4 - 2015 [@bogdanas-rosu-k-java] +- C11 - 2015 [@hathhorn-ellison-rosu-k-c] +- KJS - 2015 [@park-stefanescu-rosu-k-js] +- KEVM - 2018 [@hildenbrandt-saxena-zhu-rosu-k-evm] +- P4K - 2018 [@kheradmand-rosu-k-p4] +- KIELE - 2018 [@kasampalis-guth-moore-rosu-johnson-k-iele] +- KLLVM +- KX86-64 + +Parts of a K specification +-------------------------- + +A language spec in K consists of 3 things + +* Syntax +* Configuration ("state") +* Operational semantics as **rewrite rules** + +K Specifications: Syntax +------------------------ + +Concrete syntax built using EBNF style: + +```k + syntax Exp ::= Int | Id | "(" Exp ")" [bracket] + | Exp "*" Exp + > Exp "+" Exp // looser binding + + syntax Stmt ::= Id ":=" Exp + | Stmt ";" Stmt + | "return" Exp +``` + +. . . + +This would allow correctly parsing programs like: + +```imp + a := 3 * 2; + b := 2 * a + 5; + return b +``` + +K Specifications: Configuration +------------------------------- + +Tell K about the structure of your execution state. +For example, a simple imperative language might have: + +```k + configuration $PGM:Program + .Map + .Map +``` + +. . . + +> - `` will contain the initial parsed program +> - `` contains bindings of variable names to store locations +> - `` contains bindings of store locations to integers + +K Specifications: Transition Rules +---------------------------------- + +Using the above grammar and configuration: + +. . . + +### Variable lookup + +```k + rule X:Id => V ... + ... X |-> SX ... + ... SX |-> V ... +``` + +. . . + +### Variable assignment + +```k + rule X := I:Int => . ... + ... X |-> SX ... + ... SX |-> (V => I) ... +``` + +Example Execution +----------------- + +### Program + +```imp + a := 3 * 2; + b := 2 * a + 5; + return b +``` + +### Initial Configuration + +```k + a := 3 * 2 ; b := 2 * a + 5 ; return b + a |-> 0 b |-> 1 + 0 |-> 0 1 |-> 0 +``` + +Example Execution (cont.) +------------------------- + +### Variable assignment + +```k + rule X := I:Int => . ... + ... X |-> SX ... + ... SX |-> (V => I) ... +``` + +### Next Configuration + +```k + a := 6 ~> b := 2 * a + 5 ; return b + a |-> 0 b |-> 1 + 0 |-> 0 1 |-> 0 +``` + +Example Execution (cont.) +------------------------- + +### Variable assignment + +```k + rule X := I:Int => . ... + ... X |-> SX ... + ... SX |-> (V => I) ... +``` + +### Next Configuration + +```k + b := 2 * a + 5 ; return b + a |-> 0 b |-> 1 + 0 |-> 6 1 |-> 0 +``` + +Example Execution (cont.) +------------------------- + +### Variable lookup + +```k + rule X:Id => V ... + ... X |-> SX ... + ... SX |-> V ... +``` + +### Next Configuration + +```k + a ~> b := 2 * [] + 5 ; return b + a |-> 0 b |-> 1 + 0 |-> 6 1 |-> 0 +``` + +Example Execution (cont.) +------------------------- + +### Variable lookup + +```k + rule X:Id => V ... + ... X |-> SX ... + ... SX |-> V ... +``` + +### Next Configuration + +```k + 6 ~> b := 2 * [] + 5 ; return b + a |-> 0 b |-> 1 + 0 |-> 6 1 |-> 0 +``` + +Example Execution (cont.) +------------------------- + +### Variable lookup + +```k + rule X:Id => V ... + ... X |-> SX ... + ... SX |-> V ... +``` + +### Next Configuration + +```k + b := 2 * 6 + 5 ; return b + a |-> 0 b |-> 1 + 0 |-> 6 1 |-> 0 +``` + +Example Execution (cont.) +------------------------- + +### Variable assignment + +```k + rule X := I:Int => . ... + ... X |-> SX ... + ... SX |-> (V => I) ... +``` + +### Next Configuration + +```k + b := 17 ~> return b + a |-> 0 b |-> 1 + 0 |-> 6 1 |-> 0 +``` + +Example Execution (cont.) +------------------------- + +### Variable assignment + +```k + rule X := I:Int => . ... + ... X |-> SX ... + ... SX |-> (V => I) ... +``` + +### Next Configuration + +```k + return b + a |-> 0 b |-> 1 + 0 |-> 6 1 |-> 17 +``` + +Example Execution (cont.) +------------------------- + +### Final configuration + +```k + return 17 + a |-> 0 b |-> 1 + 0 |-> 6 1 |-> 17 +``` + +(Brief) Introduction to WebAssembly +======== + +Wasm: your new favorite compile target +---- + +- It's not web, and it's not assembly, just a great low-ish level language. + - Fast on hardware, but platform agnostic. + - Allows stream compiling. + - Efficient byte format, readable text format. + +. . . + +- Safety features + - Blocks, loops and breaks, but not arbitrary jumps. + - Allows static validation. + - No implicit casts. + +. . . + +- Organized in modules *(example coming up)*. + - Can declare functions, allocate and modify their own linear memory, global variables etc. + - Cay export some of its contents. + - Can have `start` functions, which are run when the module is loaded. + +. . . + +- Wasm is stack-based, but the syntax allows S-expression "folding" *(example coming up)*. + + +Code fold/unfold +---------------- + +```scheme +(memory.size) ;; Nullary -- push memory size (i32). +(i64.extend_i32_u) ;; Unary -- i32 ==> i64. +``` + +. . . + +```scheme +(local.get $tmp) ;; Nullary -- push local variable $tmp (i32). +(i64.load8_u) ;; Unary -- load 1 byte from argument address, push. +``` + +. . . + +```scheme +(i64.add) ;; Binary +``` + +. . . + +\vfill{} + +becomes + +. . . + +\vfill{} + +```scheme +(i64.add + (i64.extend_i32_u (memory.size)) + (i64.load8_u (local.get $tmp))) +``` + +\vfill{} + +. . . + +Mix freely! Also OK: + +```scheme +(i64.extend_i32_u (memory.size)) +(i64.load8_u (local.get $tmp))) +(i64.add) +``` + + + +Code example (folded) +--------------------- + +```scheme +(module + (memory 1) + (func ;; Function descriptors. + (export "myGrowAndStoreFunction") + (param $by i32) (param $put i64) ;; Identifiers: $by and $put. + (result i64) + (local $tmp i32) + + ;; Body of the function. + (local.set $tmp + (i32.mul (memory.grow (local.get $by)) (i32.const 65536))) + (i64.store (local.get $tmp) (local.get $put)) + (i64.add + (i64.extend_i32_u (memory.size)) + (i64.load8_u (local.get $tmp))) + ) ;; End func. +) ;; End module. +``` + +Running the example +------------------- +``` +$ ./wasm -i myProgram.wast - +wasm 1.0 reference interpreter +> (invoke "myGrowAndStoreFunction" (i32.const 2) (i64.const 0)) +3 : i64 +``` + +. . . + +``` +> (invoke "myGrowAndStoreFunction" (i32.const 2) (i64.const 0)) +5 : i64 +``` + +. . . + +``` +> (invoke "myGrowAndStoreFunction" (i32.const 1) (i64.const -1)) +261 : i64 +``` + +Wasm Specification +------------------ + +Available at . + +- Fairly unambiguous[^betterThanEVM]. +- Well written with procedural description of execution accompanied by small-step semantic rules. + +[^betterThanEVM]: Better than the [YellowPaper](https://github.com/ethereum/yellowpaper). + +Wasm Specification +------------------ + +Semantic rule: + +$$ +\STORE; \FRAME; \MEMORY.\SIZE \stepto \STORE; \FRAME; (\ITHREETWO.\CONST sz) +$$ +$$ +(\wif{|\STORE.\MEMS[\FRAME.\MODULE.\MEMADDRS[0]].\DATA| = sz * 64 Ki)} +$$ + +. . . + +*store* ($\STORE$) and a *current frame* ($\FRAME$): + +\begin{alignat*}{5} +%&store &::=~&\{ & \quad &\FUNCS ~&\quad &funcinst^* & \\ +&store &::=~&\{ & \quad &\dots ~&\quad & & \\ +%& & & & \quad &\TABLES ~&\quad &tableinst^* & \\ +& & & & \quad &\MEMS ~&\quad &meminst^* & \\ +& & & & \quad &\dots ~&\quad & &\} \\ +%& & & & \quad &\GLOBALS ~&\quad &globalinst^* \quad &\} \\ +&frame &::=~&\{ & \quad &\LOCALS ~&\quad &val^* & \\ +& & & & \quad &\MODULE ~&\quad &moduleinst \quad &\} \\ +&moduleinst~&::=~&\{~& \quad &\dots ~&\quad & & \\ +& & & & \quad &\MEMADDRS ~&\quad &memaddr^* & \\ +& & & & \quad &\dots ~&\quad & &\} +\end{alignat*} + +Wasm Specification (cont.) +-------------------------- + +**Example rule:** `(memory.size)` + +. . . + +Execution description: + +1. Let $\FRAME$ be the current frame. +2. \diminish{Assert: due to validation, $\FRAME.\MODULE.\MEMADDRS[0]$ exists.} +3. Let $a$ be the memory address $\FRAME.\MODULE.\MEMADDRS[0]$.[^memIdxZero] +4. \diminish{Assert: due to validation, $\STORE.\MEMS[a]$ exists.} +5. Let $mem$ be the memory instance $\STORE.\MEMS[a]$. +6. Let $sz$ be the length of $mem.\DATA$ divided by the page size. +7. Push the value $\ITHREETWO.\CONST sz$ to the stack. + +[^memIdxZero]: Every module in Wasm has a single memory for now, so we always implicitly work on `memaddrs[0]`. + + +Demo: implement a Wasm subset +============================= + +`(memory.size)` +--------------- + +$$ +\STORE ; \FRAME ; \MEMORY.\SIZE \stepto \STORE ; \FRAME ; (\ITHREETWO.\CONST sz) +$$ +$$ +\wif { +|\STORE.\MEMS[\FRAME.\MODULE.\MEMADDRS[0]].\DATA| = sz \cdot 64 Ki +} +$$ + +\vfill{} + +\begin{alignat*}{5} +%&store &::=~&\{ & \quad &\FUNCS ~&\quad &funcinst^* & \\ +&store &::=~&\{ & \quad &\dots ~&\quad & & \\ +%& & & & \quad &\TABLES ~&\quad &tableinst^* & \\ +& & & & \quad &\MEMS ~&\quad &meminst^* & \\ +& & & & \quad &\dots ~&\quad & &\} \\ +%& & & & \quad &\GLOBALS ~&\quad &globalinst^* \quad &\} \\ +&frame &::=~&\{ & \quad &\LOCALS ~&\quad &val^* & \\ +& & & & \quad &\MODULE ~&\quad &moduleinst \quad &\} \\ +&moduleinst~&::=~&\{~& \quad &\dots ~&\quad & & \\ +& & & & \quad &\MEMADDRS ~&\quad &memaddr^* & \\ +& & & & \quad &\dots ~&\quad & &\} +\end{alignat*} + +`(memory.grow)` +--------------- + +\begin{align*} +\STORE; \FRAME ; &(\ITHREETWO.\CONST n)~(\MEMORY.\GROW) \stepto \STORE ' ; \FRAME ; (\ITHREETWO.\CONST sz ) \\ +(&\wif{\FRAME.\MODULE.\MEMADDRS[0] = a \\ +&\land sz = |\STORE.\MEMS[a].\DATA|/64 Ki \\ +&\land \STORE ' = \STORE \with \MEMS[a] = growmem(\STORE.\MEMS[a], n))} \\ +\\ +\STORE; \FRAME ; &(\ITHREETWO.\CONST n)~(\MEMORY.\GROW) \stepto \STORE ; \FRAME ; (\ITHREETWO.\CONST {-1} ) \\ +\end{align*} + + +Future Directions +================= + +Finish KWasm +------------ + +The semantics are fairly early-stage. + +### In progress + +- Memories. + +### To be done + +- Everything floating point. +- Tables. +- Modules. + +KeWasm +------ + +- eWasm adds gas metering to Wasm, but otherwise leaves the semantics alone. + +\vfill{} + +. . . + +- KEVM currently has many verified smart contracts at . +- We similarly would like to build a repository of verified code using KeWasm. + +Conclusion/Questions? +===================== + +References +---------- + +- Thanks for listening! + +\tiny + + + + diff --git a/docs/runtimeverification-wasm-semantics/media/201903-presentation-edcon.md b/docs/runtimeverification-wasm-semantics/media/201903-presentation-edcon.md new file mode 100644 index 000000000..896c2f2f8 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/media/201903-presentation-edcon.md @@ -0,0 +1,375 @@ +--- +title: 'Intro to K, KEVM, and KWasm' +author: +- Everett Hildenbrandt +- Rikard Hjort +date: '\today' +institute: +- Runtime Verification, Inc. +- Chalmers University of Technology +theme: metropolis +fontsize: 8pt +header-includes: +- \newcommand{\instr}{instr} +- \newcommand{\LOOP}{\texttt{loop}} +- \newcommand{\LABEL}{\texttt{label}} +- \newcommand{\END}{\texttt{end}} +- \newcommand{\stepto}{\hookrightarrow} +--- + +Overview +-------- + +1. Introduction to K +2. KEVM +2. KWasm +2. Reachability Logic Prover +4. Future Directions + +(Brief) Introduction to K +========================= + +K Vision +-------- + +![K Overview](media/img/k-overview.png) + +K Tooling/Languages +------------------- + +### Tools + +- Parser +- Interpreter +- Debugger +- Reachability Logic Prover [@stefanescu-park-yuwen-li-rosu-reachability-prover] + +. . . + +### Languages + +- Java 1.4 - 2015 [@bogdanas-rosu-k-java] +- C11 - 2015 [@hathhorn-ellison-rosu-k-c] +- KJS - 2015 [@park-stefanescu-rosu-k-js] +- KEVM - 2018 [@hildenbrandt-saxena-zhu-rosu-k-evm] +- P4K - 2018 [@kheradmand-rosu-k-p4] +- KIELE - 2018 [@kasampalis-guth-moore-rosu-johnson-k-iele] +- KLLVM +- KX86-64 + +K Specification: The Components +------------------------------- + +- **Syntax** of your language (term algebra of programs). +- **Configuration** of your language (term algebra of program states). +- **Rules** describing small-step operational semantics of your language. + +K Specifications: Syntax +------------------------ + +Concrete syntax built using EBNF style: + +```k + syntax Exp ::= Int | Id | "(" Exp ")" [bracket] + | Exp "*" Exp + > Exp "+" Exp // looser binding + + syntax Stmt ::= Id ":=" Exp + | Stmt ";" Stmt + | "return" Exp +``` + +. . . + +This would allow correctly parsing programs like: + +```imp + a := 3 * 2; + b := 2 * a + 5; + return b +``` + +K Specifications: Configuration +------------------------------- + +Tell K about the structure of your execution state. +For example, a simple imperative language might have: + +```k + configuration $PGM:Program + .Map + .Map +``` + +. . . + +> - `` will contain the initial parsed program +> - `` contains bindings of variable names to store locations +> - `` conaints bindings of store locations to integers + +K Specifications: Transition Rules +---------------------------------- + +Using the above grammar and configuration: + +. . . + +### Variable lookup + +```k + rule X:Id => V ... + ... X |-> SX ... + ... SX |-> V ... +``` + +. . . + +### Variable assignment + +```k + rule X := I:Int => . ... + ... X |-> SX ... + ... SX |-> (V => I) ... +``` + +Example Execution +----------------- + +### Program + +```imp + a := 3 * 2; + b := 2 * a + 5; + return b +``` + +### Initial Configuration + +```k + a := 3 * 2 ; b := 2 * a + 5 ; return b + a |-> 0 b |-> 1 + 0 |-> 0 1 |-> 0 +``` + +Example Execution (cont.) +------------------------- + +### Variable assignment + +```k + rule X := I:Int => . ... + ... X |-> SX ... + ... SX |-> (V => I) ... +``` + +### Next Configuration + +```k + a := 6 ~> b := 2 * a + 5 ; return b + a |-> 0 b |-> 1 + 0 |-> 0 1 |-> 0 +``` + +Example Execution (cont.) +------------------------- + +### Variable assignment + +```k + rule X := I:Int => . ... + ... X |-> SX ... + ... SX |-> (V => I) ... +``` + +### Next Configuration + +```k + b := 2 * a + 5 ; return b + a |-> 0 b |-> 1 + 0 |-> 6 1 |-> 0 +``` + +KEVM +==== + +What is KEVM? +------------- + +- Implementation of EVM in K. +- Passes all of the VMTests and the BlockchainTests/GeneralStateTests. +- Derived interpreter nearly as performant as cpp-ethereum. +- The Jello Paper is derived from KEVM . + +. . . + +- Used for commercial verification by Runtime Verification, Inc. +- Used by DappHub to verify the MKR SCD and MCD core contracts. +- Check out repo of verified smart contracts at . + +KWasm Design +============ + +Wasm Specification +------------------ + +Available at . + +- Fairly unambiguous[^betterThanEVM]. +- Well written with procedural description of execution accompanied by small-step semantic rules. + +\vfill{} + +. . . + +Example rule: + +1. Let $L$ be the label whose arity is 0 and whose continuation is the start of the loop. +2. `Enter` the block $\instr^\ast$ with label $L$. + +\vfill{} + +. . . + +$$ + \LOOP~[t^?]~\instr^\ast~\END + \quad \stepto \quad + \LABEL_0\{\LOOP~[t^?]~\instr^\ast~\END\}~\instr^\ast~\END +$$ + +[^betterThanEVM]: Better than the [YellowPaper](https://github.com/ethereum/yellowpaper). + +Translation to K +---------------- + +### Wasm Spec + +\vspace{-1em} +$$ + \LOOP~[t^?]~\instr^\ast~\END + \quad \stepto \quad + \LABEL_0\{\LOOP~[t^?]~\instr^\ast~\END\}~\instr^\ast~\END +$$ + +. . . + +### In K + +```k + syntax Instr ::= "loop" Type Instrs "end" + // ----------------------------------------- + rule loop TYPE IS end + => IS + ~> label [ .ValTypes ] { + loop TYPE IS end + } STACK + ... + + STACK +``` + +Design Difference: 1 or 2 Stacks? +--------------------------------- + +. . . + +### Wasm Specification + +One stack mixing values and instructions. + +- Confusing control-flow semantics (with `label`s). +- Use meta-level context operator to describe semantics of `br`. +- Section 4.4.5 of the Wasm spec. + +\vfill{} + +. . . + +### KWasm + +Uses two stacks, values in `` cell and instructions in `` cell. + +- Can access both cells simultaneously, without backtracking/remembering one stack. +- Cleaner semantics, no meta-level context operator needed. + +Design Choice: Incremental Semantics +------------------------------------ + +- KWasm semantics are given incrementally. +- Makes it possible to execute program fragments. +- Allows users to quickly experiment with Wasm using KWasm. + +\vfill{} + +. . . + +For example, KWasm will happily execute the following fragment (without an enclosing `module`): + +```wast + (i32.const 4) + (i32.const 5) + (i32.add) +``` + +Future Steps for KWasm +---------------------- + +### To be done + +- Everything floating point. +- Tables. +- Modules. + +### KeWasm + +- eWasm adds gas metering to Wasm, but otherwise leaves the semantics alone. + +Reachability Logic Prover +========================= + +Inference System +---------------- + +![Reachability Logic Inference System](media/img/reachability-logic-inference-system.png) + +- Sound and relatively complete. +- Interesting rules are Circularity/Transitivity, allows coinductive reasoning. + +Tool +---- + +- K Reachability Logic prover accepts proof claims in same format as operational semantics axioms[@stefanescu-park-yuwen-li-rosu-reachability-prover]. +- On success will print `#True`, on failure will print symbolic counterexample end-states. +- Added instrumentation allows KLab to provide more useful interface to K Prover . +- Proof search is fully automated, only write the theorem (specification), no manual control over how to discharge it. + +Example KWasm Proof +------------------- + +Non-overflowing addition operation: + +```k + rule ( ITYPE:IValType . const X:Int ) + ( ITYPE . const Y:Int ) + ( ITYPE . add ) + => . + ... + + S:Stack => < ITYPE > (X +Int Y) : S + requires 0 <=Int X andBool 0 <=Int Y + andBool (X +Int Y) \newline +date: \today +institute: +- Chalmers University of Technology +- Runtime Verification, Inc. +theme: metropolis +fontsize: 8pt +header-includes: +- \usepackage{amssymb} +- \newcommand{\K}{$\mathbb{K}$~} +- \newcommand{\instr}{instr} +- \newcommand{\STORE}{\textit{S}} +- \newcommand{\FRAME}{\textit{F}} +- \newcommand{\CONST}{\texttt{const~}} +- \newcommand{\DATA}{\texttt{data}} +- \newcommand{\FUNCS}{\texttt{funcs}} +- \newcommand{\GLOBALS}{\texttt{globals}} +- \newcommand{\GROW}{\texttt{grow}} +- \newcommand{\ITHREETWO}{\texttt{i32}} +- \newcommand{\LOCALS}{\texttt{locals}} +- \newcommand{\MAX}{\texttt{max}} +- \newcommand{\MEMADDRS}{\texttt{memaddrs}} +- \newcommand{\MEMORY}{\texttt{memory}} +- \newcommand{\MEMS}{\texttt{mems}} +- \newcommand{\MEMINST}{\textit{meminst}} +- \newcommand{\MODULE}{\texttt{module}} +- \newcommand{\SIZE}{\texttt{size}} +- \newcommand{\TABLES}{\texttt{tables}} +- \newcommand{\with}{\text{~with~}} +- \newcommand{\stepto}{~\hookrightarrow~} +- \newcommand{\wif}[1]{\text{if}~#1} +- \newcommand{\diminish}[1]{\begin{footnotesize}#1\end{footnotesize}} +--- + +[^supervised]: supervised by Magnus Myreen. Parts of the report, especially the + description of \K, is due to Everett Hildenbrandt. \normalsize + +\newpage +Background +========== + +The motivation for this project came from wanting work on formal verification on +smart contracts. The combination of immutable, public code and processing high +value assets calls for high assurance software engineering. Looking at what has +been done in that space led me to the company MakerDAO[^maker], which famously +have verified the core contracts or their distrubuted application for the Dai +cryptocurrency[^dai-verified]. Much of this verification effort has been carried +out by, or in collaboration with, the organization DappHub[^dapphub]. When I +reached out to them in turn I was told that the *de facto* standard toolset for +verifying smart contracts is the \K framework[^kframework]. + +[^maker]: +[^dai-verified]: +[^dapphub]: +[^kframework]: + +The current state-of-the-art method of verifying Ethereum smart contracts goes +something as follows: + +1. Contracts are compiled to Ethereum virtual machine (EVM) bytecode. +2. Some property or invariant is specified as a rewrite rule. +3. \K tries to construct a proof (using the SMT solver Z3) that every possible + execution path eventually rewrites to the correct thing +4. The tool KLab (by DappHub) offers an interactive view of execution paths, + great for seeing where and why the prover failed. + +The reason \K can be used for verifying smart contracts is that there is a full +formalization of the Ethereum Virtual Machine (EVM) in \K, called KEVM +[@hildenbrandt-saxena-zhu-rosu-k-evm]. The EVM is a stack machine of +approximately 120 opcodes, many of which are very similar to eachother. The EVM +is currently the only virtual machine available to run Ethereum contracts. +However, an ongoing project in the Ethereum community is trying to migrate to +"ewasm"[^ewasm]: an Ethereum virtual machine built on top of WebAssembly. + +[^ewasm]: + +There are several reasons listed in the design documents: + +- Speed +- Size +- Security +- Write contracts in C/C++, go, or rust +- Static analysis +- Optional metering +- Portability: ewasm contracts will be compatibile with any standard Wasm + environment, including IoT and mobile devices + +Seeing how Ethereum might migrate to ewasm in the future, there is reason to +start looking into formalizing ewasm in \K. Since ewasm mostly extends +WebAssembly (and only alters WebAssembly slightly to allow for gas metering), +the first step to a formalization of ewasm in \K is a formalization of +WebAssembly. As it turns out, the main author of []KEVM, Everett Hildenbrandt, +has been working on a prototype semantics for WebAssembly. Development was put +on hold due to other projects, so I reached out to Everett and asked if I could +pick up the baton, which he happily let me do. Since January 2019, I have been +adding more of the WebAssembly semantics to the project, with help and guidance +from Everett. + +This report will serve as a brief introduction to the \K framework, WebAssembly +and KWasm, our formalization of WebAssembly in \K. A reader familiar with \K +and/or WebAssembly may want to skip ahead. + +------ + +\newpage +# Brief introduction to \K # + + + +The \K framework lets one define programming languages by giving their syntax +and semantics. The vision of \K is to provide a language-independent framework +for obtaining not only parsers, interpreters and compilers, but also more +non-standard tools like documentation, program verifiers, and model +checkers. Once a language is definied, all these tools should follow. +Perhaps the most interesting tool that comes out of a \K specification is the +Reachability Logic Prover [@stefanescu-park-yuwen-li-rosu-reachability-prover]. + +![\K Tooling Overview](media/img/k-overview.png) + +Several large and popular programming languages have been specified in \K, among +them Java 1.4 [@bogdanas-rosu-k-java], C11 [@hathhorn-ellison-rosu-k-c], and +JavaScript [@park-stefanescu-rosu-k-js]. There are ongoing projects to specfy +LLVM[^llvm] and X86 assemby[^x86]. + +[^llvm]: +[^x86]: + +A language specification in \K consists of 3 things: + +* the concrete syntax, +* a configuration, which is the exectuion state, +* operational semantics as rewrite rules on the configuration. + +## \K Specifications: Syntax ## + +The concrete syntax is built using EBNF style. As a running example, we use a +small, imperative language. + +```k + syntax Exp ::= Int | Id | "(" Exp ")" [bracket] + | Exp "*" Exp + > Exp "+" Exp // looser binding + + syntax Stmt ::= Id ":=" Exp + | Stmt ";" Stmt + | "return" Exp +``` + +This would allow correctly parsing programs like: + +```imp + a := 3 * 2; + b := 2 * a + 5; + return b +``` + +## \K Specifications: Configuration ## + +The configuration gives the structure of the execution state. +For example, a simple imperative language might have the following +configuration. + +```k + configuration $PGM:Program + .Map + .Map +``` + +- `` will contain the initial parsed program +- `` contains bindings of variable names to store locations +- `` contains bindings of store locations to integers + + +Note that the parsed program gets put into the `` cell. Thus, the program is +part of the execution state, and can be reduced or extended as part of rewrites. + +## \K Specifications: Transition Rules ## + +The actual operational semantics of a \K program is written as rewrite rules. +For example, we might define the following semantic rules for our imperative +language. + +A transition rule takes the form + +```k +rule X => Y + Z => W +``` + +where the arrow represents a rewrite: the value to the left is rewritten to the +value on the right. The above rule says that "if `` contains `X` and +`` contains `Z`, change the contents of `` to `Y` and the contents +of `` to `W`." A rule is an atomic transition, so both cells are +rewritten simultaneously. The rewrite arrows are given inside each cell to make +the rules easier to read and write. + +Another feature of the rules is that they only need to mention the parts of the +configuration they match on. If the configuration contains a cell `` +whose contents we do not want to rewrite, and that we do not want to extract any +information from, it does not have to be mentioned in the rule. Actually, +mentioning unsused cells in a rule is bad practice and makes the configuration +less modular: if we at some point were to remove `` from the +configuration we would have to also edit rules in which it is not used in any +meaningful way. + +Perhaps equally convenient, but less intuitive, is that you can leave out +surrounding cells when writing rules. The rewrite + +```k +rule + X => Y + +``` + +can be equivalently written as + +```k +rule X => Y +``` + +This freedom is convenient, since configuration cell can contain many +nestings, but it also increases modularity, as it allows us to nest the cell +`` with more or less context, without having to edit rules for which +the context is irrelevant. + +### Syntactic sugar for matching parts of a cell ### + +Idiomatically, the `` cell contains an associative +sequencing operator, `~>`, so the `` cell may look something like + +```k + STMT1 ~> STMT2 ~> STMT3 ~> MORE_STATEMENTS +``` + +A rewrite in the `` cell which only cares about the first of these statements +and replaces it by `NEW` (which could be anything) may then look something like +the following, inside a `rule` declaration: + +```k + (STMT1 ~> THE_OTHER_STATEMENTS) + => (NEW ~> THE_OTHER_STATEMENTS) + +``` + +Equivalently, we can apply the rewrite in place: + +```k + (STMT1 => NEW) ~> THE_OTHER_STATEMENTS +``` + +To avoid the trouble of explicitly mentioning the entire contents of the cell, +there is syntacit sugar that lets us write a rule like this is with ellipses, +which mean "and something else after `~>`". + +```k + STMT1 => NEW ... +``` + +Finally, when the configuration contains a Map, we may look up a key-value pair +with the key `X` using the following syntax: + +```k + MAP_LEFT X |-> SX MAP_RIGHT +``` + +meaning there is a bunch of map entries to the left, and a bunch of map entries +to the right. `Map`s in \K are defined as either the empty map, `.Map`, the map +of a single entry `X |-> SX`, or the concatenation of two `Maps`, i.e. `syntax +Map ::= Map Map`, the concatenation operator just being a single space in this +case. + +Since the map concatenation is commutative and associative, we can match on any +part of the map, wheras when the operator is just associative, like `~>`, it is +only possible to match on the leftmost operand[^assoc-comm]. + +[^assoc-comm]: The annotations `assoc` and `comm` marks opeartions associative + and commutative. + +We can write map lookup in a nicer, sugared form: + +```k + ... X |-> SX ... +``` + +Rewrites on a `Map` take the form + +`Map` rewrites take the following forms. + +Adding entries: + +```k + MAP_LEFT (.Map => X |-> SX) MAP_RIGHT +// or + ... (.Map => X |-> SX) ... +``` + +Changing values: + +```k + MAP_LEFT X |-> (SX => SX') MAP_RIGHT +// or + ... X |-> (SX => SX') ... +``` + +There are other, more idiomatic syntaxes for doing lookups, additions, updates +and deletions, but this syntax is the closest to the underlying definition of +`Map`. The interested reader may see the +[`domains.k`](https://github.com/kframework/k/blob/master/k-distribution/include/builtin/domains.k) +that follows with the K framework. + +### Variable lookup ### + +When an identifier is the first element in the `` cell, rewrite it to its +corresponding value, by first looking up what store location it is bound to. + +```k + rule X:Id => V ... + ... X |-> SX ... + ... SX |-> V ... +``` + +### Variable assignment ### + +Similarly, variable assigment is done by looking up the store location a +variable points to and replacing what is there by the right-hand side of the +assignment. + +```k + rule X := I:Int => . ... + ... X |-> SX ... + ... SX |-> (V => I) ... +``` + +Note that, for this rule to apply, the right-hand side must be an integer +literal, i.e., a fully evaluated expression. + +### Example Execution ### + +To show how to run the \K semantics, we take the program from before and supply +an initial configuration. + +```imp + a := 3 * 2; + b := 2 * a + 5; + return b +``` + +The configuration already has the variables and store locations initialized. In +a more realistic example of an imperative language, we would need to write +syntax and rules for how to declare and allocate variables, but for this +example, it suffices to assume that the configuration looks as follows. + +```k + a := 3 * 2 ; b := 2 * a + 5 ; return b + a |-> 0 b |-> 1 + 0 |-> 0 1 |-> 0 +``` + +First, \K (using some auxilliary rules of a language like this) is going to +break out the first statement in the initial list of statements, then break out +the right-hand side of the first assignment, and put it back fully evaluated +(let's not bother with how that is done for now: it's just something some other +rules do). Then, \K sees the following configuration ... + +```k + a := 6 ~> b := 2 * a + 5 ; return b + a |-> 0 b |-> 1 + 0 |-> 0 1 |-> 0 +``` + +and \K realizes that the following rule matches that configuration ... + +```k + rule X := I:Int => . ... + ... X |-> SX ... + ... SX |-> (V => I) ... +``` + +and applies the rule, giving the next configuration: + + +```k + b := 2 * a + 5 ; return b + a |-> 0 b |-> 1 + 0 |-> 6 1 |-> 0 +``` + +Again, some other rules apply and \K works its magic, until it gets to this +configuration: + +```k + a ~> b := 2 * [] + 5 ; return b + a |-> 0 b |-> 1 + 0 |-> 6 1 |-> 0 +``` + +What happened was that \K encountered a variable as it tried to evaluate the +right-hand side of the assignment to `b`. So, before it could proceed with the +arithmetic, it needed to get the value of `a`. So \K "heated" `a` and put it +first at the top of the stack, leaving a "hole" in the expression where the +result should be plugged back in. + +Now, with a solitary identifier at the head of the `` cell, the following +rule applies ... + +```k + rule X:Id => V ... + ... X |-> SX ... + ... SX |-> V ... +``` + +... leading to this configuration ... + +```k + 6 ~> b := 2 * [] + 5 ; return b + a |-> 0 b |-> 1 + 0 |-> 6 1 |-> 0 +``` + +... and after plugging back in the result `6` ... + +```k + b := 2 * 6 + 5 ; return b + a |-> 0 b |-> 1 + 0 |-> 6 1 |-> 0 +``` + +... and again, evaluating the right-hand side to an integer: + +```k + b := 17 ~> return b + a |-> 0 b |-> 1 + 0 |-> 6 1 |-> 0 +``` + +After those steps, our assigment rule matches again ... + +```k + rule X := I:Int => . ... + ... X |-> SX ... + ... SX |-> (V => I) ... +``` + +... and we und up with this final configuration + +```k + return 17 + a |-> 0 b |-> 1 + 0 |-> 6 1 |-> 17 +``` + +We take the final configuration to be the result of executing the above program. +If we wanted, we could of course define a further rule that deals with return +statements. + +In principle, \K executes programs by taking an initial configuration and +producing a resulting configuration, i.e., the configuration we are left with +when no more rules can be applied. In practice, we often start with an empty +configuration, containing only the very mininal initial values, and end when the +`` cell is completely empty. + +------- + +\newpage Brief introduction to WebAssembly +======== + +WebAssembly[@rossberg-web-up-to-speed], commonly known as "Wasm", is a low-ish +level language that is neither web-specific, nor really an assembly language in +the normal sense. Some of the boasted features are + +- Fast on modern hardware architectures, but platform agnostic. +- Allows stream compiling. +- Efficient byte format, readable text format. +- Safety features + - Blocks, loops and breaks, but not arbitrary jumps. + - Static validation. + - No implicit casts. + +Code fold/unfold +---------------- + +Wasm comes in both a bytecode format and a text format. I will use the text +format for all my examples. + +While Wasm is a stack-based, low-level language, with many of the usual +operations one would expect from a stack based assembly language, Wasm code in +the wild often takes a "folded" structure for increased readbility. The folding +uses S-expression syntax to group operations with their operands. + +In a regular stack-based fashion, one might write the following piece of code, +which gets the current memory size (in 64 Kb pages), loads a byte from memory, +and adds the size to the loaded byte. + + +```scheme +(memory.size) ;; Nullary -- push memory size (i32). +(i64.extend_i32_u) ;; Unary -- i32 ==> i64. +(local.get $tmp) ;; Nullary -- push local variable $tmp (i32). +(i64.load8_u) ;; Unary -- load 1 byte from argument address, push. +(i64.add) ;; Binary +``` + +Wasm allows us to write the exact same piece of code in the following way. + +```scheme +(i64.add + (i64.extend_i32_u (memory.size)) + (i64.load8_u (local.get $tmp))) +``` + +Note how each $n$-ary operation is followed by $n$ (folded) instructions. + +We can also mix freely between folded and unfolded syntax. The following is yet +another equivalent way to write the code above: + +```scheme +(i64.extend_i32_u (memory.size)) +(i64.load8_u (local.get $tmp))) +(i64.add) +``` + +We are not allowed, however, to fold paritally, i.e., giving the arguemnts to +`add` in this way: + +```scheme +(i64.load8_u (local.get $tmp))) +(i64.add (i64.extend_i32_u (memory.size))) ;; Not valid! +``` + +We either must give all $n$ arguments in the folded way, or none. + +Modules +--- + +All Wasm code is organized in modules, much like some languages organize all +code into classes. A module may declare functions, allocate and modify their own +linear memory, maintain module-global variables, etc. All these assets are +internal to the module by default, but the module may explicitly export them +with a name, which allows other modules to import, and treat as their own, the +assests of other modules. + +The following is a simple module showcasing some of the functionality of Wasm by +declaring a memory and two functions that modify memory. + +```scheme + +(module + (memory 1) + ;; The function $init will get run when the module is loaded. + (start $init) + (func $init + ;; Put 4 bytes of 1's at the start of the memory. + (i32.store (i32.const 0) (i32.const -1)) + ) + (func ;; Function descriptors. + (export "myGrowAndStoreFunction") + (param $by i32) (param $put i64) + (result i64) + (local $tmp i32) + + ;; Body of the function. + + ;; Grow memory, discard result (old memory size, or -1) + (drop (memory.grow (local.get $by))) + ;; Set this variable to the first address + ;; in the last page of memory. + (local.set $tmp + (i32.mul + (i32.sub (memory.size) (i32.const 1)) + (i32.const 65536))) + ;; Store a value in little-endian form at the location $tmp. + (i64.store (local.get $tmp) (local.get $put)) + ;; Add the + (i64.add + (i64.extend_i32_u (local.get $tmp)) + (i64.load8_u (local.get $tmp))) + ) ;; End function. +) ;; End module. +``` + +We can load this module into a reference interpreter[^ref-interpreter] and +invoke the exported function a few times: + +[^ref-interpreter]: + + +``` +$ ./wasm -i ../../my_programs/modules.wast - +wasm 1.0 reference interpreter +> (invoke "myGrowAndStoreFunction" (i32.const 2) (i64.const 0)) +131072 : i64 +> (invoke "myGrowAndStoreFunction" (i32.const 2) (i64.const 0)) +262144 : i64 +> (invoke "myGrowAndStoreFunction" (i32.const 0) (i64.const -1)) +262399 : i64 +``` + +Wasm Specification +------------------ + +The offical specification is available at . +It is well written and contains both a procedural, prose desription of how to +execute every operation, as well as a rewrite-based small-step semantic rules. + +For example, the `(memory.size)` operation has the following execution specification: + +1. Let $\FRAME$ be the current frame. +2. \diminish{Assert: due to validation, $\FRAME.\MODULE.\MEMADDRS[0]$ + exists.}[^validation] +3. Let $a$ be the memory address $\FRAME.\MODULE.\MEMADDRS[0]$.[^memIdxZero] +4. \diminish{Assert: due to validation, $\STORE.\MEMS[a]$ exists.} +5. Let $mem$ be the memory instance $\STORE.\MEMS[a]$. +6. Let $sz$ be the length of $mem.\DATA$ divided by the page size. +7. Push the value $\ITHREETWO.\CONST sz$ to the stack. + +[^validation]: Validation happens before execution. In KWasm, we always assume + that code we execute has been validated. +[^memIdxZero]: Every module in Wasm has a single memory for now, so we always + implicitly work on `memaddrs[0]`. + +The semantic rewrite rule is as follows. + +$$ +\STORE; \FRAME; \MEMORY.\SIZE \stepto \STORE; \FRAME; (\ITHREETWO.\CONST sz) +$$ +$$ +(\wif{|\STORE.\MEMS[\FRAME.\MODULE.\MEMADDRS[0]].\DATA| = sz \cdot 64 Ki)} +$$ + +Notice the struct-like access to the members of $\STORE$ and $\FRAME$. +The execution state contains a store of all declared modules, their functions, +memories, etc. It also contains a current exectuion frame with all local +variables and the module that the current call happens in. A memory is a byte +vector and an optional, unsigned 32-bit integer defining the maximum size of the +memory. Here's the relevant part of the execution state: + +\begin{alignat*}{5} +%&store &::=~&\{ & \quad &\FUNCS ~&\quad &funcinst^* & \\ +&store &::=~&\{ & \quad &\dots ~&\quad & & \\ +%& & & & \quad &\TABLES ~&\quad &tableinst^* & \\ +& & & & \quad &\MEMS ~&\quad &meminst^* & \\ +& & & & \quad &\dots ~&\quad & &\} \\ +%& & & & \quad &\GLOBALS ~&\quad &globalinst^* \quad &\} \\ +&meminst &::=~&\{ & \quad &\DATA ~&\quad &vec(byte) & \\ +& & & & \quad &\MAX ~&\quad &u32^? &\} \\ +&frame &::=~&\{ & \quad &\LOCALS ~&\quad &val^* & \\ +& & & & \quad &\MODULE ~&\quad &moduleinst \quad &\} \\ +&moduleinst~&::=~&\{~& \quad &\dots ~&\quad & & \\ +& & & & \quad &\MEMADDRS ~&\quad &memaddr^* & \\ +& & & & \quad &\dots ~&\quad & &\} +\end{alignat*} + +Asterisks indicate an array of 0 or more elements, and a question mark declares +a field as optional. + +------ + +\newpage +KWasm +===== + +KWasm is being developed at . + +The high-level goals of KWasm are to give a complete execution semantics of +Wasm, and to serve as a basis for KeWasm. + +A complete semantics of Wasm +---------------------------- + +The goal is to correctly run all valid Wasm programs, i.e., providing semantics +for a superset of Wasm. KWasm may parse programs that are not strictly +syntactically correct Wasm programs, and allows executing standalone script +programs outside of a module. + +This approach allows KWasm to run programs outside of modules. For example KWasm +can run the program `(i32.add (i32.const 1) (i32.const 2))` on its own, +producing a value on top of the stack. We consider this to be good from a +pedagogical perspective, and allows us to build the semantics from the bottom +up, starting with simple instructions and moving to modules. + +We tend to assume, as a design principle, that any real Wasm program that is +passed to KWasm will first have been validated. From that premise, we can skip +validation steps to keep our semantics simpler. + +A basis for KeWasm +------------------ + +KeWasm should (ideally) only extend KWasm, not modify it. The focus on KeWasm +means that in practice, we may alter the first goal by ignoring floating point +opertions. Implementing correct semantics for floating point arithmetic +according to the Wasm specification is a large undertaking and is not part of +the eWasm semantics, which uses only integer arithmetic, using fixed-point +interpretation to represent fractional numbers. + +Future direction +---------------- + +The semantics are fairly early-stage. Handling memory declaration, +instantiation, load and store are in progress. Everything floating point remains +to be done (but may be pushed until some verification effort requires it). So +does implementing *tables*, a mechanism for having function pointers. Finally, +KWasm does not currently support modules -- as mentioned, we are building the +semantics bottom-up, and modules are the highest-up construct. + +KEVM currently has many verified smart contracts at +. +We similarly would like to build a repository of verified code using KeWasm. + +Example of a translation from the WebAssembly specification into \K +------------------------------------------------------------------ + +As a case study, let's look at the `(memory.grow)` operation. It gives an +example of modifying a configuration in `K`, how we diverge from the literal +letter of the Wasm specification for performance reasons, how we use +abstractions and functions to help us develop the semantics, and how we deal +with the few cases of non-determinism in WebAssembly. + + +### The specification ### + +Recall the *store*, *meminst*, *frame* and *modulinst* parts of the runtime +structure: + +\begin{alignat*}{5} +%&store &::=~&\{ & \quad &\FUNCS ~&\quad &funcinst^* & \\ +&store &::=~&\{ & \quad &\dots ~&\quad & & \\ +%& & & & \quad &\TABLES ~&\quad &tableinst^* & \\ +& & & & \quad &\MEMS ~&\quad &meminst^* & \\ +& & & & \quad &\dots ~&\quad & &\} \\ +%& & & & \quad &\GLOBALS ~&\quad &globalinst^* \quad &\} \\ +&meminst &::=~&\{ & \quad &\DATA ~&\quad &vec(byte) & \\ +& & & & \quad &\MAX ~&\quad &u32^? &\} \\ +&frame &::=~&\{ & \quad &\LOCALS ~&\quad &val^* & \\ +& & & & \quad &\MODULE ~&\quad &moduleinst \quad &\} \\ +&moduleinst~&::=~&\{~& \quad &\dots ~&\quad & & \\ +& & & & \quad &\MEMADDRS ~&\quad &memaddr^* & \\ +& & & & \quad &\dots ~&\quad & &\} +\end{alignat*} + +The following is the semantic rules for `(memory.grow)`. Note that there are two +rules which are not mutually exclusive. This is because growing memory may fail +for reasons unknown to Wasm, i.e., the embdedder has no resources to allocate. +In this situation, the operation returns `-1`. Note also that the other rule is +specified in terms of an auxilliary function, $growmem$. This functon may fail, +in which case that rule isn't appied, and failure happens instead. + +\begin{align*} +\STORE; \FRAME ; &(\ITHREETWO.\CONST n)~(\MEMORY.\GROW) \stepto \STORE ' ; \FRAME ; (\ITHREETWO.\CONST sz ) \\ +(&\wif{\FRAME.\MODULE.\MEMADDRS[0] = a \\ +&\land sz = |\STORE.\MEMS[a].\DATA|/64 Ki \\ +&\land \STORE ' = \STORE \with \MEMS[a] = growmem(\STORE.\MEMS[a], n))} \\ +\\ +\STORE; \FRAME ; &(\ITHREETWO.\CONST n)~(\MEMORY.\GROW) \stepto \STORE ; \FRAME ; (\ITHREETWO.\CONST {-1} ) \\ +\end{align*} + +\begin{align*} +growmem(meminst, n)\quad =\quad &\MEMINST \with \DATA = \MEMINST.\DATA~(\texttt{0x00})^{n \cdot 64 Ki} \\ +&(\wif{len = n + |\MEMINST.\DATA|/64 Ki \\ +&\land len \leq 2^{16} \\ +&\land (\MEMINST.\MAX = \epsilon \lor len \leq \MEMINST.\MAX)}) +\end{align*} + +The $\with$ keyword indicates modifying a single field of a structure, in this +case the $\DATA$ field of a $\MEMINST$. + +The `(memory.grow)` operation should thus either succeed with the old size of the +memory pushed to the stack, or fail by pushing a `-1` to the stack. Since the +32-bit unsigned interpretation of `-1` is larger than the max allowed memory +size, `2^{16}`, there is no ambiguity here. + + +In \K +---- + +This is the full definition of the `(memory.grow)` operation: + +```k + syntax Instr ::= "(" "memory.grow" ")" + | "(" "memory.grow" Instr ")" + | "grow" Int + // -------------------------------------------- + rule + ( memory.grow I:Instr ) => + I ~> ( memory.grow ) ... + + rule ( memory.grow ) => grow N ... + < i32 > N : STACK => STACK + + rule grow N + => < i32 > #if #growthAllowed(SIZE +Int N, MAX) + #then SIZE + #else -1 + #fi ... + 0 |-> ADDR + + ADDR + MAX + SIZE => #if #growthAllowed(SIZE +Int N, MAX) + #then SIZE +Int N + #else SIZE + #fi + ... + + + rule grow N => < i32 > -1 + + false + + + syntax Bool ::= #growthAllowed(Int, MemBound) [function] + // -------------------------------------------------------- + rule #growthAllowed(SIZE, .MemBound) + => SIZE <=Int #maxMemorySize() + rule #growthAllowed(SIZE, I:Int) + => #growthAllowed(SIZE, .MemBound) andBool SIZE <=Int I +``` + +The first rule simply unfolds the folded case, and the second rule pops an +operand (which must be of type `i32`, due to validation), and passes it to an +instructon in abstract syntax, `grow`. This instruction is not in Wasm, but +since all syntax defined in \K becomes concrete, it would be possbile to use +this syntax when writing a program for KWasm. + +The third and fourth rule implement the semantic rules from the Wasm +specification somewhat faithfully. There is a check made with the help of a +function, `#growthAllowed`, that checks that we don't violate the size +conditions. + +A deviation from the official specification is that we store the size of memory +explicitly. The spec, read literally, asks an implementation to allocate full +pages of zereo-bytes when growing (or allocating) memory. For space efficiency, +we store only the size, and represent data as a map from indices to non-zero +bytes. + +Finally, we add a flag to the configuration to tell KWasm when to allow +non-deterministic fails. Since we may want to conduct proofs on programs under +the assumption that memory growth does not fail spuriously, we allow a toggle +for this in the configuration, so that proofs can be conducted under different +assumptions. By setting the toggle `` to `true`, we +can conduct proofs under the assumption that the foruth rule is never applied. +In the future, we may make this more advanced, by maintaining instead a counter +of available memory pages, decrementing it when memory is allocated or grown, +and stating only allowing the fourth rule when we would run out of memory. + +Hopefully this serves as an illustration of how we generally follow the Wasm +specification, but make a few design choices to suit our needs, when we can do +so without changing the semantics of an actual Wasm program. + +---- + +\newpage +References +========== diff --git a/docs/runtimeverification-wasm-semantics/media/201906-presentation-wasm-on-blockchain.md b/docs/runtimeverification-wasm-semantics/media/201906-presentation-wasm-on-blockchain.md new file mode 100644 index 000000000..c09d4c3a7 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/media/201906-presentation-wasm-on-blockchain.md @@ -0,0 +1,394 @@ +--- +title: 'KWasm' +subtitle: 'Semantics of WebAssembly in the K framework' +author: +- Rikard Hjort +- Everett Hildenbrandt +- Qianyang Peng +date: June 8, 2019 +institute: +- Runtime Verification, Inc. +theme: metropolis +fontsize: 8pt +header-includes: +- \usepackage{color} +- \usepackage{fancyvrb} +- \fvset{commandchars=\\\{\}} +- \newcommand{\instr}{instr} +- \newcommand{\STORE}{\textit{S}} +- \newcommand{\FRAME}{\textit{F}} +- \newcommand{\CONST}{\texttt{const~}} +- \newcommand{\LOCALGET}{\texttt{local.get~}} +- \newcommand{\LOCALSET}{\texttt{local.set~}} +- \newcommand{\DATA}{\texttt{data}} +- \newcommand{\FUNCS}{\texttt{funcs}} +- \newcommand{\GLOBALS}{\texttt{globals}} +- \newcommand{\GROW}{\texttt{grow}} +- \newcommand{\ITHREETWO}{\texttt{i32}} +- \newcommand{\LOCALS}{\texttt{locals}} +- \newcommand{\MEMADDRS}{\texttt{memaddrs}} +- \newcommand{\MEMORY}{\texttt{memory}} +- \newcommand{\MEMS}{\texttt{mems}} +- \newcommand{\MODULE}{\texttt{module}} +- \newcommand{\SIZE}{\texttt{size}} +- \newcommand{\TABLES}{\texttt{tables}} +- \newcommand{\with}{\textit{~with~}} +- \newcommand{\stepto}{~\hookrightarrow~} +- \newcommand{\wif}[1]{\text{if}~#1} +- \newcommand{\diminish}[1]{\begin{footnotesize}#1\end{footnotesize}} +--- + + +Overview +-------- + +> 1. KWasm: Intro and roadmap +> 2. Introduction to K & KEVM +> 3. Deepdive: What the semantics look like +> 4. Demo: Proving things + +. . . + +\vspace{1em} + +Please ask questions as we go. + +KWasm: Intro and roadmap +======================== + +KWasm +----- + +> * KWasm is the project name for specifying Wasm in K. +> * K is a framework for creating **runnable specifications** of programming languages. +> * K uses rewrite based semantics, just like those Wasm is defined with [@rossberg-web-up-to-speed]. +> * The goal is to use the runnable spec to **formally verify** aspects of blockchain runtimes and smart contracts. +> * There is already a specification of the EVM, called KEVM [@hildenbrandt-saxena-zhu-rosu-k-evm], which we use for formal verification. \newline ![](media/img/kevm-paper.png) + +Status +------ + +![](media/img/github-top-screenshot.png) + +* Bulk of the semantics are done. +* Tables and indirect calls in progress. + +A few big todos: + +- Defining and instantiating several modules. +- Parsing more textual versions of commands (syntactic sugar). +- Add floating point numbers (not top priority). + +Design +------ + +* A very faithful translation of Wasm spec (K and Wasm both use rewrite semantics). Some differences: + - Two stacks: one for operands, one for instructions and control flow. + - We are *more permissive*; allow running instructions directly: +\newline `(i32.add (i32.const 1337) (i32.const 42))` is a full KWasm program. +* Execution focused, we assume validation is done beforehand. + +Goals +----- + +- "Make KEVM for Ethereum 2.0". +- Create Ewasm semantics, KEwasm, by importing and embedding KWasm. +- We would like to build a repository of verified code using KEwasm. +There is such a repository for KEVM: + +[![](media/img/github-verified-contracts-screenshot.png)](https://github.com/runtimeverification/verified-smart-contracts) + +Introduction to K +================= + +The Vision: Language Independence +--------------------------------- + +![K Tooling Overview](media/img/k-overview.png) + + +K Tooling/Languages +------------------- + +### Tools + +- Parser +- Interpreter +- Debugger +- Reachability Logic Prover [@stefanescu-park-yuwen-li-rosu-reachability-prover] +- ... + +. . . + +### Languages + +- Java 1.4 - 2015 [@bogdanas-rosu-k-java] +- C11 - 2015 [@hathhorn-ellison-rosu-k-c] +- KJS - 2015 [@park-stefanescu-rosu-k-js] +- KEVM - 2018 [@hildenbrandt-saxena-zhu-rosu-k-evm] +- KLLVM +- KX86-64 +- In progress (external groups): + - Solidity + - Rust + +Parts of a K specification +-------------------------- + +A language spec in K consists of 3 things + +* Syntax +* Configuration ("state") +* Operational semantics as **rewrite rules** + +K Specifications: Syntax +------------------------ + +Concrete syntax built using EBNF style: + +```k + syntax IValType ::= "i32" | "i64" + syntax Instr ::= "(" IType "." "const" Int ")" + syntax Instr ::= "(" "local.get" Int ")" | "(" "local.set" Int ")" + syntax Instr ::= "(" IValType "." IBinOp ")" // Concrete syntax + | IValType "." IBinOp Int Int // Abstract syntax + syntax IBinOp ::= "div_u" + syntax Instrs ::= List{Instr, ""} // Builtin helper for cons lists. +``` + +Note: we tend to mix abstract and concrete syntax. + +. . . + +This would allow parsing a program like this: + +```scheme + (local.get 1) + (local.get 0) + (i32.div_u) + (local.set 0) +``` + +K Specifications: Configuration +------------------------------- + +Tell K about the structure of your execution state. + +```k + configuration $PGM:Instrs + .ValStack + .Map +``` + +. . . + +> - `` will contain the initial parsed program. +> - `` operand stack of `Val` items. +> - `` a mapping `Int -> Val` + +. . . + +```k + syntax Val ::= "<" IValType ">" Int +``` + +K Specifications: Transition Rules +---------------------------------- + +Using the above grammar and configuration: + +. . . + +### Push to ValStack + +\begin{Verbatim}[] + rule ( ITYPE . const I ) \textcolor{blue}{=>} #chop(< ITYPE > I) \textcolor{blue}{...} +\end{Verbatim} + +. . . + +> - `=>` is the rewrite arrow. +> - Words in all caps are variables. +> - We match on and rewrite the front of the cell contents, and `...` matches the rest of the cell. +> - We don't need to mention the cells we don't use or modify. + +\vfill{} + +. . . + +\begin{Verbatim}[] + rule \textcolor{blue}{V:Val => \textbf{.}} ... + \textcolor{blue}{VALSTACK => V : VALSTACK} +\end{Verbatim} + +. . . + +> - `.` is like $\epsilon$, so rewriting to `.` is erasing. +> - We can rewrite several cells at once. +> - In ``, we match on the entire cell. + +K Specifications: Transition Rules +---------------------------------- + +### Helper functions: + +\begin{Verbatim}[] + syntax Val ::= #chop ( Val ) \textcolor{blue}{[function]} + // ---------------------------------------- + rule #chop(< ITYPE > N) => < ITYPE > (N modInt #pow(ITYPE)) + + syntax Int ::= #pow ( IValType ) \textcolor{blue}{[function]} + // ------------------------------------------- + rule #pow (i32) => 4294967296 +\end{Verbatim} + +. . . + +\vspace{1em} + +> - The `[function]` annotation means the rule applies regardless of context. + +K Specifications: Transition Rules +---------------------------------- + +### Binary operators + +\begin{Verbatim}[] + rule ( ITYPE . BOP:IBinOp ) => ITYPE . BOP C1 C2 ... + + < ITYPE > C2 : < ITYPE > C1 : VALSTACK => VALSTACK + + + rule ITYPE . div_u I1 I2 => < ITYPE > (I1 /Int I2) ... + \textcolor{blue}{requires I2 =/=Int 0} + rule ITYPE . div_u I1 I2 => undefined ... + \textcolor{blue}{requires I2 ==Int 0} +\end{Verbatim} + +\vspace{1em} + +- `requires` specifies side conditions. +- We often use K operators specialized by type, e.g. `==Int` + +K Specifications: Transition Rules +---------------------------------- + +### Get local variable + +$$ +\FRAME ; (\LOCALGET x) \stepto \FRAME ; val +\qquad \textcolor{blue}{(\wif \FRAME.\LOCALS[x] = val)} +$$ + +. . . + +\begin{Verbatim}[] + rule ( local.get INDEX ) => . ... + VALSTACK => VALUE : VALSTACK + ... \textcolor{blue}{INDEX |-> VALUE} ... +\end{Verbatim} + +. . . + +- `` is a `Map` (builtin data structure), which is an associative-commutative pair of values. We can put `...` on both sides indictating we are matching *somewhere* in the `Map`. + + +K Specifications: Transition Rules +---------------------------------- + +### Set local variable + +$$ +\FRAME ; (\LOCALSET x) \stepto \FRAME' ; \epsilon +\qquad (\wif \FRAME' = \FRAME \textcolor{blue}{\with \LOCALS[x] = val}) +$$ + +. . . + +\begin{Verbatim}[] + rule ( local.set INDEX ) => . ... + VALUE : VALSTACK => VALSTACK + ... INDEX |-> \textcolor{blue}{(_ => VALUE)} ... +\end{Verbatim} + +. . . + +- `_` is a wildcard (matches any value). +- We can use parentheses to isolate the part of a term we are rewriting, like updating the value part of a map entry. + + +## Example execution + +### We can use KLab to explore execution of our example program. + +```scheme + (local.get 1) + (local.get 0) + (i32.div_u) + (local.set 0) +``` + +with intial configuration + +```k + + 0 |-> 4 + 1 |-> 24 + +``` + +. . . + +\vfill + +\center\huge DEMO! + +Repo tour +========= + +Repo layout +--------- + +* Literate programming style in markdown with K code blocks. +* `wasm.md`: The main part of the semantics +* `data.md`: Some helper data structures. +* `test.md`: Some useful assertion functions. +* `kwasm-lemmas.md`: A trusted base for proving. + +Proving +======= + +Verifying Wasm programs +---------------------------- + +> 1. From the KWasm semantics, K generates a parser and a deductive program verifier. +> 2. A verification claim is written like a rewrite rule. `rule A => B` should be read as "`A` will eventually always evaluate to `B`". +> 3. The automatic prover tries to construct a proof (with the help of Z3 to check constraint satisfiability) that every possible execution path starting in `A` eventually rewrites to `B`. +> 4. KLab offers an interactive view of execution, both the successful and the failed paths. + +. . . + +\vfill + +\center\huge DEMO! + +RV specializes in formal verification +------------------------------------- + +- If you're interested in verification of Wasm programs, talk to us! +- [rikard.hjort@runtimeverification.com](rikard.hjort@runtimeverification.com) +- https://riot.im/app/#/room/#k:matrix.org + +Conclusion/Questions? +===================== + +Thanks! +------- + +- Thanks for listening! + +References +---------- + +\tiny + diff --git a/docs/runtimeverification-wasm-semantics/media/202001-presentation-dlab.md b/docs/runtimeverification-wasm-semantics/media/202001-presentation-dlab.md new file mode 100644 index 000000000..ac464cc30 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/media/202001-presentation-dlab.md @@ -0,0 +1,687 @@ +--- +title: 'KWasm' +subtitle: 'Semantics of WebAssembly in the \K framework' +author: +- Rikard Hjort (presenting) +- Everett Hildenbrandt +- Qianyang Peng +- Stephen Skeirik +date: June 8, 2019 +institute: +- Runtime Verification, Inc. +theme: metropolis +fontsize: 8pt +header-includes: +- \usepackage{color} +- \usepackage{fancyvrb} +- \usepackage{listings} +- \fvset{commandchars=\\\{\}} +- \newcommand{\instr}{instr} +- \newcommand{\STORE}{\textit{S}} +- \newcommand{\FRAME}{\textit{F}} +- \newcommand{\CONST}{\texttt{const~}} +- \newcommand{\LOCALGET}{\texttt{local.get~}} +- \newcommand{\LOCALSET}{\texttt{local.set~}} +- \newcommand{\DATA}{\texttt{data}} +- \newcommand{\FUNCS}{\texttt{funcs}} +- \newcommand{\GLOBALS}{\texttt{globals}} +- \newcommand{\GROW}{\texttt{grow}} +- \newcommand{\ITHREETWO}{\texttt{i32}} +- \newcommand{\LOCALS}{\texttt{locals}} +- \newcommand{\MEMADDRS}{\texttt{memaddrs}} +- \newcommand{\MEMORY}{\texttt{memory}} +- \newcommand{\MEMS}{\texttt{mems}} +- \newcommand{\MODULE}{\texttt{module}} +- \newcommand{\SIZE}{\texttt{size}} +- \newcommand{\TABLES}{\texttt{tables}} +- \newcommand{\with}{\textit{~with~}} +- \newcommand{\stepto}{~\hookrightarrow~} +- \newcommand{\wif}[1]{\text{if}~#1} +- \newcommand{\diminish}[1]{\begin{footnotesize}#1\end{footnotesize}} +- \newcommand{\K}{$\mathbb{K}$~} +- \newcommand{\lK}{$\mathbb{K}$} +--- + + +Overview +-------- + +> 1. KWasm: Intro and roadmap +> 2. \K recap +> 3. Proving things + +KWasm: Intro and roadmap +======================== + +KWasm +----- + +> * The goal is to use the runnable spec to **formally verify** aspects of blockchain runtimes and smart contracts. +> * The current targets are **Ewasm** (Ethereum smart contract VM) an **Pwasm** (Polkadot client bytecode). + +Status +------ + +![](media/img/github-top-screenshot.png) + +* 98 % complete for text format (missing a few floating point instructions). +* We have a full Wasm semantics, and prototype Ewasm and Pwasm **embeddings**. +* No bytecode support (yet), so bytecode is piped through `wasm2wat` tool. +* We have verified a few simple properties, but not yet any "real" programs (in progress with WRC20). + +Design +------ + +* A very faithful translation of Wasm spec (\K and Wasm both use rewrite semantics). Some differences: + - Two stacks: one for operands, one for instructions and control flow. + - We are *more permissive*; allow running instructions directly: +\newline `(i32.add (i32.const 1337) (i32.const 42))` is a full KWasm program. +* Execution focused, we assume validation is done beforehand. No type checking or similar. +* We sometimes need to be more explicit to make rules computational. + +Working with \K +============= + +The Vision: Language Independence +--------------------------------- + +\center +\includegraphics[height=0.9\paperheight]{media/img/k-overview} + + +\K Tooling/Languages +------------------- + +### Tools + +- Parser +- Interpreter +- Debugger +- Reachability Logic Prover [@stefanescu-park-yuwen-li-rosu-reachability-prover] +- ... + +. . . + +### Languages + +- Java 1.4 - 2015 [@bogdanas-rosu-k-java] +- C11 - 2015 [@hathhorn-ellison-rosu-k-c] +- KJS - 2015 [@park-stefanescu-rosu-k-js] +- KEVM - 2018 [@hildenbrandt-saxena-zhu-rosu-k-evm] +- KLLVM +- KX86-64 +- In progress (external groups): + - Solidity + - Rust + +Parts of a \K specification +-------------------------- + +A language spec in \K consists of 3 things + +* Syntax +* Configuration ("state") +* Operational semantics as **rewrite rules** + + + +\K Specifications: Transition Rules +---------------------------------- + +We have configuration with a few cells. + +Among them a `` cell and a `` cell. + +. . . + +### Push to ValStack + +\begin{Verbatim}[] + rule ( ITYPE . const I ) \textcolor{blue}{=>} #chop(< ITYPE > I) \textcolor{blue}{...} +\end{Verbatim} + +. . . + +> - `=>` is the rewrite arrow. +> - Words in all caps are variables. +> - We match on and rewrite the front of the cell contents, and `...` matches the rest of the cell. +> - We don't need to mention the cells we don't use or modify. + +\vfill{} + +. . . + +\begin{Verbatim}[] + rule \textcolor{blue}{V:Val => \textbf{.}} ... + \textcolor{blue}{VALSTACK => V : VALSTACK} +\end{Verbatim} + +. . . + +> - `.` is like $\epsilon$, so rewriting to `.` is erasing. +> - We can rewrite several cells at once. +> - In ``, we match on the entire cell. + + +\K Specifications: Transition Rules +---------------------------------- + +### Helper functions: + +\begin{Verbatim}[] + syntax Val ::= #chop ( Val ) \textcolor{blue}{[function]} + // ---------------------------------------- + rule #chop(< ITYPE > N) => < ITYPE > (N modInt #pow(ITYPE)) + + syntax Int ::= #pow ( IValType ) \textcolor{blue}{[function]} + // ------------------------------------------- + rule #pow (i32) => 4294967296 +\end{Verbatim} + +. . . + +\vspace{1em} + +> - The `[function]` annotation means the rule applies regardless of context. +> - This is also how we write lemmas. A lemma is basically a function rule. + +Proving +======= + +Verifying Wasm programs +---------------------------- + +> 1. From the KWasm semantics, \K generates a parser and a deductive program verifier. +> 2. A verification claim is written like a rewrite rule. `rule A => B` should be read as "`A` will eventually always evaluate to `B`". +> 3. The automatic prover tries to construct a proof (with the help of Z3 to check constraint satisfiability) that every possible execution path starting in `A` eventually rewrites to `B`. +> 4. KLab offers an interactive view of execution, both the successful and the failed paths. + +The program (pseudo-code) +----------- + +``` +(func $i64.reverse_bytes(i64 input) + i64 local[1] = 0 + i64 local[2] = 0 + while True: + if local[1] >= 8: + break + bits = 56 - (local[1] * 8) + local[2] = local[2] + (((input << bits) >> 56) << bits) + local[1] = local[1] + 1 + return local[2] +) +``` + +Specification +------------- + +``` +rule #wrc20ReverseBytes + ~> (i64.load (i32.const ADDR)) + (i64.store (i32.const ADDR) (call $i64.reverse_bytes)) + => . ... + +// ... + BM => BM' + requires #inUnsignedRange(i32, ADDR) +// ... + ensures #get(BM, ADDR +Int 0) ==Int #get(BM', ADDR +Int 7 ) + andBool #get(BM, ADDR +Int 1) ==Int #get(BM', ADDR +Int 6 ) + andBool #get(BM, ADDR +Int 2) ==Int #get(BM', ADDR +Int 5 ) + andBool #get(BM, ADDR +Int 3) ==Int #get(BM', ADDR +Int 4 ) + andBool #get(BM, ADDR +Int 4) ==Int #get(BM', ADDR +Int 3 ) + andBool #get(BM, ADDR +Int 5) ==Int #get(BM', ADDR +Int 2 ) + andBool #get(BM, ADDR +Int 6) ==Int #get(BM', ADDR +Int 1 ) + andBool #get(BM, ADDR +Int 7) ==Int #get(BM', ADDR +Int 0 ) +``` + + + + +First proof attempt +------------------- + +\tiny + +``` +( ( ( #getRange ( BM , ADDR , 8 ) <>Int 56 <>Int 56 <>Int 56 <>Int 56 <>Int 56 <>Int 56 <>Int 56 <>Int 56 < * Each line in the top part represents a byte. +> * The expression is essentially a sum, where the implicit invariant that they can only have `1` bits in separate locations (no addition carry). + +First proof attempt +------------------- + +Least significant byte in original number, modified the first loop iteration: + +``` +(((((( #getRange(BM, ADDR, 8) <>Int 56) <>Int} + child { + node (k) {modInt} + child { + node (m) {< X} +\pause + +\vspace*{16pt} + +\texttt{rule (X modInt M) modInt N => X modInt M} + +\texttt{~~requires M >Int 0} + +\texttt{~~~andBool M <=Int N} + \pause + +\vspace*{16pt} + +\texttt{rule (X < (X modInt (POW /Int (2 \^{}Int N)))} + +\texttt{~~~~ <=Int 0} + +\texttt{~~~ andBool POW >Int 0} + +\texttt{~~~ andBool POW modInt (2 \^{}Int N) ==Int 0} + +\end{minipage} + +New state +--------- + +\begin{minipage}[l]{0.40\textwidth} +\begin{tikzpicture}[scale=0.56] + \node (a) {modInt} + child { + node (b) {+Int} + child { + node (d) {modInt} + child { + node (e) {<>Int} + child { + node (k) {modInt} + child { + node (m) {<>Int} + child { + node (m) {<{\begin{minipage}[r]{0.52\textwidth} + +\vspace*{60pt} + +\texttt{rule (X <>Int M} + +\texttt{~ => X <=Int M} + +\vspace*{16pt} + +\uncover<3->{ + +\texttt{rule \#getRange(BM, ADDR, WIDTH) modInt 256} + +\texttt{~ => \#get(BM, ADDR)} + +\texttt{~ requires WIDTH =/=Int 0} + +\texttt{~~ andBool \#isByteMap(BM)} + +\texttt{~ ensures 0 <=Int \#get(BM, ADDR)} + +\texttt{~~ andBool \#get(BM, ADDR) {\begin{minipage}[r]{0.39\textwidth} +\begin{tikzpicture}[scale=0.6] + \node (a) {<>Int} + child { + node (m) {<>Int} + child { + node (m) {< * WRC20 is a token contract written in Wasm. +> * The capstone of this project would have been to fully verify it. + +. . . + +* It has two public functions: + - `balanceOf :: address -> i256` + - `transfer :: (address, i256 value) -> i1` + +\$i64.reverse\_bytes +-------------------- + +WRC20 has one helper function: + +`reverse_bytes :: i64 -> i64` + +. . . + +Ethereum sends parameter big-endian encoded, but Wasm uses little-endian. +Parameters and return values need to be converted accordingly. + +. . . + +In pseudo-code: + +``` +func $i64.reverse_bytes(i64 input) (result i64) { + i64 i = 0 + i64 res = 0 + while i < 8 { + bits = 56 - (i * 8) + res = res + (((input << bits) >> 56) << bits) + i++ + } + return res +} +``` + +. . . + +A pure Wasm function! + +Overview +-------- + + +1. The tool: KWasm +2. Finishing KWasm + * Completing KWasm + * Ewasm embedding +3. Proofs and axioms + * Example: Verifying the helper function + * Axiom engineering +4. Discussion + +KWasm as a tool +=============== + +Why \lK? +-------- + +* Give \K an operational semantics, and it produces a powerful and automatic reachability logic prover. [@stefanescu-park-yuwen-li-rosu-reachability-prover] + +. . . + +* Mature + - Complex languages: + - KEVM - 2018 [@hildenbrandt-saxena-zhu-rosu-k-evm] + - Java 1.4 - 2015 [@bogdanas-rosu-k-java] + - C11 - 2015 [@hathhorn-ellison-rosu-k-c] + - KJS - 2015 [@park-stefanescu-rosu-k-js] + - KLLVM + - KX86-64 + +. . . + +* Uses non-deterministic rewriting, same formalism as Wasm. + +. . . + +* The forerunner to KWasm, KEVM, has seen significant adoption, and we want to build on the success. + +. . . + +* KWasm was a mature prototype when we started---no need to start from scratch. + +Steps Towards Finishing KWasm +=========================== + +Completing KWasm +---------------- + +![](media/img/github-top-screenshot.png) + +* Before the project, Wasm lacked support for more than one module. +* Ad-hoc tests. + +. . . + +We + +* added module support. +* wrote a test harness to test KWasm against the official conformance tests. + +. . . + +Each are a topic of their own, skipping over them in the interest of time. + +Ewasm embedding +--------------- + +\center +![](media/img/ewasm-contract.png){ width=90%} + +\flushleft + +> * An Ewasm contract exposes a `"main"` function that is invoked when the contract is called. +> * Ethereum state is accessed through imported host functions, 256-bit results are returned in the Wasm memory. +> * The Wasm state is cleared between each invocation, persistent data lives in the blockchain client. +> * Design: Ethereum client (EEI) and Wasm semantics composed into one semantics with a thin boundary between them. + +Proofs and Axioms +===================== + +Verifying Wasm programs +---------------------------- + +> 1. From the KWasm semantics, \K generates a parser and a deductive program verifier. +> 2. A verification claim is written like a rewrite rule. `rule A => B` should be read as "`A` will eventually always evaluate to `B`". +> 3. A proof is (hopefully) constructed for the entire rewrite by composing rules in the semantics. Some equalities and side conditions are proved by Z3, the SMT solver. +> 3. The automatic prover tries to construct a proof (with the help of Z3 to check constraint satisfiability) that every possible execution path starting in `A` eventually rewrites to `B`. +> 4. We can help the prover by adding lemmas (which must be proven), or axioms (which are taken for true). These must be expressed as context-free rewrite rules, i.e. they can rewrite expressions but not step the machine. + +Verification Example: `reverse_bytes` Program Again +----------------- + +``` +func $i64.reverse_bytes(i64 input) (result i64) { + i64 i = 0 + i64 res = 0 + while i < 8 { + bits = 56 - (i * 8) + res = res + (((input << bits) >> 56) << bits) + i++ + } + return res +} +``` + +Specification +------------- + +``` +rule #wrc20ReverseBytes + ~> (i64.load (i32.const ADDR)) + (i64.store (i32.const ADDR) (call $i64.reverse_bytes)) + => . ... + +// ... + BM => ?BM' + requires #inUnsignedRange(i32, ADDR) +// ... + ensures #get(BM, ADDR +Int 0) ==Int #get(?BM', ADDR +Int 7 ) + andBool #get(BM, ADDR +Int 1) ==Int #get(?BM', ADDR +Int 6 ) + andBool #get(BM, ADDR +Int 2) ==Int #get(?BM', ADDR +Int 5 ) + andBool #get(BM, ADDR +Int 3) ==Int #get(?BM', ADDR +Int 4 ) +// ... +``` + +. . . + +> * To test reversing bytes, we load from memory (linear byte array), call the function, store the result memory, and compare memory locations. +> * Expressed as a side condition (`ensures`). +> * Uses a simplifying sidecondition (`requires`). + +First proof attempt +------------------- + +The prover runs the full program, but ends up storing the following symbolic expression back to the memory location. + +\tiny + +``` +( ( ( #getRange ( BM , ADDR , 8 ) <>Int 56 <>Int 56 <>Int 56 <>Int 56 <>Int 56 <>Int 56 <>Int 56 <>Int 56 < * Each line in the top part represents a byte. +> * The expression is essentially a sum, where the implicit invariant that they can only have `1` bits in separate locations (no addition carry). + + +First proof attempt +------------------- + +> * Our approach will be to extend \lK's ability to reason about these kinds of arithmetic expressions, and about the `#getRange` function +> * Axioms will be added as context-free rewrite rules. +> * Each is simple and has been hand-verified, but not yet machine-verified. + + +. . . + +Least significant byte in original number, modified the first loop iteration: + +``` +(((((( #getRange(BM, ADDR, 8) + <>Int 56) + <>Int} + child { + node (k) {modInt} + child { + node (m) {< X} +\pause + +\vspace*{16pt} + +\texttt{rule (X modInt M) modInt N => X modInt M} + +\texttt{~~requires M >Int 0} + +\texttt{~~~andBool M <=Int N} + \pause + +\vspace*{16pt} + +\texttt{rule (X < (X modInt (POW /Int (2 \^{}Int N)))} + +\texttt{~~~~ <=Int 0} + +\texttt{~~~ andBool POW >Int 0} + +\texttt{~~~ andBool POW modInt (2 \^{}Int N) ==Int 0} + +\end{minipage} + +New state +--------- +\small + +\begin{minipage}[l]{0.40\textwidth} +\begin{tikzpicture}[scale=0.56] + \node (a) {modInt} + child { + node (b) {+Int} + child { + node (d) {modInt} + child { + node (e) {<>Int} + child { + node (k) {modInt} + child { + node (m) {<>Int} + child { + node (m) {<{\begin{minipage}[r]{0.52\textwidth} + +\vspace*{60pt} + +\texttt{rule (X <>Int M} + +\texttt{~ => X <=Int M} + +\vspace*{10pt} + +\uncover<3->{ +\texttt{rule X < X} +} + +\vspace*{10pt} + +\uncover<4->{ + +\texttt{rule \#getRange(BM, ADDR, WIDTH) modInt 256} + +\texttt{~ => \#get(BM, ADDR)} + +\texttt{~ requires WIDTH =/=Int 0} + +\texttt{~~ andBool \#isByteMap(BM)} + +\texttt{~ ensures 0 <=Int \#get(BM, ADDR)} + +\texttt{~~ andBool \#get(BM, ADDR) {\begin{minipage}[r]{0.391\textwidth} +\begin{tikzpicture}[scale=0.6] + \node (a) {<>Int} + child { + node (m) {<>Int} + child { + node (m) {< * \K supports adding new axioms (as rewrite rules), or new statements to Z3, to help the prover. +> * Whenever we can, we choose to add lemmas to KWasm and upstream them into the \K tool when they are general enough. +> * Axioms in \K apply unconditionally, so there is a risk of infinite rewrites. (No commutativity or associativity axioms!) +> * We found a lexicographic product which the axioms always decrease. + +. . . + +$$ +(b, e, n) +$$ + +- $b$: Number and height in parse tree of certain operations, (for now `mod`, `<<`, and `>>`.) +- $e$: Expression size. +- $n$: Sum of absolute values of integers. + +Discussion & Conclusion +======================= + +Discussion & Conclusion +----------------------- + +> * We are missing the capstone: verifying a smart contract ... +> * ... but pieces are in place: completed KWasm, embedding, memory lemmas, an example of pure Wasm verification. +> * \K needs more arithmetic reasoning, and KWasm is spearheading the process. + +Future Work +----------- + +> * Verifying the full WRC20 would tie it all together. Started work, but still exploratory. +> * Committee with other \K projects on what axioms we want in general. +> * Proving lemmas sound, possible even using the \K prover itself. +> * More control over the prover, proof assistant style. +> * A DSL for the prover---expressing properties over blockchain state is verbose and error prone without it. + +. . . + +Open source project[^2], currently funded, development is ongoing. + +Thanks! +------- + +- Thanks for listening! +- [rikard.hjort@runtimeverification.com](rikard.hjort@runtimeverification.com) +- https://riot.im/app/#/room/#k:matrix.org + +\vfill + +### References + +\tiny + +Cover image by Bogdan Stanciu. + + +[^1]: , as of 2020-01-25 + +[^2]: + diff --git a/docs/runtimeverification-wasm-semantics/media/berlin-demo/README.md b/docs/runtimeverification-wasm-semantics/media/berlin-demo/README.md new file mode 100644 index 000000000..8370636da --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/media/berlin-demo/README.md @@ -0,0 +1,14 @@ +The files in this directory give a demo of a simple Wasm program, and of trying to prove a property of division. + +Standing in this directory, do + +```sh +./pre-run.sh +``` + +This will run the `example-execution.wast` file, and perform proofs on the `div*-spec.k` files. +Two of the proofs will fail, so an error message and non-zero exit codes is to be expected. + +You can then explore exection and proofs with `./kwasm klab-view [FILENAME]`. +For example, `./kwasm klab-view div3-spec.k` +See the documentation for [KLab](https://github.com/dapphub/klab) for details of how to use KLab diff --git a/docs/runtimeverification-wasm-semantics/media/berlin-demo/div1-spec.k b/docs/runtimeverification-wasm-semantics/media/berlin-demo/div1-spec.k new file mode 100644 index 000000000..015011518 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/media/berlin-demo/div1-spec.k @@ -0,0 +1,31 @@ +requires "kwasm-lemmas.k" + +module DIV1-SPEC + imports WASM + imports KWASM-LEMMAS + +// Try to prove an incorrect property of unsigned division: +// If X' = X / Y, then X' < X. + + rule (local.set 0 + (i32.div_u (local.get 0) (local.get 1))) + // Rewriting to "." means we expect the program to complete + // without trapping. + => . + + + 0 |-> < i32 > (X => X') + 1 |-> < i32 > Y + + // We must specify that X and Y fulfill the invariant for locals to + // be in the unsigned range. Otherwise, X and Y can be *any* integers. + requires #inUnsignedRange(i32, X) + andBool #inUnsignedRange(i32, Y) + ensures X' 0, +// then X' < X + + rule (if (local.get 1) + (then (local.set 0 + (i32.div_u (local.get 0) (local.get 1)))) + (else + + (local.set 0 (i32.const 0)))) + => . + + + 0 |-> < i32 > (X => X') + 1 |-> < i32 > Y + + requires #inUnsignedRange(i32, X) + andBool #inUnsignedRange(i32, Y) + + andBool X =/=Int 0 + ensures X' 0, +// and Y > 1, +// then X' < X + rule (if (local.get 1) + (then (local.set 0 + (i32.div_u (local.get 0) (local.get 1)))) + (else + + (local.set 0 (i32.const 0)))) + => . + + + 0 |-> < i32 > (X => X') + 1 |-> < i32 > Y + + requires #inUnsignedRange(i32, X) + andBool #inUnsignedRange(i32, Y) + andBool Y =/=Int 1 + andBool X =/=Int 0 + ensures X' 0, +// and Y > 1, +// then X' < X + +// Has en extra neat demonstration, that the `br_if` will not cause a branch, +// because we know the value of Y at that point. + + rule (if (local.get 1) + (then (local.set 0 + (i32.div_u (local.get 0) (local.get 1)))) + (else + (br_if 0 (local.get 1)) // Will not branch, due to our constraints. + (local.set 0 (i32.const 0)))) + => . + + + 0 |-> < i32 > (X => X') + 1 |-> < i32 > Y + + requires #inUnsignedRange(i32, X) + andBool #inUnsignedRange(i32, Y) + andBool Y =/=Int 1 + andBool X =/=Int 0 + ensures X' 4 : < i32 > 24 : .ValStack + +(local.get 1) +(local.get 0) +(i32.div_u) +(local.set 0) \ No newline at end of file diff --git a/docs/runtimeverification-wasm-semantics/media/berlin-demo/pre-run.sh b/docs/runtimeverification-wasm-semantics/media/berlin-demo/pre-run.sh new file mode 100755 index 000000000..5152d9caa --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/media/berlin-demo/pre-run.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +cd ../.. +rm -rf .build/klab +./build +./kwasm klab-run media/berlin-demo/example-execution.wast +for i in `seq 4`; do + FILE=div$i-spec.k + echo "" + echo "" + echo "$FILE" + ./kwasm klab-prove media/berlin-demo/$FILE -m KWASM-LEMMAS +done + +cd - diff --git a/docs/runtimeverification-wasm-semantics/media/citations.md b/docs/runtimeverification-wasm-semantics/media/citations.md new file mode 100644 index 000000000..9b995670f --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/media/citations.md @@ -0,0 +1,234 @@ +--- +csl: media/ieee.csl +references: +- id: bogdanas-rosu-k-java + type: paper-conference + author: + - family: Bogdănaş + given: Denis + - family: Roşu + given: Grigore + issued: + - year: 2015 + title: 'K-Java: A Complete Semantics of Java' + container-title: Proceedings of the 42nd symposium on principles of programming + languages (popl’15) + publisher: ACM + page: 445-456 + DOI: http://dx.doi.org/10.1145/2676726.2676982 + +- id: hathhorn-ellison-rosu-k-c + type: paper-conference + author: + - family: Hathhorn + given: Chris + - family: Ellison + given: Chucky + - family: Roşu + given: Grigore + issued: + - year: 2015 + title: Defining the undefinedness of c + container-title: Proceedings of the 36th acm sigplan conference on programming language + design and implementation (pldi’15) + publisher: ACM + page: 336-345 + DOI: http://dx.doi.org/10.1145/2813885.2737979 + +- id: park-stefanescu-rosu-k-js + type: paper-conference + author: + - family: Park + given: Daejun + - family: Ştefănescu + given: Andrei + - family: Roşu + given: Grigore + issued: + - year: 2015 + title: 'KJS: A complete formal semantics of JavaScript' + title-short: KJS + container-title: Proceedings of the 36th acm sigplan conference on programming language + design and implementation (pldi’15) + publisher: ACM + page: 346-356 + DOI: http://dx.doi.org/10.1145/2737924.2737991 + +- id: hildenbrandt-saxena-zhu-rosu-k-evm + type: paper-conference + author: + - family: Hildenbrandt + given: Everett + - family: Saxena + given: Manasvi + - family: Zhu + given: Xiaoran + - family: Rodrigues + given: Nishant + - family: Daian + given: Philip + - family: Guth + given: Dwight + - family: Moore + given: Brandon + - family: Zhang + given: Yi + - family: Park + given: Daejun + - family: Ştefănescu + given: Andrei + - family: Roşu + given: Grigore + issued: + - year: 2018 + title: 'KEVM: A complete semantics of the ethereum virtual machine' + title-short: KEVM + container-title: 2018 ieee 31st computer security foundations symposium + publisher: IEEE + page: 204-217 + +- id: kheradmand-rosu-k-p4 + type: report + author: + - family: Kheradmand + given: Ali + - family: Roşu + given: Grigore + issued: + - year: 2018 + title: 'P4K: A formal semantics of p4 and applications' + title-short: P4K + publisher: University of Illinois at Urbana-Champaign + number: https://arxiv.org/abs/1804.01468 + +- id: kasampalis-guth-moore-rosu-johnson-k-iele + type: report + author: + - family: Kasampalis + given: Theodoros + - family: Guth + given: Dwight + - family: Moore + given: Brandon + - family: Serbanuta + given: Traian + - family: Serbanuta + given: Virgil + - family: Filaretti + given: Daniele + - family: Rosu + given: Grigore + - family: Johnson + given: Ralph + issued: + - year: 2018 + title: 'IELE: An intermediate-level blockchain language designed and implemented + using formal semantics' + title-short: IELE + publisher: University of Illinois + number: http://hdl.handle.net/2142/100320 + +- id: stefanescu-park-yuwen-li-rosu-reachability-prover + type: paper-conference + author: + - family: Ştefănescu + given: Andrei + - family: Park + given: Daejun + - family: Yuwen + given: Shijiao + - family: Li + given: Yilong + - family: Roşu + given: Grigore + issued: + - year: 2016 + month: 11 + title: Semantics-based program verifiers for all languages + container-title: Proceedings of the 31th conference on object-oriented programming, + systems, languages, and applications (oopsla’16) + publisher: ACM + page: 74-91 + DOI: http://dx.doi.org/10.1145/2983990.2984027 + +- id: alpuente-cuenca-ortega-escobar-meseguer + type: article-journal + author: + - family: Alpuente + given: María + - family: Cuenca-Ortega + given: Angel + - family: Escobar + given: Santiago + - family: Meseguer + given: José + issued: + - year: 2016 + title: Partial evaluation of order-sorted equational programs modulo axioms + container-title: CoRR + volume: abs/1608.03424 + URL: http://arxiv.org/abs/1608.03424 + +- id: meseguer-rusu-generalized-rewrite-coherence-completion + type: paper-conference + author: + - family: Meseguer + given: José + editor: + - family: Rusu + given: Vlad + issued: + - year: '2018' + title: Generalized rewrite theories and coherence completion + container-title: Rewriting logic and its applications - 12th international workshop, + wrla 2018, held as a satellite event of etaps, 2018, proceedings + collection-title: Lecture notes in computer science (including subseries lecture + notes in artificial intelligence and lecture notes in bioinformatics) + publisher: Springer-Verlag + page: '164-183' + keyword: Coherence, Rewriting, Symbolic execution, Variants + DOI: 10.1007/978-3-319-99840-4_10 + ISBN: '9783319998398' + + +- id: rossberg-web-up-to-speed + type: article-journal + author: + - family: Rossberg + given: Andreas + - family: Titzer + given: Ben L. + - family: Haas + given: Andreas + - family: Schuff + given: Derek L. + - family: Gohman + given: Dan + - family: Wagner + given: Luke + - family: Zakai + given: Alon + - family: Bastien + given: J. F. + - family: Holman + given: Michael + issued: + - year: 2018 + title: Bringing the web up to speed with webassembly. + container-title: Communications of the ACM + page: 107 - 115 + volume: '61' + issue: '12' + abstract: This article discusses the implementation of the low-level computer programming + language for software known as WebAssembly. The authors comment on the use of + WebAssembly in advanced web applications, including three-dimensional visualization, + audio and video software, and gaming. The benefits of WebAssembly include its + ability to function independent of the computer hardware and operating system + on which it runs. + keyword: PROGRAMMING languages, ASSEMBLY languages (Electronic computers), COMPUTER + operating systems, COMPUTER software, THREE-dimensional imaging, WEB-based user + interfaces + ISSN: '00010782' + +... + diff --git a/docs/runtimeverification-wasm-semantics/media/ieee.csl b/docs/runtimeverification-wasm-semantics/media/ieee.csl new file mode 100644 index 000000000..301349ca2 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/media/ieee.csl @@ -0,0 +1,354 @@ + + + diff --git a/docs/runtimeverification-wasm-semantics/media/img/RV-logo-blue.eps b/docs/runtimeverification-wasm-semantics/media/img/RV-logo-blue.eps new file mode 100644 index 000000000..feae90ab9 Binary files /dev/null and b/docs/runtimeverification-wasm-semantics/media/img/RV-logo-blue.eps differ diff --git a/docs/runtimeverification-wasm-semantics/media/img/cover_image.svg b/docs/runtimeverification-wasm-semantics/media/img/cover_image.svg new file mode 100644 index 000000000..f6e177270 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/media/img/cover_image.svg @@ -0,0 +1,214 @@ + + + + + + image/svg+xml + + KWasm cover Rikard + + + + + + + + KWasm cover Rikard + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/runtimeverification-wasm-semantics/media/img/dapphub.png b/docs/runtimeverification-wasm-semantics/media/img/dapphub.png new file mode 100644 index 000000000..a5858a895 Binary files /dev/null and b/docs/runtimeverification-wasm-semantics/media/img/dapphub.png differ diff --git a/docs/runtimeverification-wasm-semantics/media/img/ethereum.png b/docs/runtimeverification-wasm-semantics/media/img/ethereum.png new file mode 100644 index 000000000..41776c7a4 Binary files /dev/null and b/docs/runtimeverification-wasm-semantics/media/img/ethereum.png differ diff --git a/docs/runtimeverification-wasm-semantics/media/img/ewasm-contract.png b/docs/runtimeverification-wasm-semantics/media/img/ewasm-contract.png new file mode 100644 index 000000000..6eb811f4a Binary files /dev/null and b/docs/runtimeverification-wasm-semantics/media/img/ewasm-contract.png differ diff --git a/docs/runtimeverification-wasm-semantics/media/img/github-top-screenshot.png b/docs/runtimeverification-wasm-semantics/media/img/github-top-screenshot.png new file mode 100644 index 000000000..e22cfe2ca Binary files /dev/null and b/docs/runtimeverification-wasm-semantics/media/img/github-top-screenshot.png differ diff --git a/docs/runtimeverification-wasm-semantics/media/img/github-verified-contracts-screenshot.png b/docs/runtimeverification-wasm-semantics/media/img/github-verified-contracts-screenshot.png new file mode 100644 index 000000000..2bd08ca37 Binary files /dev/null and b/docs/runtimeverification-wasm-semantics/media/img/github-verified-contracts-screenshot.png differ diff --git a/docs/runtimeverification-wasm-semantics/media/img/k-overview.png b/docs/runtimeverification-wasm-semantics/media/img/k-overview.png new file mode 100644 index 000000000..90a9e36a5 Binary files /dev/null and b/docs/runtimeverification-wasm-semantics/media/img/k-overview.png differ diff --git a/docs/runtimeverification-wasm-semantics/media/img/k.png b/docs/runtimeverification-wasm-semantics/media/img/k.png new file mode 100644 index 000000000..74c547321 Binary files /dev/null and b/docs/runtimeverification-wasm-semantics/media/img/k.png differ diff --git a/docs/runtimeverification-wasm-semantics/media/img/kevm-paper.png b/docs/runtimeverification-wasm-semantics/media/img/kevm-paper.png new file mode 100644 index 000000000..5496345a3 Binary files /dev/null and b/docs/runtimeverification-wasm-semantics/media/img/kevm-paper.png differ diff --git a/docs/runtimeverification-wasm-semantics/media/img/maker.png b/docs/runtimeverification-wasm-semantics/media/img/maker.png new file mode 100644 index 000000000..f97637ff3 Binary files /dev/null and b/docs/runtimeverification-wasm-semantics/media/img/maker.png differ diff --git a/docs/runtimeverification-wasm-semantics/media/img/reachability-logic-inference-system.png b/docs/runtimeverification-wasm-semantics/media/img/reachability-logic-inference-system.png new file mode 100644 index 000000000..ad031fe14 Binary files /dev/null and b/docs/runtimeverification-wasm-semantics/media/img/reachability-logic-inference-system.png differ diff --git a/docs/runtimeverification-wasm-semantics/media/img/rv.png b/docs/runtimeverification-wasm-semantics/media/img/rv.png new file mode 100644 index 000000000..81289772d Binary files /dev/null and b/docs/runtimeverification-wasm-semantics/media/img/rv.png differ diff --git a/docs/runtimeverification-wasm-semantics/media/k-overview.png b/docs/runtimeverification-wasm-semantics/media/k-overview.png new file mode 100644 index 000000000..90a9e36a5 Binary files /dev/null and b/docs/runtimeverification-wasm-semantics/media/k-overview.png differ diff --git a/docs/runtimeverification-wasm-semantics/media/memory-demo/.gitignore b/docs/runtimeverification-wasm-semantics/media/memory-demo/.gitignore new file mode 100644 index 000000000..21d038c09 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/media/memory-demo/.gitignore @@ -0,0 +1,3 @@ +.krun-* +*-kompiled + diff --git a/docs/runtimeverification-wasm-semantics/media/memory-demo/README.md b/docs/runtimeverification-wasm-semantics/media/memory-demo/README.md new file mode 100644 index 000000000..fcafac9e8 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/media/memory-demo/README.md @@ -0,0 +1,13 @@ +Building and running example + +``` +kompile --syntax-module WASM wasm.k +krun example-memory.wast +``` + +Proving + +``` +kompile --syntax-module WASM --backend java wasm.k +kprove memory-spec.k +``` diff --git a/docs/runtimeverification-wasm-semantics/media/memory-demo/example_memory.wast b/docs/runtimeverification-wasm-semantics/media/memory-demo/example_memory.wast new file mode 100644 index 000000000..27a2d0c17 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/media/memory-demo/example_memory.wast @@ -0,0 +1,6 @@ +(i64.const 10) +(memory) +(memory.size) +(memory.grow (i32.const 5)) +(memory.size) + diff --git a/docs/runtimeverification-wasm-semantics/media/memory-demo/memory-spec.k b/docs/runtimeverification-wasm-semantics/media/memory-demo/memory-spec.k new file mode 100644 index 000000000..e8dc009c5 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/media/memory-demo/memory-spec.k @@ -0,0 +1,35 @@ +module MEMORY-SPEC + imports WASM + + rule ( memory.size ) => (i32.const SZ) ... + 0 |-> A + + A + SZ + + + rule (memory.grow (i32.const X)) => (i32.const SZ) ... + 0 |-> A + + A + SZ => (SZ +Int X) + + // Necessary conditions. Remove them to see what happens! + false + requires X >=Int 0 + andBool X <=Int (2 ^Int 16) + andBool SZ >=Int 0 + + rule (memory.grow (i32.const X)) => (i32.const -1) ... + 0 |-> A + + A + SZ + + // Necessary conditions. Remove them to see what happens! + true + requires X >=Int (2 ^Int 16) + andBool X =Int 0 + +endmodule diff --git a/docs/runtimeverification-wasm-semantics/media/memory-demo/wasm.k b/docs/runtimeverification-wasm-semantics/media/memory-demo/wasm.k new file mode 100644 index 000000000..4d3e548da --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/media/memory-demo/wasm.k @@ -0,0 +1,144 @@ +module WASM + imports DOMAINS + + // Solutions to the TODOs are in a comment at the bottom of the file. + + configuration + $PGM:Instrs + .Stack + // TODO: Extend the configuration to have a current frame and and a store. + + + // Useful definitions. + + syntax Instrs ::= List{Instr, ""} + // --------------------------------- + rule .Instrs => . + rule I IS:Instrs => I ~> IS ... + + syntax Stack ::= ".Stack" | StackItem ":" Stack + syntax StackItem ::= "<" IType ">" Int + // -------------------------------------- + + syntax IType ::= "i32" | "i64" + // ------------------------------ + + // Functions. + + syntax Int ::= #chop(IType, Int) [function] + // ------------------------------------------- + rule #chop(i32, I) => I modInt (2 ^Int 32) + rule #chop(i64, I) => I modInt (2 ^Int 64) + + + // Basic instructions. + syntax Instr ::= "(" IType "." "const" Int ")" + // ---------------------------------------------- + rule ( ITYPE . const I ) => . ... + S => < ITYPE > #chop(ITYPE, I) : S + + // Memory instructions. + + // Memory instantiation. + syntax Instr ::= "(" "memory" ")" + // --------------------------------- + // TODO: Add a rule that: + // allocates memory with size 0 to the store, and stores saves the address + // to that memory in the current frame. + + syntax Instr ::= "(" "memory.size" ")" + // -------------------------------------- + // TODO: Add a rule that: + // Looks up the memory of the current frame in the store, and pushes its + // size to the stack. + + syntax Instr ::= "(" "memory.grow" ")" | "(" "memory.grow" Instr ")" + // -------------------------------------------------------------------- + // TODO: Add a rule that: + // Unfolds the folded syntax rule. + // TODO: Add a rule that: + // Grows the current memory by a specified value (look at the top of the + // stack). This rule should only apply if the resulting memory size is less + // than 2^16 pages. + // TODO: Add a rule that: + // Only applies if we have a toggle set in the configuration, called + // . If so, it just pushes (i32.const -1). + +endmodule + + + + + + + + + + + + + + + + + +/* + + configuration + $PGM:Instrs + .Stack + + + .Map + + + + 0 + + + 0 + 0 + + + + false + + rule (( memory ) ~> ELSE) => ELSE + .Map => 0 |-> NEXT + NEXT => NEXT +Int 1 + + (.Bag => + + NEXT + ... + ) + ... + + + syntax Instr ::= "(" "memory.size" ")" + // -------------------------------------- + rule ( memory.size ) => ( i32.const SZ ) ... + 0 |-> A + + A + SZ + ... + + rule ( memory.size ) => ( i32.const -1 ) ... + + syntax Instr ::= "(" "memory.grow" ")" | "(" "memory.grow" Instr ")" + // -------------------------------------------------------------------- + rule (memory.grow I:Instr) => I ~> (memory.grow) ... + rule (memory.grow) => (i32.const SZ) ... + < i32 > V : S => S + 0 |-> A + + A + SZ => SZ +Int V + + requires SZ +Int V (memory.grow) => (i32.const -1) ... + < i32 > _ : S => S + true + +*/ \ No newline at end of file diff --git a/docs/runtimeverification-wasm-semantics/numeric.md b/docs/runtimeverification-wasm-semantics/numeric.md new file mode 100644 index 000000000..a502e00c1 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/numeric.md @@ -0,0 +1,455 @@ +Numeric Instructions +-------------------- + +In this file we implement the numeric rules specified in section `4.3 Numerics` of the offical WebAssembly specification. + +In the notations of some operators, `sx` is the signedness of the operator and could be either `s` (signed) or `u` (unsigned), which indicates whether the operands should be interpreted as signed integer or unsigned integer. + +```k +require "data.md" + +module WASM-NUMERIC-SYNTAX + + syntax IUnOp ::= "clz" [klabel(aClz), symbol] + | "ctz" [klabel(aCtz), symbol] + | "popcnt" [klabel(aPopcnt), symbol] + // --------------------------------------------------- + + syntax FUnOp ::= "abs" [klabel(aAbs) , symbol] + | "neg" [klabel(aNeg) , symbol] + | "sqrt" [klabel(aSqrt) , symbol] + | "floor" [klabel(aFloor) , symbol] + | "ceil" [klabel(aCeil) , symbol] + | "trunc" [klabel(aTrunc) , symbol] + | "nearest" [klabel(aNearest), symbol] + // ----------------------------------------------------- + + syntax IBinOp ::= "add" [klabel(intAdd), symbol] + | "sub" [klabel(intSub), symbol] + | "mul" [klabel(intMul), symbol] + | "div_u" [klabel(intDiv_u), symbol] + | "rem_u" [klabel(intRem_u), symbol] + | "div_s" [klabel(intDiv_s), symbol] + | "rem_s" [klabel(intRem_s), symbol] + | "and" [klabel(intAnd), symbol] + | "or" [klabel(intOr), symbol] + | "xor" [klabel(intXor), symbol] + | "shl" [klabel(intShl), symbol] + | "shr_u" [klabel(intShr_u), symbol] + | "shr_s" [klabel(intShr_s), symbol] + | "rotl" [klabel(intRotl), symbol] + | "rotr" [klabel(intRotr), symbol] + // -------------------------------------------------- + + syntax FBinOp ::= "add" [klabel(floatAdd), symbol] + | "sub" [klabel(floatSub), symbol] + | "mul" [klabel(floatMul), symbol] + | "div" [klabel(floatDiv), symbol] + | "min" [klabel(floatMin), symbol] + | "max" [klabel(floatMax), symbol] + | "copysign" [klabel(floatCopysign), symbol] + // ------------------------------------------------------------ + + syntax TestOp ::= "eqz" [klabel(aEqz), symbol] + // ---------------------------------------------- + + syntax IRelOp ::= "eq" [klabel(intEq), symbol] + | "ne" [klabel(intNe), symbol] + | "lt_u" [klabel(intLt_u), symbol] + | "gt_u" [klabel(intGt_u), symbol] + | "lt_s" [klabel(intLt_s), symbol] + | "gt_s" [klabel(intGt_s), symbol] + | "le_u" [klabel(intLe_u), symbol] + | "ge_u" [klabel(intGe_u), symbol] + | "le_s" [klabel(intLe_s), symbol] + | "ge_s" [klabel(intGe_s), symbol] + // -------------------------------------------------- + + syntax FRelOp ::= "lt" [klabel(floatLt), symbol] + | "gt" [klabel(floatGt), symbol] + | "le" [klabel(floatLe), symbol] + | "ge" [klabel(floatGe), symbol] + | "eq" [klabel(floatEq), symbol] + | "ne" [klabel(floatNe), symbol] + // ------------------------------------------------ + + syntax CvtOp ::= Cvti32Op | Cvti64Op | Cvtf32Op | Cvtf64Op + // ---------------------------------------------------------- + + syntax Cvti32Op ::= "extend_i32_u" [klabel(aExtend_i32_u), symbol] + | "extend_i32_s" [klabel(aExtend_i32_s), symbol] + | "convert_i32_s" [klabel(aConvert_i32_s), symbol] + | "convert_i32_u" [klabel(aConvert_i32_u), symbol] + // -------------------------------------------------------------------- + + syntax Cvti64Op ::= "wrap_i64" [klabel(aWrap_i64), symbol] + | "convert_i64_s" [klabel(aConvert_i64_s), symbol] + | "convert_i64_u" [klabel(aConvert_i64_u), symbol] + // -------------------------------------------------------------------- + + syntax Cvtf32Op ::= "promote_f32" [klabel(aPromote_f32), symbol] + | "trunc_f32_s" [klabel(aTrunc_f32_s), symbol] + | "trunc_f32_u" [klabel(aTrunc_f32_u), symbol] + // ---------------------------------------------------------------- + + syntax Cvtf64Op ::= "demote_f64" [klabel(aDemote_f64), symbol] + | "trunc_f64_s" [klabel(aTrunc_f64_s), symbol] + | "trunc_f64_u" [klabel(aTrunc_f64_u), symbol] + // ---------------------------------------------------------------- + +endmodule + +module WASM-NUMERIC + imports WASM-NUMERIC-SYNTAX + imports WASM-DATA + +``` + +### Unary Operators + +`*UnOp` takes one oprand and returns a `Val`. + +```k + syntax Val ::= IValType "." IUnOp Int [klabel(intUnOp) , function] + | FValType "." FUnOp Float [klabel(floatUnOp), function] + // --------------------------------------------------------------------- +``` + +#### Unary Operators for Integers + +There three unary operators for integers: `clz`, `ctz` and `popcnt`. + +- `clz` counts the number of leading zero-bits, with 0 having all leading zero-bits. +- `ctz` counts the number of trailing zero-bits, with 0 having all trailing zero-bits. +- `popcnt` counts the number of non-zero bits. + +Note: The actual `ctz` operator considers the integer 0 to have *all* zero-bits, whereas the `#ctz` helper function considers it to have *no* zero-bits, in order for it to be width-agnostic. + +```k + rule ITYPE . clz I1 => < ITYPE > #width(ITYPE) -Int #minWidth(I1) + rule ITYPE . ctz I1 => < ITYPE > #if I1 ==Int 0 #then #width(ITYPE) #else #ctz(I1) #fi + rule ITYPE . popcnt I1 => < ITYPE > #popcnt(I1) + + syntax Int ::= #minWidth ( Int ) [function] + | #ctz ( Int ) [function] + | #popcnt ( Int ) [function] + // ------------------------------------------- + rule #minWidth(0) => 0 + rule #minWidth(N) => 1 +Int #minWidth(N >>Int 1) requires N =/=Int 0 + + rule #ctz(0) => 0 + rule #ctz(N) => #if N modInt 2 ==Int 1 #then 0 #else 1 +Int #ctz(N >>Int 1) #fi requires N =/=Int 0 + + rule #popcnt(0) => 0 + rule #popcnt(N) => #bool(N modInt 2 ==Int 1) +Int #popcnt(N >>Int 1) requires N =/=Int 0 +``` + +Before we implement the rule for float point numbers, we first need to define a helper function. + +- `#isInfinityOrNaN` judges whether a `float` is infinity or NaN. + +```k + syntax Bool ::= #isInfinityOrNaN ( Float ) [function] + // ----------------------------------------------------- + rule #isInfinityOrNaN ( F ) => (isNaN(F) orBool isInfinite(F)) +``` + +#### Unary Operators for Floats + +There are 7 unary operators for floats: `abs`, `neg`, `sqrt`, `floor`, `ceil`, `trunc` and `nearest`. + +- `abs` returns the absolute value of the given float point number. +- `neg` returns the additive inverse value of the given float point number. +- `sqrt` returns the square root of the given float point number. +- `floor` returns the greatest integer less than or equal to the given float point number. +- `ceil` returns the least integer greater than or equal to the given float point number. +- `trunc` returns the integral value by discarding the fractional part of the given float. +- `nearest` returns the integral value that is nearest to the given float number; if two values are equally near, returns the even one. + +```k + rule FTYPE . abs F => < FTYPE > absFloat (F) + rule FTYPE . neg F => < FTYPE > --Float F + rule FTYPE . sqrt F => < FTYPE > sqrtFloat (F) + rule FTYPE . floor F => < FTYPE > floorFloat (F) + rule FTYPE . ceil F => < FTYPE > ceilFloat (F) + rule FTYPE . trunc F => < FTYPE > truncFloat (F) + rule FTYPE . nearest F => < FTYPE > F requires #isInfinityOrNaN (F) + rule FTYPE . nearest F => #round(FTYPE, Float2Int(F)) requires (notBool #isInfinityOrNaN (F)) andBool notBool (Float2Int(F) ==Int 0 andBool signFloat(F)) + rule FTYPE . nearest F => < FTYPE > -0.0 requires (notBool #isInfinityOrNaN (F)) andBool Float2Int(F) ==Int 0 andBool signFloat(F) +``` + +### Binary Operators + +`*BinOp` takes two oprands and returns a `Val`. +A `*BinOp` operator always produces a result of the same type as its operands. + +```k + syntax Val ::= IValType "." IBinOp Int Int [klabel(intBinOp) , function] + | FValType "." FBinOp Float Float [klabel(floatBinOp), function] + // ----------------------------------------------------------------------------- +``` + +#### Binary Operators for Integers + +There are 12 binary operators for integers: `add`, `sub`, `mul`, `div_sx`, `rem_sx`, `and`, `or`, `xor`, `shl`, `shr_sx`, `rotl`, `rotr`. + + +- `add` returns the result of adding up the 2 given integers modulo 2^N. +- `sub` returns the result of substracting the second oprand from the first oprand modulo 2^N. +- `mul` returns the result of multiplying the 2 given integers modulo 2^N. + +`add`, `sub`, and `mul` are given semantics by lifting the correct K operators through the `#chop` function. + +```k + rule ITYPE:IValType . add I1 I2 => #chop(< ITYPE > I1 +Int I2) + rule ITYPE:IValType . sub I1 I2 => #chop(< ITYPE > I1 -Int I2) + rule ITYPE:IValType . mul I1 I2 => #chop(< ITYPE > I1 *Int I2) +``` + +- `div_sx` returns the result of dividing the first operand by the second oprand, truncated toward zero. +- `rem_sx` returns the remainder of dividing the first operand by the second oprand. + +`div_sx` and `rem_sx` have extra side-conditions about when they are defined or not. + +```k + rule ITYPE . div_u I1 I2 => < ITYPE > I1 /Int I2 requires I2 =/=Int 0 + rule _ITYPE . div_u _I1 I2 => undefined requires I2 ==Int 0 + + rule ITYPE . rem_u I1 I2 => < ITYPE > I1 %Int I2 requires I2 =/=Int 0 + rule _ITYPE . rem_u _I1 I2 => undefined requires I2 ==Int 0 + + rule ITYPE . div_s I1 I2 => < ITYPE > #unsigned(ITYPE, #signed(ITYPE, I1) /Int #signed(ITYPE, I2)) + requires I2 =/=Int 0 + andBool #signed(ITYPE, I1) /Int #signed(ITYPE, I2) =/=Int #pow1(ITYPE) + + rule _ITYPE . div_s _I1 I2 => undefined + requires I2 ==Int 0 + + rule ITYPE . div_s I1 I2 => undefined + requires I2 =/=Int 0 + andBool #signed(ITYPE, I1) /Int #signed(ITYPE, I2) ==Int #pow1(ITYPE) + + rule ITYPE . rem_s I1 I2 => < ITYPE > #unsigned(ITYPE, #signed(ITYPE, I1) %Int #signed(ITYPE, I2)) + requires I2 =/=Int 0 + + rule _ITYPE . rem_s _I1 I2 => undefined + requires I2 ==Int 0 +``` + +- `and` returns the bitwise conjunction of the 2 given floats. +- `or` returns the bitwise disjunction of the 2 given floats. +- `xor` returns the bitwise exclusive disjunction of the 2 given floats. + +Of the bitwise operators, `and` will not overflow, but `or` and `xor` could. +These simply are the lifted K operators. + +```k + rule ITYPE . and I1 I2 => < ITYPE > I1 &Int I2 + rule ITYPE . or I1 I2 => #chop(< ITYPE > I1 |Int I2) + rule ITYPE . xor I1 I2 => #chop(< ITYPE > I1 xorInt I2) +``` + +- `shl` returns the result of shifting the first operand left by k bits modulo 2^N, in which k is the second operand modulo N. +- `shr_u` returns the result of shifting the first operand right by k bits, and extended with 0 bits. +- `shr_s` returns the result of shifting the first operand right by k bits, and extended with the most significant bit of the original value + +Similarly, K bitwise shift operators are lifted for `shl` and `shr_u`. +Careful attention is made for the signed version `shr_s`. + +```k + rule ITYPE . shl I1 I2 => #chop(< ITYPE > I1 < < ITYPE > I1 >>Int (I2 %Int #width(ITYPE)) + + rule ITYPE . shr_s I1 I2 => < ITYPE > #unsigned(ITYPE, #signed(ITYPE, I1) >>Int (I2 %Int #width(ITYPE))) +``` + +- `rotl` returns the result of rotating the first operand left by k bits, in which k is the second operand modulo N. +- `rotr` returns the result of rotating the first operand right by k bits, in which k is the second operand modulo N. + +The rotation operators `rotl` and `rotr` do not have appropriate K builtins, and so are built with a series of shifts. + +```k + rule ITYPE . rotl I1 I2 => #chop(< ITYPE > (I1 <>Int (#width(ITYPE) -Int (I2 %Int #width(ITYPE))))) + rule ITYPE . rotr I1 I2 => #chop(< ITYPE > (I1 >>Int (I2 %Int #width(ITYPE))) +Int (I1 < < FTYPE > F1 +Float F2 + rule FTYPE:FValType . sub F1 F2 => < FTYPE > F1 -Float F2 + rule FTYPE:FValType . mul F1 F2 => < FTYPE > F1 *Float F2 + rule FTYPE . div F1 F2 => < FTYPE > F1 /Float F2 + rule FTYPE . min F1 F2 => < FTYPE > minFloat (F1, F2) + rule FTYPE . max F1 F2 => < FTYPE > maxFloat (F1, F2) + rule FTYPE . copysign F1 F2 => < FTYPE > F1 requires signFloat (F1) ==Bool signFloat (F2) + rule FTYPE . copysign F1 F2 => < FTYPE > --Float F1 requires signFloat (F1) =/=Bool signFloat (F2) +``` + +### Test Operators + +Test operations consume one operand and produce a bool, which is an `i32` value. +There is no test operation for float numbers. + +```k + syntax Val ::= IValType "." TestOp Int [klabel(intTestOp), function] + // -------------------------------------------------------------------- +``` + +#### Test Operators for Integers + +- `eqz` checks wether its operand is 0. + +```k + rule _ . eqz I => < i32 > #bool(I ==Int 0) +``` + +### Relationship Operators + +Relationship Operators consume two operands and produce a bool, which is an `i32` value. + +```k + syntax Val ::= IValType "." IRelOp Int Int [klabel(intRelOp) , function] + | FValType "." FRelOp Float Float [klabel(floatRelOp), function] + // ----------------------------------------------------------------------------- +``` + +### Relationship Operators for Integers + +There are 6 relationship operators for integers: `eq`, `ne`, `lt_sx`, `gt_sx`, `le_sx` and `ge_sx`. + +- `eq` returns 1 if the 2 given integers are equal, 0 otherwise. +- `eq` returns 1 if the 2 given integers are not equal, 0 otherwise. + +```k + rule _:IValType . eq I1 I2 => < i32 > #bool(I1 ==Int I2) + rule _:IValType . ne I1 I2 => < i32 > #bool(I1 =/=Int I2) +``` + +- `lt_sx` returns 1 if the first oprand is less than the second opeand, 0 otherwise. +- `gt_sx` returns 1 if the first oprand is greater than the second opeand, 0 otherwise. + +```k + rule _ . lt_u I1 I2 => < i32 > #bool(I1 < i32 > #bool(I1 >Int I2) + + rule ITYPE . lt_s I1 I2 => < i32 > #bool(#signed(ITYPE, I1) < i32 > #bool(#signed(ITYPE, I1) >Int #signed(ITYPE, I2)) +``` + +- `le_sx` returns 1 if the first oprand is less than or equal to the second opeand, 0 otherwise. +- `ge_sx` returns 1 if the first oprand is greater than or equal to the second opeand, 0 otherwise. + +```k + rule _ . le_u I1 I2 => < i32 > #bool(I1 <=Int I2) + rule _ . ge_u I1 I2 => < i32 > #bool(I1 >=Int I2) + + rule ITYPE . le_s I1 I2 => < i32 > #bool(#signed(ITYPE, I1) <=Int #signed(ITYPE, I2)) + rule ITYPE . ge_s I1 I2 => < i32 > #bool(#signed(ITYPE, I1) >=Int #signed(ITYPE, I2)) +``` + +### Relationship Operators for Floats + +There are 6 relationship operators for floats: `eq`, `ne`, `lt`, `gt`, `le` and `ge`. + +- `eq` returns 1 if the 2 given floats are equal, 0 otherwise. +- `ne` returns 1 if the 2 given floats are not equal, 0 otherwise. +- `lt` returns 1 if the first oprand is less than the second opeand, 0 otherwise. +- `gt` returns 1 if the first oprand is greater than the second opeand, 0 otherwise. +- `le` returns 1 if the first oprand is less than or equal to the second opeand, 0 otherwise. +- `ge` returns 1 if the first oprand is greater than or equal to the second opeand, 0 otherwise. + +```k + rule _ . lt F1 F2 => < i32 > #bool(F1 < i32 > #bool(F1 >Float F2) + rule _ . le F1 F2 => < i32 > #bool(F1 <=Float F2) + rule _ . ge F1 F2 => < i32 > #bool(F1 >=Float F2) + rule _:FValType . eq F1 F2 => < i32 > #bool(F1 ==Float F2) + rule _:FValType . ne F1 F2 => < i32 > #bool(F1 =/=Float F2) +``` + +### Conversion Operators + +Conversion operators always take a single argument as input and cast it to another type. +The operators are further broken down into subsorts for their input type, for simpler type-checking. + +```k + syntax Val ::= ValType "." CvtOp Number [klabel(numberCvtOp), function] + // ----------------------------------------------------------------------- +``` + +There are 7 conversion operators: `wrap`, `extend`, `trunc`, `convert`, `demote` ,`promote` and `reinterpret`. + +- `wrap` takes an `i64` value, cuts of the 32 most significant bits and returns an `i32` value. + +```k + rule i32 . wrap_i64 I => #chop(< i32 > I) +``` + +- `extend` takes an `i32` type value, converts its type into the `i64` and returns the result. + +```k + rule i64 . extend_i32_u I:Int => < i64 > I + rule i64 . extend_i32_s I:Int => < i64 > #unsigned(i64, #signed(i32, I)) +``` + +- `convert` takes an `int` type value and convert it to the nearest `float` type value. + +```k + rule FTYPE . convert_i32_s I:Int => #round( FTYPE , #signed(i32, I) ) + rule FTYPE . convert_i32_u I:Int => #round( FTYPE , I ) + + rule FTYPE . convert_i64_s I:Int => #round( FTYPE , #signed(i64, I) ) + rule FTYPE . convert_i64_u I:Int => #round( FTYPE , I ) +``` + +- `demote` turns an `f64` type value to the nearest `f32` type value. +- `promote` turns an `f32` type value to the nearest `f64` type value: + +```k + rule f64 . promote_f32 F => #round( f64 , F ) + + rule f32 . demote_f64 F => #round( f32 , F ) +``` + +- `trunc` first truncates a float value, then convert the result to the nearest ineger value. + +```k + rule ITYPE . trunc_f32_s F => undefined + requires #isInfinityOrNaN (F) orBool (Float2Int(truncFloat(F)) >=Int #pow1(ITYPE)) orBool (0 -Int Float2Int(truncFloat(F)) >Int #pow1 (ITYPE)) + rule ITYPE . trunc_f32_u F => undefined + requires #isInfinityOrNaN (F) orBool (Float2Int(truncFloat(F)) >=Int #pow (ITYPE)) orBool (Float2Int(truncFloat(F)) #unsigned(ITYPE, Float2Int(truncFloat(F))) + requires notBool (#isInfinityOrNaN (F) orBool (Float2Int(truncFloat(F)) >=Int #pow1(ITYPE)) orBool (0 -Int Float2Int(truncFloat(F)) >Int #pow1 (ITYPE))) + rule ITYPE . trunc_f32_u F => Float2Int(truncFloat(F)) + requires notBool (#isInfinityOrNaN (F) orBool (Float2Int(truncFloat(F)) >=Int #pow (ITYPE)) orBool (Float2Int(truncFloat(F)) undefined + requires #isInfinityOrNaN (F) orBool (Float2Int(truncFloat(F)) >=Int #pow1(ITYPE)) orBool (0 -Int Float2Int(truncFloat(F)) >Int #pow1 (ITYPE)) + rule ITYPE . trunc_f64_u F => undefined + requires #isInfinityOrNaN (F) orBool (Float2Int(truncFloat(F)) >=Int #pow (ITYPE)) orBool (Float2Int(truncFloat(F)) #unsigned(ITYPE, Float2Int(truncFloat(F))) + requires notBool (#isInfinityOrNaN (F) orBool (Float2Int(truncFloat(F)) >=Int #pow1(ITYPE)) orBool (0 -Int Float2Int(truncFloat(F)) >Int #pow1 (ITYPE))) + rule ITYPE . trunc_f64_u F => Float2Int(truncFloat(F)) + requires notBool (#isInfinityOrNaN (F) orBool (Float2Int(truncFloat(F)) >=Int #pow (ITYPE)) orBool (Float2Int(truncFloat(F)) = 20.0.2`, `poetry >= 1.3.2`. + +```bash +make build +pip install dist/*.whl +``` + + +## For Developers + +Use `make` to run common tasks (see the [Makefile](Makefile) for a complete list of available targets). + +* `make build`: Build wheel +* `make check`: Check code style +* `make format`: Format code +* `make test-unit`: Run unit tests + +For interactive use, spawn a shell with `poetry shell` (after `poetry install`), then run an interpreter. diff --git a/docs/runtimeverification-wasm-semantics/pykwasm/flake.nix b/docs/runtimeverification-wasm-semantics/pykwasm/flake.nix new file mode 100644 index 000000000..4b892fbca --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/pykwasm/flake.nix @@ -0,0 +1,41 @@ +{ + description = "pykwasm - "; + inputs = { + nixpkgs.url = "nixpkgs/nixos-22.05"; + flake-utils.url = "github:numtide/flake-utils"; + poetry2nix.url = "github:nix-community/poetry2nix"; + }; + outputs = { self, nixpkgs, flake-utils, poetry2nix }: + let + allOverlays = [ + poetry2nix.overlay + (final: prev: { + pykwasm = prev.poetry2nix.mkPoetryApplication { + python = prev.python39; + projectDir = ./.; + groups = []; + # We remove `dev` from `checkGroups`, so that poetry2nix does not try to resolve dev dependencies. + checkGroups = []; + }; + }) + ]; + in flake-utils.lib.eachSystem [ + "x86_64-linux" + "x86_64-darwin" + "aarch64-linux" + "aarch64-darwin" + ] (system: + let + pkgs = import nixpkgs { + inherit system; + overlays = allOverlays; + }; + in { + packages = rec { + inherit (pkgs) pykwasm; + default = pykwasm; + }; + }) // { + overlay = nixpkgs.lib.composeManyExtensions allOverlays; + }; +} diff --git a/docs/runtimeverification-wasm-semantics/pykwasm/poetry.lock b/docs/runtimeverification-wasm-semantics/pykwasm/poetry.lock new file mode 100644 index 000000000..c9c7bebd7 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/pykwasm/poetry.lock @@ -0,0 +1,1075 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "attrs" +version = "22.2.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, + {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] +tests = ["attrs[tests-no-zope]", "zope.interface"] +tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] + +[[package]] +name = "autoflake" +version = "2.0.2" +description = "Removes unused imports and unused variables" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "autoflake-2.0.2-py3-none-any.whl", hash = "sha256:a82d8efdcbbb7129a8a23238c529fb9d9919c562e26bb7963ea6890fbfff7d02"}, + {file = "autoflake-2.0.2.tar.gz", hash = "sha256:e0164421ff13f805f08a023e249d84200bd00463d213b490906bfefa67e83830"}, +] + +[package.dependencies] +pyflakes = ">=3.0.0" +tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} + +[[package]] +name = "black" +version = "23.3.0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, + {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, + {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, + {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, + {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, + {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, + {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, + {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, + {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, + {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, + {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, + {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, + {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, + {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "cmd2" +version = "2.4.3" +description = "cmd2 - quickly build feature-rich and user-friendly interactive command line applications in Python" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "cmd2-2.4.3-py3-none-any.whl", hash = "sha256:f1988ff2fff0ed812a2d25218a081b0fa0108d45b48ba2a9272bb98091b654e6"}, + {file = "cmd2-2.4.3.tar.gz", hash = "sha256:71873c11f72bd19e2b1db578214716f0d4f7c8fa250093c601265a9a717dee52"}, +] + +[package.dependencies] +attrs = ">=16.3.0" +pyperclip = ">=1.6" +pyreadline3 = {version = "*", markers = "sys_platform == \"win32\""} +wcwidth = ">=0.1.7" + +[package.extras] +dev = ["codecov", "doc8", "flake8", "invoke", "mypy", "nox", "pytest (>=4.6)", "pytest-cov", "pytest-mock", "sphinx", "sphinx-autobuild", "sphinx-rtd-theme", "twine (>=1.11)"] +test = ["codecov", "coverage", "gnureadline", "pytest (>=4.6)", "pytest-cov", "pytest-mock"] +validate = ["flake8", "mypy", "types-pkg-resources"] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coloredlogs" +version = "15.0.1" +description = "Colored terminal output for Python's logging module" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"}, + {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"}, +] + +[package.dependencies] +humanfriendly = ">=9.1" + +[package.extras] +cron = ["capturer (>=2.4)"] + +[[package]] +name = "coverage" +version = "7.2.3" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "coverage-7.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e58c0d41d336569d63d1b113bd573db8363bc4146f39444125b7f8060e4e04f5"}, + {file = "coverage-7.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:344e714bd0fe921fc72d97404ebbdbf9127bac0ca1ff66d7b79efc143cf7c0c4"}, + {file = "coverage-7.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974bc90d6f6c1e59ceb1516ab00cf1cdfbb2e555795d49fa9571d611f449bcb2"}, + {file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0743b0035d4b0e32bc1df5de70fba3059662ace5b9a2a86a9f894cfe66569013"}, + {file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d0391fb4cfc171ce40437f67eb050a340fdbd0f9f49d6353a387f1b7f9dd4fa"}, + {file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a42e1eff0ca9a7cb7dc9ecda41dfc7cbc17cb1d02117214be0561bd1134772b"}, + {file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:be19931a8dcbe6ab464f3339966856996b12a00f9fe53f346ab3be872d03e257"}, + {file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72fcae5bcac3333a4cf3b8f34eec99cea1187acd55af723bcbd559adfdcb5535"}, + {file = "coverage-7.2.3-cp310-cp310-win32.whl", hash = "sha256:aeae2aa38395b18106e552833f2a50c27ea0000122bde421c31d11ed7e6f9c91"}, + {file = "coverage-7.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:83957d349838a636e768251c7e9979e899a569794b44c3728eaebd11d848e58e"}, + {file = "coverage-7.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dfd393094cd82ceb9b40df4c77976015a314b267d498268a076e940fe7be6b79"}, + {file = "coverage-7.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:182eb9ac3f2b4874a1f41b78b87db20b66da6b9cdc32737fbbf4fea0c35b23fc"}, + {file = "coverage-7.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bb1e77a9a311346294621be905ea8a2c30d3ad371fc15bb72e98bfcfae532df"}, + {file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca0f34363e2634deffd390a0fef1aa99168ae9ed2af01af4a1f5865e362f8623"}, + {file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55416d7385774285b6e2a5feca0af9652f7f444a4fa3d29d8ab052fafef9d00d"}, + {file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:06ddd9c0249a0546997fdda5a30fbcb40f23926df0a874a60a8a185bc3a87d93"}, + {file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fff5aaa6becf2c6a1699ae6a39e2e6fb0672c2d42eca8eb0cafa91cf2e9bd312"}, + {file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ea53151d87c52e98133eb8ac78f1206498c015849662ca8dc246255265d9c3c4"}, + {file = "coverage-7.2.3-cp311-cp311-win32.whl", hash = "sha256:8f6c930fd70d91ddee53194e93029e3ef2aabe26725aa3c2753df057e296b925"}, + {file = "coverage-7.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:fa546d66639d69aa967bf08156eb8c9d0cd6f6de84be9e8c9819f52ad499c910"}, + {file = "coverage-7.2.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2317d5ed777bf5a033e83d4f1389fd4ef045763141d8f10eb09a7035cee774c"}, + {file = "coverage-7.2.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be9824c1c874b73b96288c6d3de793bf7f3a597770205068c6163ea1f326e8b9"}, + {file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c3b2803e730dc2797a017335827e9da6da0e84c745ce0f552e66400abdfb9a1"}, + {file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f69770f5ca1994cb32c38965e95f57504d3aea96b6c024624fdd5bb1aa494a1"}, + {file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1127b16220f7bfb3f1049ed4a62d26d81970a723544e8252db0efde853268e21"}, + {file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:aa784405f0c640940595fa0f14064d8e84aff0b0f762fa18393e2760a2cf5841"}, + {file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3146b8e16fa60427e03884301bf8209221f5761ac754ee6b267642a2fd354c48"}, + {file = "coverage-7.2.3-cp37-cp37m-win32.whl", hash = "sha256:1fd78b911aea9cec3b7e1e2622c8018d51c0d2bbcf8faaf53c2497eb114911c1"}, + {file = "coverage-7.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f3736a5d34e091b0a611964c6262fd68ca4363df56185902528f0b75dbb9c1f"}, + {file = "coverage-7.2.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:981b4df72c93e3bc04478153df516d385317628bd9c10be699c93c26ddcca8ab"}, + {file = "coverage-7.2.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0045f8f23a5fb30b2eb3b8a83664d8dc4fb58faddf8155d7109166adb9f2040"}, + {file = "coverage-7.2.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f760073fcf8f3d6933178d67754f4f2d4e924e321f4bb0dcef0424ca0215eba1"}, + {file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c86bd45d1659b1ae3d0ba1909326b03598affbc9ed71520e0ff8c31a993ad911"}, + {file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:172db976ae6327ed4728e2507daf8a4de73c7cc89796483e0a9198fd2e47b462"}, + {file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2a3a6146fe9319926e1d477842ca2a63fe99af5ae690b1f5c11e6af074a6b5c"}, + {file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f649dd53833b495c3ebd04d6eec58479454a1784987af8afb77540d6c1767abd"}, + {file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c4ed4e9f3b123aa403ab424430b426a1992e6f4c8fd3cb56ea520446e04d152"}, + {file = "coverage-7.2.3-cp38-cp38-win32.whl", hash = "sha256:eb0edc3ce9760d2f21637766c3aa04822030e7451981ce569a1b3456b7053f22"}, + {file = "coverage-7.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:63cdeaac4ae85a179a8d6bc09b77b564c096250d759eed343a89d91bce8b6367"}, + {file = "coverage-7.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:20d1a2a76bb4eb00e4d36b9699f9b7aba93271c9c29220ad4c6a9581a0320235"}, + {file = "coverage-7.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ea748802cc0de4de92ef8244dd84ffd793bd2e7be784cd8394d557a3c751e21"}, + {file = "coverage-7.2.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b154aba06df42e4b96fc915512ab39595105f6c483991287021ed95776d934"}, + {file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd214917cabdd6f673a29d708574e9fbdb892cb77eb426d0eae3490d95ca7859"}, + {file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2e58e45fe53fab81f85474e5d4d226eeab0f27b45aa062856c89389da2f0d9"}, + {file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:87ecc7c9a1a9f912e306997ffee020297ccb5ea388421fe62a2a02747e4d5539"}, + {file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:387065e420aed3c71b61af7e82c7b6bc1c592f7e3c7a66e9f78dd178699da4fe"}, + {file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ea3f5bc91d7d457da7d48c7a732beaf79d0c8131df3ab278e6bba6297e23c6c4"}, + {file = "coverage-7.2.3-cp39-cp39-win32.whl", hash = "sha256:ae7863a1d8db6a014b6f2ff9c1582ab1aad55a6d25bac19710a8df68921b6e30"}, + {file = "coverage-7.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:3f04becd4fcda03c0160d0da9c8f0c246bc78f2f7af0feea1ec0930e7c93fa4a"}, + {file = "coverage-7.2.3-pp37.pp38.pp39-none-any.whl", hash = "sha256:965ee3e782c7892befc25575fa171b521d33798132692df428a09efacaffe8d0"}, + {file = "coverage-7.2.3.tar.gz", hash = "sha256:d298c2815fa4891edd9abe5ad6e6cb4207104c7dd9fd13aea3fdebf6f9b91259"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "cytoolz" +version = "0.12.1" +description = "Cython implementation of Toolz: High performance functional utilities" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "cytoolz-0.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c59bb4ca88e1c69931468bf21f91c8f64d8bf1999eb163b7a2df336f60c304a"}, + {file = "cytoolz-0.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d700e011156ff112966c6d77faaae125fcaf538f4cec2b9ce8957de82858f0f"}, + {file = "cytoolz-0.12.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23c3f57c48eb939d2986eba4aeaeedf930ebf94d58c91a42d4e0fc45ed5427dc"}, + {file = "cytoolz-0.12.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25ff13c468c06da9ef26651dc389e7e8bb7af548f8c1dfb96305f57f18d398a8"}, + {file = "cytoolz-0.12.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a734511144309ea6e105406633affb74e303a3df07d8a3954f9b01946e27ecb1"}, + {file = "cytoolz-0.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48bc2f30d1b2646d675bb8e7778ab59379bf9edc59fe06fb0e7f85ba1271bf44"}, + {file = "cytoolz-0.12.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30936ae8fa68b6a1ac8ad6c4bacb5a8a00d51bc6c89f9614a1557b0105d09f8a"}, + {file = "cytoolz-0.12.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:efd1b2da3ee577fcfa723a214f73186aef9674dd5b28242d90443c7a82722b0f"}, + {file = "cytoolz-0.12.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6805b007af3557ee6c20dab491b6e55a8177f5b6845d9e6c653374d540366ba7"}, + {file = "cytoolz-0.12.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a6e63fc67b23830947b51e0a488992e3c904fce825ead565f3904dcf621d05f7"}, + {file = "cytoolz-0.12.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:9e324a94856d88ecf10f34c102d0ded67d7c3cf644153d77e34a29720ce6aa47"}, + {file = "cytoolz-0.12.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:02975e2b1e61e47e9afa311f4c1783d155136fad37c54a1cebfe991c5a0798a1"}, + {file = "cytoolz-0.12.1-cp310-cp310-win32.whl", hash = "sha256:b6569f6038133909cd658dbdcc6fc955f791dc47a7f5b55d2066f742253dcbfe"}, + {file = "cytoolz-0.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:1be368623e46ad3c1ce807e7a436acb119c26001507b31f92ceb21b86e08c386"}, + {file = "cytoolz-0.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:849f461bffa1e7700ccfcb5186df29cd4cdcc9efdb7199cb8b5681dc37045d72"}, + {file = "cytoolz-0.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4284120c978fb7039901bf6e66832cb3e82ac1b2a107512e735bdb04fd5533ed"}, + {file = "cytoolz-0.12.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ec296f01c29c809698eaf677211b6255691295c2b35caab2131e1e7eaadfbac"}, + {file = "cytoolz-0.12.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37c53f456a1c84566a7d911eec57c4c6280b915ab0600e7671582793cc2769fe"}, + {file = "cytoolz-0.12.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b6761791973b1e839b8309d5853b40eeb413368e31beaf5f2b6ed44c6fc7cf0"}, + {file = "cytoolz-0.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff478682e8ee6dbaa37201bb71bf4a6eee744006ab000e8f5cea05066fc7c845"}, + {file = "cytoolz-0.12.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:867bebe6be30ee36a836f9b835790762a74f46be8cc339ea57b68dcecdbc1133"}, + {file = "cytoolz-0.12.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7e903df991f0957e2b271a37bb25d28e0d260c52825ae67507d15ca55a935961"}, + {file = "cytoolz-0.12.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e797c4afb1b7962d3205b1959e1051f7e6bfbba29da44042a9efc2391f1feb38"}, + {file = "cytoolz-0.12.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b8eceaa12b7f152b046b67cb053ec2b5b00f73593983de69bc5e63a8aca4a7a8"}, + {file = "cytoolz-0.12.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b575393dd431b8e211de35bd593d831dac870172b16e2b7934f3566b8fc89377"}, + {file = "cytoolz-0.12.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3032c0ba42dee5836d6b57a72a569b65df2c29e8ed266cb900d569003cf933a9"}, + {file = "cytoolz-0.12.1-cp311-cp311-win32.whl", hash = "sha256:c576bd63495150385b8d05eaae775387f378be2fd9805d3ffb4d17c87271fbad"}, + {file = "cytoolz-0.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:421b224dc4157a0d66625acb5798cf50858cfa06a5232d39a8bd6cf1fa88aca3"}, + {file = "cytoolz-0.12.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:be5a454a95797343d0fb1ed02caecae73a023b1393c112951c84f17ec9f4076c"}, + {file = "cytoolz-0.12.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:061387aa39b9c1576c25d0c59142513c09e77a2a07bd5d6211a43c7a758b6f45"}, + {file = "cytoolz-0.12.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14f4dbc3f0ec8f6fc68865489af21dcf042ff007d2737c27bfd73296f15db544"}, + {file = "cytoolz-0.12.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a816bff6bf424753e1ac2441902ceaf37ae6718b745a53f6aa1a60c617fb4f5f"}, + {file = "cytoolz-0.12.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:633f19d1990b1cf9c67dce9c28bf8b5a18e42785d15548607a100e1236384d5d"}, + {file = "cytoolz-0.12.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fa7009c843667868aa8bdb3d68e5ef3d6356dd418b17ed5ca4e1340e82483a5"}, + {file = "cytoolz-0.12.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:1c29dd04e282ddfd45b457e3551075beec9128aa9271245e58ce924bf6e055f8"}, + {file = "cytoolz-0.12.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cd35c0be4c46274129dd1678bb911dd4e93d23968b26f4e39cd55bc7cb3b1bac"}, + {file = "cytoolz-0.12.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5158ae6d8dd112d003f677039a3613ca7d2592bfe35d7accf23684edb961fc26"}, + {file = "cytoolz-0.12.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:7eb9e6fa8a82c3d2f519f7d3942898a97792e3895569e9501b9431048289b82f"}, + {file = "cytoolz-0.12.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ac6784cc43aec51a86cf9058a2a343084f8cf46a9281bea5762bfa608127c53b"}, + {file = "cytoolz-0.12.1-cp36-cp36m-win32.whl", hash = "sha256:794cce219bbcb2f36ca220f27d5afd64eaa854e04901bd6f240be156a578b607"}, + {file = "cytoolz-0.12.1-cp36-cp36m-win_amd64.whl", hash = "sha256:695dd8231e4f1bfb9a2363775a6e4e56ad9d2058058f817203a49614f4bfe33b"}, + {file = "cytoolz-0.12.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1bd8017ef0da935a20106272c5f5ff6b1114add1ccb09cfed1ff7ec5cc01c6d"}, + {file = "cytoolz-0.12.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56e1ebf6eb4438b8c45cbe7e7b22fc65df0c9efa97a70d3bf2f51e08b19756a5"}, + {file = "cytoolz-0.12.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:816c2038008ebf50d81171ddfae377f1af9e71d504ec609469dcb0906bfcf2ae"}, + {file = "cytoolz-0.12.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bebe58f7a160db7838eb70990c704db4bdc2d58bd364290fd69be0587be8bac"}, + {file = "cytoolz-0.12.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a72440305f634604827f96810e4469877b89f5c060d6852267650a49b0e3768c"}, + {file = "cytoolz-0.12.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b46ebc463bb45f278a2b94e630061c26e10077cb68d4c93583d8f4199699a5ef"}, + {file = "cytoolz-0.12.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e75e287787e6adafed9d8c3d3e7647c0b5eb460221f9f92d7dfe48b45ba77c0d"}, + {file = "cytoolz-0.12.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:03ab22c9aeb1535f8647d23b6520b0c3d41aaa18d04ef42b352dde1931f2e2b1"}, + {file = "cytoolz-0.12.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b2ac288f27a2689d9e39f4cf4df5437a8eb038eaae515169586c77f9f8fb343a"}, + {file = "cytoolz-0.12.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:97a24c0d0806fcf9a6e75fc18aeb95adc37eb0baf6451f10a2de23ffd815329d"}, + {file = "cytoolz-0.12.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:42c9e5cd2a48a257b1f2402334b48122501f249b8dcf77082f569f2680f185eb"}, + {file = "cytoolz-0.12.1-cp37-cp37m-win32.whl", hash = "sha256:35fae4eaa0eaf9072a5fe2d244a79e65baae4e5ddbe9cc629c5037af800213a2"}, + {file = "cytoolz-0.12.1-cp37-cp37m-win_amd64.whl", hash = "sha256:5af43ca7026ead3dd08b261e4f7163cd2cf3ceaa74fa5a81f7b7ea5d445e41d6"}, + {file = "cytoolz-0.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fcc378fa97f02fbcef090b3611305425d72bd1c0afdd13ef4a82dc67d40638b6"}, + {file = "cytoolz-0.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc3645cf6b9246cb8e179db2803e4f0d148211d2a2cf22d5c9b5219111cd91a0"}, + {file = "cytoolz-0.12.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b245b824f4705aef0e4a03fafef3ad6cb59ef43cc564cdbf683ee28dfc11ad5"}, + {file = "cytoolz-0.12.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c1964dcb5f250fd13fac210944b20810d61ef4094a17fbbe502ab7a7eaeeace7"}, + {file = "cytoolz-0.12.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7194a22a4a24f3561cb6ad1cca9c9b2f2cf34cc8d4bce6d6a24c80960323fa8"}, + {file = "cytoolz-0.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1c5434db53f3a94a37ad8aedb231901e001995d899af6ed1165f3d27fa04a6a"}, + {file = "cytoolz-0.12.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b30cd083ef8af4ba66d9fe5cc75c653ede3f2655f97a032db1a14cc8a006719c"}, + {file = "cytoolz-0.12.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bef934bd3e024d512c6c0ad1c66eb173f61d9ccb4dbca8d75f727a5604f7c2f6"}, + {file = "cytoolz-0.12.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:37320669c364f7d370392af33cc1034b4563da66c22cd3261e3530f4d30dbe4b"}, + {file = "cytoolz-0.12.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3cb95d23defb2322cddf70efb4af6dac191d95edaa343e8c1f58f1afa4f92ecd"}, + {file = "cytoolz-0.12.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ac5895d5f78dbd8646fe37266655ba4995f9cfec38a86595282fee69e41787da"}, + {file = "cytoolz-0.12.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:499af2aff04f65b4c23de1df08e1d1484a93b23ddaaa0163e44b5070b68356eb"}, + {file = "cytoolz-0.12.1-cp38-cp38-win32.whl", hash = "sha256:aa61e3da751a2dfe95aeca603f3ef510071a136ba9905f61ae6cb5d0696271ad"}, + {file = "cytoolz-0.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:f5b43ce952a5a31441556c55f5f5f5a8e62c28581a0ff2a2c31c04ef992d73bd"}, + {file = "cytoolz-0.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8b8f88251b84b3877254cdd59c86a1dc6b2b39a03c6c9c067d344ef879562e0"}, + {file = "cytoolz-0.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d72415b0110f7958dd3a5ee98a70166f47bd42ede85e3535669c794d06f57406"}, + {file = "cytoolz-0.12.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8101ab6de5aa0b26a2b5032bc488d430010c91863e701812d65836b03a12f61"}, + {file = "cytoolz-0.12.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2eed428b5e68c28abf2c71195e799850e040d67a27c05f7785319c611665b86a"}, + {file = "cytoolz-0.12.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59641eb1f41cb688b3cb2f98c9003c493a5024325f76b5c02333d08dd972127c"}, + {file = "cytoolz-0.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a48940ff0449ffcf690310bf9228bb57885f7571406ed2fe05c98e299987195"}, + {file = "cytoolz-0.12.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bae431a5985cdb2014be09d37206c288e0d063940cf9539e9769bd2ec26b220"}, + {file = "cytoolz-0.12.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cb8b10405960a8e6801a4702af98ea640130ec6ecfc1208195762de3f5503ba9"}, + {file = "cytoolz-0.12.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3c9a16a5b4f54d5c0a131f56b0ca65998a9a74958b5b36840c280edba4f8b907"}, + {file = "cytoolz-0.12.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:49911cb533c96d275e31e7eaeb0742ac3f7afe386a1d8c40937814d75039a0f7"}, + {file = "cytoolz-0.12.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:dbae37d48ef5a0ab90cfaf2b9312d96f034b1c828208a9cbe25377a1b19ba129"}, + {file = "cytoolz-0.12.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c34e69be4429633fc614febe3127fa03aa418a1abb9252f29d9ba5b3394573a5"}, + {file = "cytoolz-0.12.1-cp39-cp39-win32.whl", hash = "sha256:0d474dacbafbdbb44c7de986bbf71ff56ae62df0d52ab3b6fa966784dc88737a"}, + {file = "cytoolz-0.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:3d6d0b0075731832343eb88229cea4bf39e96f3fc7acbc449aadbdfec2842703"}, + {file = "cytoolz-0.12.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8506d1863f30d26f577c4ed59d2cfd03d2f39569f9cbaa02a764a9de73d312d5"}, + {file = "cytoolz-0.12.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a1eae39656a1685e8b3f433eecfd72015ce5c1d7519e9c8f9402153c68331bb"}, + {file = "cytoolz-0.12.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a0055943074c6c85b77fcc3f42f7c54010a3478daa2ed9d6243d0411c84a4d3"}, + {file = "cytoolz-0.12.1-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8a7a325b8fe885a6dd91093616c703134f2dacbd869bc519970df3849c2a15b"}, + {file = "cytoolz-0.12.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:7b60caf0fa5f1b49f1062f7dc0f66c7b23e2736bad50fa8296bfb845995e3051"}, + {file = "cytoolz-0.12.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:980e7eb7205e01816a92f3290cfc80507957e64656b9271a0dfebb85fe3718c0"}, + {file = "cytoolz-0.12.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06d38a40fe153f23cda0e823413fe9d9ebee89dd461827285316eff929fb121e"}, + {file = "cytoolz-0.12.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d540e9c34a61b53b6a374ea108794a48388178f7889d772e364cdbd6df37774c"}, + {file = "cytoolz-0.12.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:117871f036926e42d3abcee587eafa9dc7383f1064ac53a806d33e76604de311"}, + {file = "cytoolz-0.12.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:31131b54a0c72efc0eb432dc66df546c6a54f2a7d396c9a34cf65ac1c26b1df8"}, + {file = "cytoolz-0.12.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4534cbfad73cdb1a6dad495530d4186d57d73089c01e9cb0558caab50e46cb3b"}, + {file = "cytoolz-0.12.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50db41e875e36aec11881b8b12bc69c6f4836b7dd9e88a9e5bbf26c2cb3ba6cd"}, + {file = "cytoolz-0.12.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6716855f9c669c9e25a185d88e0f169839bf8553d16496796325acd114607c11"}, + {file = "cytoolz-0.12.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f32452e833f0605b871626e6c61b71b0cba24233aad0e04accc3240497d4995"}, + {file = "cytoolz-0.12.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ba74c239fc6cb6e962eabc420967c7565f3f363b776c89b3df5234caecf1f463"}, + {file = "cytoolz-0.12.1.tar.gz", hash = "sha256:fc33909397481c90de3cec831bfb88d97e220dc91939d996920202f184b4648e"}, +] + +[package.dependencies] +toolz = ">=0.8.0" + +[package.extras] +cython = ["cython"] + +[[package]] +name = "exceptiongroup" +version = "1.1.1" +description = "Backport of PEP 654 (exception groups)" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, + {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "execnet" +version = "1.9.0" +description = "execnet: rapid multi-Python deployment" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"}, + {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, +] + +[package.extras] +testing = ["pre-commit"] + +[[package]] +name = "filelock" +version = "3.11.0" +description = "A platform independent file lock." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "filelock-3.11.0-py3-none-any.whl", hash = "sha256:f08a52314748335c6460fc8fe40cd5638b85001225db78c2aa01c8c0db83b318"}, + {file = "filelock-3.11.0.tar.gz", hash = "sha256:3618c0da67adcc0506b015fd11ef7faf1b493f0b40d87728e19986b536890c37"}, +] + +[package.extras] +docs = ["furo (>=2023.3.27)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.2.2)", "diff-cover (>=7.5)", "pytest (>=7.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "flake8" +version = "6.0.0" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-6.0.0-py2.py3-none-any.whl", hash = "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7"}, + {file = "flake8-6.0.0.tar.gz", hash = "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.10.0,<2.11.0" +pyflakes = ">=3.0.0,<3.1.0" + +[[package]] +name = "flake8-bugbear" +version = "23.3.23" +description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." +category = "dev" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-bugbear-23.3.23.tar.gz", hash = "sha256:ea565bdb87b96b56dc499edd6cc3ba7f695373d902a5f56c989b74fad7c7719d"}, + {file = "flake8_bugbear-23.3.23-py3-none-any.whl", hash = "sha256:8a218d13abd6904800970381057ce8e72cde8eea743240c4ef3ede4dd0bc9cfb"}, +] + +[package.dependencies] +attrs = ">=19.2.0" +flake8 = ">=6.0.0" + +[package.extras] +dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit", "pytest", "tox"] + +[[package]] +name = "flake8-comprehensions" +version = "3.12.0" +description = "A flake8 plugin to help you write better list/set/dict comprehensions." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "flake8_comprehensions-3.12.0-py3-none-any.whl", hash = "sha256:013234637ec7dfcb7cd2900578fb53c512f81db909cefe371c019232695c362d"}, + {file = "flake8_comprehensions-3.12.0.tar.gz", hash = "sha256:419ef1a6e8de929203791a5e8ff5e3906caeba13eb3290eebdbf88a9078d502e"}, +] + +[package.dependencies] +flake8 = ">=3.0,<3.2.0 || >3.2.0" + +[[package]] +name = "flake8-quotes" +version = "3.3.2" +description = "Flake8 lint for quotes." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "flake8-quotes-3.3.2.tar.gz", hash = "sha256:6e26892b632dacba517bf27219c459a8396dcfac0f5e8204904c5a4ba9b480e1"}, +] + +[package.dependencies] +flake8 = "*" + +[[package]] +name = "graphviz" +version = "0.20.1" +description = "Simple Python interface for Graphviz" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "graphviz-0.20.1-py3-none-any.whl", hash = "sha256:587c58a223b51611c0cf461132da386edd896a029524ca61a1462b880bf97977"}, + {file = "graphviz-0.20.1.zip", hash = "sha256:8c58f14adaa3b947daf26c19bc1e98c4e0702cdc31cf99153e6f06904d492bf8"}, +] + +[package.extras] +dev = ["flake8", "pep8-naming", "tox (>=3)", "twine", "wheel"] +docs = ["sphinx (>=5)", "sphinx-autodoc-typehints", "sphinx-rtd-theme"] +test = ["coverage", "mock (>=4)", "pytest (>=7)", "pytest-cov", "pytest-mock (>=3)"] + +[[package]] +name = "humanfriendly" +version = "10.0" +description = "Human friendly output for text interfaces using Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"}, + {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, +] + +[package.dependencies] +pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""} + +[[package]] +name = "importlib-metadata" +version = "4.13.0" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-4.13.0-py3-none-any.whl", hash = "sha256:8a8a81bcf996e74fee46f0d16bd3eaa382a7eb20fd82445c3ad11f4090334116"}, + {file = "importlib_metadata-4.13.0.tar.gz", hash = "sha256:dd0173e8f150d6815e098fd354f6414b0f079af4644ddfe90c71e2fc6174346d"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +perf = ["ipython"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "isort" +version = "5.12.0" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, + {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, +] + +[package.extras] +colors = ["colorama (>=0.4.3)"] +pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + +[[package]] +name = "markdown-it-py" +version = "2.2.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, + {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "mypy" +version = "1.2.0" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mypy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:701189408b460a2ff42b984e6bd45c3f41f0ac9f5f58b8873bbedc511900086d"}, + {file = "mypy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fe91be1c51c90e2afe6827601ca14353bbf3953f343c2129fa1e247d55fd95ba"}, + {file = "mypy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d26b513225ffd3eacece727f4387bdce6469192ef029ca9dd469940158bc89e"}, + {file = "mypy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3a2d219775a120581a0ae8ca392b31f238d452729adbcb6892fa89688cb8306a"}, + {file = "mypy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:2e93a8a553e0394b26c4ca683923b85a69f7ccdc0139e6acd1354cc884fe0128"}, + {file = "mypy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3efde4af6f2d3ccf58ae825495dbb8d74abd6d176ee686ce2ab19bd025273f41"}, + {file = "mypy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:695c45cea7e8abb6f088a34a6034b1d273122e5530aeebb9c09626cea6dca4cb"}, + {file = "mypy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0e9464a0af6715852267bf29c9553e4555b61f5904a4fc538547a4d67617937"}, + {file = "mypy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8293a216e902ac12779eb7a08f2bc39ec6c878d7c6025aa59464e0c4c16f7eb9"}, + {file = "mypy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:f46af8d162f3d470d8ffc997aaf7a269996d205f9d746124a179d3abe05ac602"}, + {file = "mypy-1.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:031fc69c9a7e12bcc5660b74122ed84b3f1c505e762cc4296884096c6d8ee140"}, + {file = "mypy-1.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:390bc685ec209ada4e9d35068ac6988c60160b2b703072d2850457b62499e336"}, + {file = "mypy-1.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4b41412df69ec06ab141808d12e0bf2823717b1c363bd77b4c0820feaa37249e"}, + {file = "mypy-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4e4a682b3f2489d218751981639cffc4e281d548f9d517addfd5a2917ac78119"}, + {file = "mypy-1.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a197ad3a774f8e74f21e428f0de7f60ad26a8d23437b69638aac2764d1e06a6a"}, + {file = "mypy-1.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c9a084bce1061e55cdc0493a2ad890375af359c766b8ac311ac8120d3a472950"}, + {file = "mypy-1.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaeaa0888b7f3ccb7bcd40b50497ca30923dba14f385bde4af78fac713d6d6f6"}, + {file = "mypy-1.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bea55fc25b96c53affab852ad94bf111a3083bc1d8b0c76a61dd101d8a388cf5"}, + {file = "mypy-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:4c8d8c6b80aa4a1689f2a179d31d86ae1367ea4a12855cc13aa3ba24bb36b2d8"}, + {file = "mypy-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:70894c5345bea98321a2fe84df35f43ee7bb0feec117a71420c60459fc3e1eed"}, + {file = "mypy-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4a99fe1768925e4a139aace8f3fb66db3576ee1c30b9c0f70f744ead7e329c9f"}, + {file = "mypy-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:023fe9e618182ca6317ae89833ba422c411469156b690fde6a315ad10695a521"}, + {file = "mypy-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4d19f1a239d59f10fdc31263d48b7937c585810288376671eaf75380b074f238"}, + {file = "mypy-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:2de7babe398cb7a85ac7f1fd5c42f396c215ab3eff731b4d761d68d0f6a80f48"}, + {file = "mypy-1.2.0-py3-none-any.whl", hash = "sha256:d8e9187bfcd5ffedbe87403195e1fc340189a68463903c39e2b63307c9fa0394"}, + {file = "mypy-1.2.0.tar.gz", hash = "sha256:f70a40410d774ae23fcb4afbbeca652905a04de7948eaf0b1789c8d1426b72d1"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "nanoid" +version = "2.0.0" +description = "A tiny, secure, URL-friendly, unique string ID generator for Python" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "nanoid-2.0.0-py3-none-any.whl", hash = "sha256:90aefa650e328cffb0893bbd4c236cfd44c48bc1f2d0b525ecc53c3187b653bb"}, + {file = "nanoid-2.0.0.tar.gz", hash = "sha256:5a80cad5e9c6e9ae3a41fa2fb34ae189f7cb420b2a5d8f82bd9d23466e4efa68"}, +] + +[[package]] +name = "numpy" +version = "1.24.2" +description = "Fundamental package for array computing in Python" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "numpy-1.24.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eef70b4fc1e872ebddc38cddacc87c19a3709c0e3e5d20bf3954c147b1dd941d"}, + {file = "numpy-1.24.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8d2859428712785e8a8b7d2b3ef0a1d1565892367b32f915c4a4df44d0e64f5"}, + {file = "numpy-1.24.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6524630f71631be2dabe0c541e7675db82651eb998496bbe16bc4f77f0772253"}, + {file = "numpy-1.24.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a51725a815a6188c662fb66fb32077709a9ca38053f0274640293a14fdd22978"}, + {file = "numpy-1.24.2-cp310-cp310-win32.whl", hash = "sha256:2620e8592136e073bd12ee4536149380695fbe9ebeae845b81237f986479ffc9"}, + {file = "numpy-1.24.2-cp310-cp310-win_amd64.whl", hash = "sha256:97cf27e51fa078078c649a51d7ade3c92d9e709ba2bfb97493007103c741f1d0"}, + {file = "numpy-1.24.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7de8fdde0003f4294655aa5d5f0a89c26b9f22c0a58790c38fae1ed392d44a5a"}, + {file = "numpy-1.24.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4173bde9fa2a005c2c6e2ea8ac1618e2ed2c1c6ec8a7657237854d42094123a0"}, + {file = "numpy-1.24.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cecaed30dc14123020f77b03601559fff3e6cd0c048f8b5289f4eeabb0eb281"}, + {file = "numpy-1.24.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a23f8440561a633204a67fb44617ce2a299beecf3295f0d13c495518908e910"}, + {file = "numpy-1.24.2-cp311-cp311-win32.whl", hash = "sha256:e428c4fbfa085f947b536706a2fc349245d7baa8334f0c5723c56a10595f9b95"}, + {file = "numpy-1.24.2-cp311-cp311-win_amd64.whl", hash = "sha256:557d42778a6869c2162deb40ad82612645e21d79e11c1dc62c6e82a2220ffb04"}, + {file = "numpy-1.24.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d0a2db9d20117bf523dde15858398e7c0858aadca7c0f088ac0d6edd360e9ad2"}, + {file = "numpy-1.24.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c72a6b2f4af1adfe193f7beb91ddf708ff867a3f977ef2ec53c0ffb8283ab9f5"}, + {file = "numpy-1.24.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29e6bd0ec49a44d7690ecb623a8eac5ab8a923bce0bea6293953992edf3a76a"}, + {file = "numpy-1.24.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2eabd64ddb96a1239791da78fa5f4e1693ae2dadc82a76bc76a14cbb2b966e96"}, + {file = "numpy-1.24.2-cp38-cp38-win32.whl", hash = "sha256:e3ab5d32784e843fc0dd3ab6dcafc67ef806e6b6828dc6af2f689be0eb4d781d"}, + {file = "numpy-1.24.2-cp38-cp38-win_amd64.whl", hash = "sha256:76807b4063f0002c8532cfeac47a3068a69561e9c8715efdad3c642eb27c0756"}, + {file = "numpy-1.24.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4199e7cfc307a778f72d293372736223e39ec9ac096ff0a2e64853b866a8e18a"}, + {file = "numpy-1.24.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:adbdce121896fd3a17a77ab0b0b5eedf05a9834a18699db6829a64e1dfccca7f"}, + {file = "numpy-1.24.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:889b2cc88b837d86eda1b17008ebeb679d82875022200c6e8e4ce6cf549b7acb"}, + {file = "numpy-1.24.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f64bb98ac59b3ea3bf74b02f13836eb2e24e48e0ab0145bbda646295769bd780"}, + {file = "numpy-1.24.2-cp39-cp39-win32.whl", hash = "sha256:63e45511ee4d9d976637d11e6c9864eae50e12dc9598f531c035265991910468"}, + {file = "numpy-1.24.2-cp39-cp39-win_amd64.whl", hash = "sha256:a77d3e1163a7770164404607b7ba3967fb49b24782a6ef85d9b5f54126cc39e5"}, + {file = "numpy-1.24.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:92011118955724465fb6853def593cf397b4a1367495e0b59a7e69d40c4eb71d"}, + {file = "numpy-1.24.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9006288bcf4895917d02583cf3411f98631275bc67cce355a7f39f8c14338fa"}, + {file = "numpy-1.24.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:150947adbdfeceec4e5926d956a06865c1c690f2fd902efede4ca6fe2e657c3f"}, + {file = "numpy-1.24.2.tar.gz", hash = "sha256:003a9f530e880cb2cd177cba1af7220b9aa42def9c4afc2a2fc3ee6be7eb2b22"}, +] + +[[package]] +name = "packaging" +version = "23.1" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] + +[[package]] +name = "pathspec" +version = "0.11.1" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, + {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, +] + +[[package]] +name = "pep8-naming" +version = "0.13.3" +description = "Check PEP-8 naming conventions, plugin for flake8" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pep8-naming-0.13.3.tar.gz", hash = "sha256:1705f046dfcd851378aac3be1cd1551c7c1e5ff363bacad707d43007877fa971"}, + {file = "pep8_naming-0.13.3-py3-none-any.whl", hash = "sha256:1a86b8c71a03337c97181917e2b472f0f5e4ccb06844a0d6f0a33522549e7a80"}, +] + +[package.dependencies] +flake8 = ">=5.0.0" + +[[package]] +name = "platformdirs" +version = "3.2.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.2.0-py3-none-any.whl", hash = "sha256:ebe11c0d7a805086e99506aa331612429a72ca7cd52a1f0d277dc4adc20cb10e"}, + {file = "platformdirs-3.2.0.tar.gz", hash = "sha256:d5b638ca397f25f979350ff789db335903d7ea010ab28903f57b27e1b16c2b08"}, +] + +[package.extras] +docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "psutil" +version = "5.9.4" +description = "Cross-platform lib for process and system monitoring in Python." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"}, + {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"}, + {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"}, + {file = "psutil-5.9.4-cp27-cp27m-win32.whl", hash = "sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad"}, + {file = "psutil-5.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94"}, + {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"}, + {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7"}, + {file = "psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"}, + {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1"}, + {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08"}, + {file = "psutil-5.9.4-cp36-abi3-win32.whl", hash = "sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff"}, + {file = "psutil-5.9.4-cp36-abi3-win_amd64.whl", hash = "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"}, + {file = "psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"}, + {file = "psutil-5.9.4.tar.gz", hash = "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"}, +] + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + +[[package]] +name = "pybind11" +version = "2.10.4" +description = "Seamless operability between C++11 and Python" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pybind11-2.10.4-py3-none-any.whl", hash = "sha256:ec9be0c45061c829648d7e8c98a7d041768b768c934acd15196e0f1943d9a818"}, + {file = "pybind11-2.10.4.tar.gz", hash = "sha256:0bb621d3c45a049aa5923debb87c5c0e2668227905c55ebe8af722608d8ed927"}, +] + +[package.extras] +global = ["pybind11-global (==2.10.4)"] + +[[package]] +name = "pycodestyle" +version = "2.10.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"}, + {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"}, +] + +[[package]] +name = "pyflakes" +version = "3.0.1" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyflakes-3.0.1-py2.py3-none-any.whl", hash = "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf"}, + {file = "pyflakes-3.0.1.tar.gz", hash = "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"}, +] + +[[package]] +name = "pygments" +version = "2.15.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pygments-2.15.0-py3-none-any.whl", hash = "sha256:77a3299119af881904cd5ecd1ac6a66214b6e9bed1f2db16993b54adede64094"}, + {file = "Pygments-2.15.0.tar.gz", hash = "sha256:f7e36cffc4c517fbc252861b9a6e4644ca0e5abadf9a113c72d1358ad09b9500"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pyk" +version = "0.1.232" +description = "" +category = "main" +optional = false +python-versions = "^3.10" +files = [] +develop = false + +[package.dependencies] +cmd2 = "^2.4.2" +coloredlogs = "^15.0.1" +filelock = "^3.9.0" +graphviz = "^0.20.1" +psutil = "^5.9.4" +pybind11 = "^2.10.3" +textual = "^0.10.1" +tomli = "^2.0.1" + +[package.source] +type = "git" +url = "https://github.com/runtimeverification/pyk.git" +reference = "v0.1.232" +resolved_reference = "92bfc282f5b03f7cb82ba4acbbddb26fc15eb4db" + +[[package]] +name = "pyperclip" +version = "1.8.2" +description = "A cross-platform clipboard module for Python. (Only handles plain text for now.)" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "pyperclip-1.8.2.tar.gz", hash = "sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57"}, +] + +[[package]] +name = "pyreadline3" +version = "3.4.1" +description = "A python implementation of GNU readline." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "pyreadline3-3.4.1-py3-none-any.whl", hash = "sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb"}, + {file = "pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae"}, +] + +[[package]] +name = "pytest" +version = "7.3.0" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.3.0-py3-none-any.whl", hash = "sha256:933051fa1bfbd38a21e73c3960cebdad4cf59483ddba7696c48509727e17f201"}, + {file = "pytest-7.3.0.tar.gz", hash = "sha256:58ecc27ebf0ea643ebfdf7fb1249335da761a00c9f955bcd922349bcb68ee57d"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "4.0.0" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, + {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "pytest-mock" +version = "3.10.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"}, + {file = "pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b"}, +] + +[package.dependencies] +pytest = ">=5.0" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + +[[package]] +name = "pytest-xdist" +version = "3.2.1" +description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-xdist-3.2.1.tar.gz", hash = "sha256:1849bd98d8b242b948e472db7478e090bf3361912a8fed87992ed94085f54727"}, + {file = "pytest_xdist-3.2.1-py3-none-any.whl", hash = "sha256:37290d161638a20b672401deef1cba812d110ac27e35d213f091d15b8beb40c9"}, +] + +[package.dependencies] +execnet = ">=1.1" +pytest = ">=6.2.0" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + +[[package]] +name = "rich" +version = "13.3.4" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "main" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.3.4-py3-none-any.whl", hash = "sha256:22b74cae0278fd5086ff44144d3813be1cedc9115bdfabbfefd86400cb88b20a"}, + {file = "rich-13.3.4.tar.gz", hash = "sha256:b5d573e13605423ec80bdd0cd5f8541f7844a0e71a13f74cf454ccb2f490708b"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0,<3.0.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "textual" +version = "0.10.1" +description = "Modern Text User Interface framework" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "textual-0.10.1-py3-none-any.whl", hash = "sha256:dd9a5b38a74cf42364a0f247e8f57e3ded7d69d44a63ee664af333f986c48e81"}, + {file = "textual-0.10.1.tar.gz", hash = "sha256:928cfeec37c60b212963f484e806b25380afdddb5a2aecd888ce8c9b46f93553"}, +] + +[package.dependencies] +importlib-metadata = ">=4.11.3,<5.0.0" +nanoid = ">=2.0.0" +rich = ">12.6.0" + +[package.extras] +dev = ["aiohttp (>=3.8.1)", "click (>=8.1.2)", "msgpack (>=1.0.3)"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "toolz" +version = "0.12.0" +description = "List processing tools and functional utilities" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ + {file = "toolz-0.12.0-py3-none-any.whl", hash = "sha256:2059bd4148deb1884bb0eb770a3cde70e7f954cfbbdc2285f1f2de01fd21eb6f"}, + {file = "toolz-0.12.0.tar.gz", hash = "sha256:88c570861c440ee3f2f6037c4654613228ff40c93a6c25e0eba70d17282c6194"}, +] + +[[package]] +name = "typing-extensions" +version = "4.5.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, + {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, +] + +[[package]] +name = "wcwidth" +version = "0.2.6" +description = "Measures the displayed width of unicode strings in a terminal" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, + {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, +] + +[[package]] +name = "zipp" +version = "3.15.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, + {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "0179605f7400362c7ac722df0bfcfd161e5e0afcadecaee7f48a3a0e181bbdea" diff --git a/docs/runtimeverification-wasm-semantics/pykwasm/pyproject.toml b/docs/runtimeverification-wasm-semantics/pykwasm/pyproject.toml new file mode 100644 index 000000000..47c35c0fe --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/pykwasm/pyproject.toml @@ -0,0 +1,51 @@ +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry] +name = "pykwasm" +version = "0.1.0" +description = "" +authors = [ + "Runtime Verification, Inc. ", +] + +[tool.poetry.dependencies] +python = "^3.10" +cytoolz = "^0.12.1" +numpy = "^1.24.2" +pyk = { git = "https://github.com/runtimeverification/pyk.git", tag="v0.1.232" } + +[tool.poetry.group.dev.dependencies] +autoflake = "*" +black = "*" +flake8 = "*" +flake8-bugbear = "*" +flake8-comprehensions = "*" +flake8-quotes = "*" +isort = "*" +mypy = "*" +pep8-naming = "*" +pytest = "*" +pytest-cov = "*" +pytest-mock = "*" +pytest-xdist = "*" + +[tool.isort] +profile = "black" +line_length = 120 + +[tool.autoflake] +recursive = true +expand-star-imports = true +remove-all-unused-imports = true +ignore-init-module-imports = true +remove-duplicate-keys = true +remove-unused-variables = true + +[tool.black] +line-length = 120 +skip-string-normalization = true + +[tool.mypy] +disallow_untyped_defs = true diff --git a/docs/runtimeverification-wasm-semantics/pykwasm/src/pykwasm/__init__.py b/docs/runtimeverification-wasm-semantics/pykwasm/src/pykwasm/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/runtimeverification-wasm-semantics/pykwasm/src/pykwasm/hello.py b/docs/runtimeverification-wasm-semantics/pykwasm/src/pykwasm/hello.py new file mode 100644 index 000000000..6cab2ee46 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/pykwasm/src/pykwasm/hello.py @@ -0,0 +1,2 @@ +def hello(name: str) -> str: + return f'Hello, {name}!' diff --git a/docs/runtimeverification-wasm-semantics/pykwasm/src/pykwasm/py.typed b/docs/runtimeverification-wasm-semantics/pykwasm/src/pykwasm/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/docs/runtimeverification-wasm-semantics/pykwasm/src/tests/__init__.py b/docs/runtimeverification-wasm-semantics/pykwasm/src/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/runtimeverification-wasm-semantics/pykwasm/src/tests/integration/__init__.py b/docs/runtimeverification-wasm-semantics/pykwasm/src/tests/integration/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/runtimeverification-wasm-semantics/pykwasm/src/tests/integration/test_integration.py b/docs/runtimeverification-wasm-semantics/pykwasm/src/tests/integration/test_integration.py new file mode 100644 index 000000000..c939204da --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/pykwasm/src/tests/integration/test_integration.py @@ -0,0 +1,5 @@ +from pykwasm.hello import hello + + +def test_hello() -> None: + assert hello('World') == 'Hello, World!' diff --git a/docs/runtimeverification-wasm-semantics/pykwasm/src/tests/unit/__init__.py b/docs/runtimeverification-wasm-semantics/pykwasm/src/tests/unit/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/runtimeverification-wasm-semantics/pykwasm/src/tests/unit/test_unit.py b/docs/runtimeverification-wasm-semantics/pykwasm/src/tests/unit/test_unit.py new file mode 100644 index 000000000..c939204da --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/pykwasm/src/tests/unit/test_unit.py @@ -0,0 +1,5 @@ +from pykwasm.hello import hello + + +def test_hello() -> None: + assert hello('World') == 'Hello, World!' diff --git a/docs/runtimeverification-wasm-semantics/test.md b/docs/runtimeverification-wasm-semantics/test.md new file mode 100644 index 000000000..6ea7bb4cf --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/test.md @@ -0,0 +1,530 @@ +KWasm Testing +============= + +For testing, we augment the semantics with some helpers. + +```k +require "wasm-text.md" +require "auxil.md" +``` + +Module `WASM-TEST-SYNTAX` is just used for program parsing and `WASM-TEST` consists of the definitions both for parsing and execution. + +```k +module WASM-TEST-SYNTAX + imports WASM-TEST + imports WASM-TEXT-SYNTAX + imports WASM-AUXIL + imports WASM-REFERENCE-INTERPRETER-SYNTAX +endmodule + +module WASM-REFERENCE-INTERPRETER-SYNTAX + imports WASM-COMMON-SYNTAX + + syntax Auxil ::= Action + syntax Action ::= "(" "invoke" OptionalId WasmString Instrs ")" + | "invoke" Int WasmString + | "(" "get" OptionalId WasmString ")" + | "get" Int WasmString + // ---------------------------------------------------- + + syntax Auxil ::= "(" "register" WasmString ")" + | "(" "register" WasmString Index ")" + // ---------------------------------------------------- + + syntax DefnStrings ::= List{WasmString, ""} + syntax ModuleDecl ::= "(" "module" OptionalId "binary" DataString ")" [macro] + | "(" "module" OptionalId "quote" DefnStrings ")" [macro] + // ------------------------------------------------------------------------------ +``` + +Assertions for KWasm tests +-------------------------- + +We'll make `Assertion` a subsort of `Auxil`, since it is a form of top-level embedder instrucion. + +```k + syntax Auxil ::= Assertion + // -------------------------- +``` + +### Conformance Assertions + +Here we inplement the conformance assertions specified in [spec interpreter] including: + +``` + ( assert_return * ) ;; assert action has expected results. Sometimes * is just empty. + ( assert_return_canonical_nan ) ;; assert action results in NaN in a canonical form + ( assert_return_arithmetic_nan ) ;; assert action results in NaN with 1 in MSB of fraction field + ( assert_trap ) ;; assert action traps with given failure string + ( assert_exhaustion ) ;; assert action exhausts system resources + ( assert_malformed ) ;; assert module cannot be decoded with given failure string + ( assert_invalid ) ;; assert module is invalid with given failure string + ( assert_unlinkable ) ;; assert module fails to link + ( assert_trap ) ;; assert module traps on instantiation +``` + +```k + syntax Assertion ::= "(" "assert_return" Action Instr ")" + | "(" "assert_return" Action ")" + | "(" "assert_return_canonical_nan" Action ")" + | "(" "assert_return_arithmetic_nan" Action ")" + | "(" "assert_trap" Action WasmString ")" + | "(" "assert_exhaustion" Action WasmString ")" + | "(" "assert_malformed" ModuleDecl WasmString ")" + | "(" "assert_invalid" ModuleDecl WasmString ")" + | "(" "assert_unlinkable" ModuleDecl WasmString ")" + | "(" "assert_trap" ModuleDecl WasmString ")" + // --------------------------------------------------------------------------------- +``` + +```k +endmodule + +module WASM-TEST + imports WASM-REFERENCE-INTERPRETER-SYNTAX + imports WASM-AUXIL + imports WASM-TEXT +``` + +Configuration +------------- + +```k + configuration + + $PGM:Stmts + + +``` + +Passing Control +--------------- + +The test embedder passes control to the execution cell in Wasm. + +```k + rule PGM => . + .K => sequenceStmts(text2abstract(PGM)) +``` + +Bare Allocations +---------------- + +We allow allocations to appear outside of modules, for example interleaved with assertions in tests. +This is purely a KWasm feature, which is useful for testing. + +```k + rule A:Alloc => #emptyModule() ~> A ... + .Int + [owise] +``` + +Instruction sugar +----------------- + +We allow writing instructions at the top level in the test embedder. + +```k + rule FI:FoldedInstr => sequenceInstrs(unfoldInstrs(FI .Instrs)) ... +``` + +Auxiliary +--------- + +We add `token` as a value in order to implement some test assertions. + +```k + syntax Val ::= "token" + // ---------------------- +``` + +Reference Interpreter Commands +------------------------------ + +TODO: Move this to a separate `EMBEDDER` module? + +The official test suite contains some special auxillary instructions outside of the standard Wasm semantics. +The reference interpreter is a particular embedder with auxillary instructions, specified in [spec interpreter](https://github.com/WebAssembly/spec/blob/master/interpreter/README.md). + +### Actions + +We allow 2 kinds of actions: + +- We allow to `invoke` a function by its exported name. +- We allow to `get` a global export. + +```k + rule ( invoke OID:OptionalId ENAME:WasmString IS:Instrs ) => sequenceInstrs(IS) ~> ( invoke OID ENAME .Instrs ) ... + requires IS =/=K .Instrs + + rule ( invoke ENAME:WasmString .Instrs ) => invoke CUR ENAME ... + CUR + + rule ( invoke ID:Identifier ENAME:WasmString .Instrs ) => invoke MODIDX ENAME ... + ... ID |-> MODIDX ... + + rule invoke MODIDX:Int ENAME:WasmString => ( invoke FADDR ):Instr ... + + MODIDX + ... ENAME |-> IDX ... + ... IDX |-> FADDR ... + ... + + + rule ( get NAME:WasmString ) => get CUR NAME ... + CUR + + rule ( get MOD:Identifier NAME:WasmString ) => get MODIDX NAME ... + ... MOD |-> MODIDX ... + + rule get MODIDX:Int NAME:WasmString => VAL ... + + MODIDX + ... NAME |-> TFIDX ... + IDS + ... #ContextLookup(IDS, TFIDX) |-> ADDR ... + ... + + + ADDR + VAL + ... + +``` + +### Registering Modules + +We will reference modules by name in imports. +`register` is the instruction that allows us to associate a name with a module. + +```k + rule ( register S ) => ( register S (NEXT -Int 1) )... // Register last instantiated module. + NEXT + requires NEXT >Int 0 + + rule ( register S ID:Identifier ) => ( register S IDX ) ... + ... ID |-> IDX ... + + rule ( register S:WasmString IDX:Int ) => . ... + ... .Map => S |-> IDX ... +``` + +### Addtional Module Syntax + +The conformance test cases contain the syntax of declaring modules in the format of `(module binary *)` and `(module quote *)`. +They are not defined in the official specification. +In order to parse the conformance test cases, we handle these declarations here and just reduce them to the empty module. + +```k + rule ( module OID binary _DS ) => ( module OID .Defns ) + + rule ( module OID quote _DS ) => ( module OID .Defns ) +``` + +The conformance tests contain imports of the `"spectest"` module. +For now, we just introduce some special handling that ignores any tests that make use of `"spectest"`. +The handling consists of trapping whenever a `"spectest"` function is called, and removing the trap whenever a new module or assertion comes up. + +TODO: Actually implement the `"spectest"` module, or call out to the supplied on in the spec repo. + +```k + syntax Instr ::= "spectest_trap" + // -------------------------------- + rule spectest_trap ~> (_L:Label => .) ... + rule spectest_trap ~> (_F:Frame => .) ... + rule spectest_trap ~> (_I:Instr => .) ... + rule spectest_trap ~> (_D:Defn => .) ... + + rule (spectest_trap => .) ~> _M:ModuleDecl ... + rule (spectest_trap => .) ~> _A:Assertion ... + + rule #import(MOD, _, #funcDesc(... id: OID, type: TIDX)) + => #func(... type: TIDX, locals: [ .ValTypes ], body: spectest_trap .Instrs, metadata: #meta(... id: OID, localIds: .Map)) + ... + + requires MOD ==K #token("\"spectest\"", "WasmStringToken") + orBool MOD ==K #token("\"test\"" , "WasmStringToken") +``` + +Except `assert_return` and `assert_trap`, the remaining rules are directly reduced to empty. We are not planning to implement them and these rules are only used to make it easier to parse conformance tests. + +*TODO:* Make use of `assert_exhaustion`, by detecting stack overflow. + +```k + rule (assert_return ACT INSTR) => ACT ~> INSTR ~> #assertAndRemoveEqual ~> #assertAndRemoveToken ... + VALSTACK => token : VALSTACK + rule (assert_return ACT) => ACT ~> #assertAndRemoveToken ... + VALSTACK => token : VALSTACK + rule (assert_return_canonical_nan _ACT) => . ... + rule (assert_return_arithmetic_nan _ACT) => . ... + rule (assert_trap ACT:Action DESC) => ACT ~> #assertTrap DESC ... + rule (assert_exhaustion _ACT:Action _DESC) => . ... + rule (assert_malformed _MOD _DESC) => . ... + rule (assert_invalid _MOD _DESC) => . ... + rule (assert_unlinkable _MOD _DESC) => . ... + rule (assert_trap MOD:ModuleDecl DESC) => sequenceStmts(text2abstract(MOD .Stmts)) ~> #assertTrap DESC ... +``` + +And we implement some helper assertions to help testing. + +```k + syntax Assertion ::= "#assertAndRemoveEqual" + | "#assertAndRemoveToken" + // -------------------------------------------- + rule #assertAndRemoveEqual => #assertTopStack V .WasmString ~> ( drop ) ... + V : VALSTACK => VALSTACK + rule #assertAndRemoveToken => . ... + token : VALSTACK => VALSTACK +``` + +### Trap Assertion + +This asserts that a `trap` was just thrown. + +```k + syntax Assertion ::= "#assertTrap" WasmString + // --------------------------------------------- + rule trap ~> #assertTrap _ => . ... +``` + +### ValStack Assertions + +These functions make assertions about the state of the `` cell. + +```k + syntax Assertion ::= "#assertTopStack" Val WasmString + | "#assertTopStackExactly" Val WasmString + | "#assertStack" ValStack WasmString + | "#assertStackAux" ValStack ValStack + // --------------------------------------------------------------- + rule #assertTopStack S _ => . ... S : _VALSTACK + rule #assertTopStack < ITYPE:IValType > VAL _ => . ... < ITYPE > VAL' : _VALSTACK + requires #unsigned(ITYPE, VAL) ==Int VAL' + rule #assertTopStack < FTYPE:FValType > VAL _ => . ... < FTYPE > VAL' : _VALSTACK + requires signFloat(VAL) ==Bool signFloat(VAL') andBool VAL ==Float VAL' + + rule #assertTopStackExactly A _ => . ... A : _VALSTACK + + rule #assertStack S1 _ => #assertStackAux S1 S2 ... + S2 + rule #assertStackAux .ValStack _ => . ... + rule #assertStackAux ( V : S1') (V : S2') => #assertStackAux S1' S2' ... + rule #assertStackAux (< ITYPE > VAL : S1') (< ITYPE > VAL' : S2') => #assertStackAux S1' S2' ... + requires #unsigned(ITYPE, VAL) ==Int VAL' +``` + +### Variables Assertions + +The operator `#assertLocal`/`#assertGlobal` operators perform a check for a local/global variable's value. + +```k + syntax Assertion ::= "#assertLocal" Int Val WasmString + | "#assertGlobal" Index Val WasmString + // --------------------------------------------------------- + rule #assertLocal INDEX VALUE _ => . ... + ... INDEX |-> VALUE ... + + rule #assertGlobal TFIDX VALUE _ => . ... + CUR + + CUR + IDS + ... #ContextLookup(IDS , TFIDX) |-> GADDR ... + ... + + + + GADDR + VALUE + ... + + ... + +``` + +### Type Assertions + +`#assertType` checks whether a type is allocated to the correct index. +`#assertNextTypeIdx` checks whether the number of types are allocated correctly. + +```k + syntax Assertion ::= "#assertType" Int FuncType + | "#assertNextTypeIdx" Int + // --------------------------------------------- + rule #assertType IDX FTYPE => . ... + CUR + + CUR + ... IDX |-> FTYPE ... + ... + + + rule #assertNextTypeIdx IDX => . ... + CUR + + CUR + IDX + ... + +``` + +### Function Assertions + +This simply checks that the given function exists in the `` cell and has the given signature and local types. + +```k + syntax Assertion ::= "#assertFunction" Index FuncType VecType WasmString + // ------------------------------------------------------------------------ + rule #assertFunction IDX FTYPE LTYPE _ => . ... + CUR + + CUR + ... IDX |-> FADDR ... + ... + + + + FADDR + FTYPE + LTYPE + ... + + ... + +``` + +### Table Assertions + +This asserts related operation about tables. + +```k + syntax Assertion ::= "#assertTable" Index Int OptionalInt WasmString + // -------------------------------------------------------------------- + rule #assertTable TFIDX SIZE MAX _MSG => . ... + CUR + + CUR + IDS + #ContextLookup(IDS, TFIDX) |-> ADDR + ... + + + + ADDR + MAX + SIZE + ... + + ... + + + syntax Assertion ::= "#assertTableElem" "(" Int "," Int ")" WasmString + // ---------------------------------------------------------------------- + rule #assertTableElem (KEY , VAL) _MSG => . ... + CUR + + CUR + 0 |-> ADDR + ... + + + + ADDR + ... KEY |-> VAL ... + ... + + ... + +``` + +### Memory Assertions + +This checks that the last allocated memory has the given size and max value. + +```k + syntax Assertion ::= "#assertMemory" Index Int OptionalInt WasmString + // --------------------------------------------------------------------- + rule #assertMemory TFIDX SIZE MAX _MSG => . ... + CUR + + CUR + IDS + #ContextLookup(IDS, TFIDX) |-> ADDR + ... + + + + ADDR + MAX + SIZE + ... + + ... + + + syntax Assertion ::= "#assertMemoryData" "(" Int "," Int ")" WasmString + syntax Assertion ::= "#assertMemoryData" Int "(" Int "," Int ")" WasmString + // --------------------------------------------------------------------------- + rule #assertMemoryData (KEY , VAL) MSG => #assertMemoryData CUR (KEY, VAL) MSG ... + CUR + + rule #assertMemoryData MODIDX (KEY , VAL) _MSG => . ... + + MODIDX + 0 |-> ADDR + ... + + + + ADDR + BM + ... + + ... + + requires #getRange(BM, KEY, 1) ==Int VAL +``` + +### Module Assertions + +These assertions test (and delete) module instances. +These assertions act on the last module defined. + +```k + syntax Assertion ::= "#assertNamedModule" Identifier WasmString + // --------------------------------------------------------------- + rule #assertNamedModule NAME _S => . ... + ... NAME |-> IDX ... + + + IDX + ... + + ... + +``` + +The modules are cleaned all together after the test file is executed. + +Registry Assertations +--------------------- + +We also want to be able to test that the embedder's registration function is working. + +```k + syntax Assertion ::= "#assertRegistrationUnnamed" WasmString WasmString + | "#assertRegistrationNamed" WasmString Identifier WasmString + // ---------------------------------------------------------------------------------- + rule #assertRegistrationUnnamed REGNAME _ => . ... + IDX + ... REGNAME |-> IDX ... + + rule #assertRegistrationNamed REGNAME _NAME _ => . ... + IDX + ... REGNAME |-> IDX ... +``` + +```k +endmodule +``` diff --git a/docs/runtimeverification-wasm-semantics/tests/binary/datas.wat b/docs/runtimeverification-wasm-semantics/tests/binary/datas.wat new file mode 100644 index 000000000..f798727fc --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/binary/datas.wat @@ -0,0 +1,5 @@ +(module + (memory 1) + (data (i32.const 0) "WASM") + (data (i32.const 3) "foo\AA\01") + ) \ No newline at end of file diff --git a/docs/runtimeverification-wasm-semantics/tests/binary/elems.wat b/docs/runtimeverification-wasm-semantics/tests/binary/elems.wat new file mode 100644 index 000000000..4f4d45c93 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/binary/elems.wat @@ -0,0 +1,7 @@ +(module + (table 10 funcref) + (elem (i32.const 0) 0 0 $foo) + (elem (i32.const 3) 1 1 $foo) + (func) + (func) + (func $foo)) \ No newline at end of file diff --git a/docs/runtimeverification-wasm-semantics/tests/binary/exports.wat b/docs/runtimeverification-wasm-semantics/tests/binary/exports.wat new file mode 100644 index 000000000..858e13dc9 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/binary/exports.wat @@ -0,0 +1,6 @@ +(module + (func (export "foo")) + (func (export "fom")) + (memory (export "baz") 1 1) + (global (export "faz") (mut f64) (f64.const 0)) + (table (export "bar") 1 3 funcref)) \ No newline at end of file diff --git a/docs/runtimeverification-wasm-semantics/tests/binary/funcs.wat b/docs/runtimeverification-wasm-semantics/tests/binary/funcs.wat new file mode 100644 index 000000000..12b92c654 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/binary/funcs.wat @@ -0,0 +1,3 @@ +(module + (func (param i32 i64) (local f64)) +) diff --git a/docs/runtimeverification-wasm-semantics/tests/binary/globals.wat b/docs/runtimeverification-wasm-semantics/tests/binary/globals.wat new file mode 100644 index 000000000..7533bc018 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/binary/globals.wat @@ -0,0 +1,3 @@ +(module + (global i64 (i64.const 0)) + (global (mut i32) (i32.const 1))) \ No newline at end of file diff --git a/docs/runtimeverification-wasm-semantics/tests/binary/imports.wat b/docs/runtimeverification-wasm-semantics/tests/binary/imports.wat new file mode 100644 index 000000000..0bc7ed608 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/binary/imports.wat @@ -0,0 +1,6 @@ +(module + (type (func (param i32) (result i64))) + (import "env" "foo" (func)) + (import "env" "baz" (global (mut f64))) + (import "env" "faz" (memory 1 3)) + (import "env" "bar" (table 1 3 funcref))) diff --git a/docs/runtimeverification-wasm-semantics/tests/binary/instrs.wat b/docs/runtimeverification-wasm-semantics/tests/binary/instrs.wat new file mode 100644 index 000000000..e86b313d0 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/binary/instrs.wat @@ -0,0 +1,175 @@ +(module + (memory 0) + (table 100 funcref) + (global (mut i32) (i32.const 0)) + (func (param i32 i64) (local f64)) + (func (local i32) + ;; `unreachable` and `drop` are inserted as needed to + ;; ensure correct typing. + unreachable + + ;; Numeric Instrs + ;; -------------- + ;; Commented out instructions are not currently supported by py-wasm. + i32.const 0 drop + f32.const 0 drop + ;; i32.extend8_s drop + ;; i32.extend16_s drop + ;; i64.extend32_s drop + i32.wrap_i64 drop + i64.extend_i32_s drop + i64.extend_i32_u drop + i32.trunc_f64_s drop + i64.trunc_f32_u drop + ;; i32.trunc_sat_f64_s drop + ;; i32.trunc_sat_f64_u drop + f32.demote_f64 drop + f64.promote_f32 drop + f64.convert_i32_s drop + f64.convert_i32_u drop + ;; i32.reinterpret_f32 drop + ;; f64.reinterpret_i64 drop + ;; -- + unreachable + + ;; IUnOp + ;; ----- + i32.clz + i32.ctz + i32.popcnt + ;; -- + unreachable + + ;; IBinOp + ;; ------ + i64.add + i64.sub + i64.mul + i64.div_s + i64.div_u + i64.rem_s + i64.rem_u + i64.and + i64.or + i64.xor + i64.shl + i64.shr_s + i64.shr_u + i64.rotl + i64.rotr + ;; -- + unreachable + + ;; FUnOp + ;; ----- + f32.abs + f32.neg + f32.sqrt + f32.ceil + f32.floor + f32.trunc + f32.nearest + ;; -- + unreachable + + ;; FBinOp + ;; ------ + f64.add + f64.sub + f64.mul + f64.div + f64.min + f64.max + f64.copysign + ;; -- + unreachable + + ;; ITestop + ;; ------- + i32.eqz + ;; -- + unreachable + + ;; IRelOp + ;; ------ + i32.eq + i32.ne + i32.lt_s + i32.lt_u + i32.gt_s + i32.gt_u + i32.le_s + i32.le_u + i32.ge_s + i32.ge_u + ;; -- + unreachable + + ;; FRelOp + ;; ------ + f64.eq drop + f64.ne drop + f64.lt drop + f64.gt drop + f64.le drop + f64.ge drop + ;; -- + unreachable + + ;; Stack Instrs + ;; ------------ + drop + select + ;; -- + unreachable + + ;; Variable Instrs + ;; --------------- + local.get 0 + local.set 0 + local.tee 0 + global.get 0 + global.set 0 + ;; -- + unreachable + + ;; Memory Instrs + ;; ------------- + i32.load offset=10 drop + f32.load offset=10 drop + i32.store offset=10 drop + f32.store offset=10 drop + i32.load8_s offset=10 drop + i32.load8_u offset=10 drop + i32.load16_s offset=1 drop + i32.load16_u offset=1 drop + i64.load32_s offset=1 drop + i64.load32_u offset=1 drop + i32.store8 offset=10 drop + i32.store16 offset=10 drop + i64.store32 offset=10 drop + memory.size + memory.grow + ;; -- + unreachable + + ;; Control Instrs + ;; -------------- + nop + unreachable + block (result i32) unreachable end + block (result i32) unreachable nop end + block end + if (result f32) unreachable else unreachable end + loop unreachable end + br 0 + br_if 0 + br_table 0 0 0 + return + call 0 + call_indirect (type 0) + ;; -- + unreachable + ) + +) diff --git a/docs/runtimeverification-wasm-semantics/tests/binary/memories.wat b/docs/runtimeverification-wasm-semantics/tests/binary/memories.wat new file mode 100644 index 000000000..a6b37b194 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/binary/memories.wat @@ -0,0 +1,2 @@ +(module + (memory 1)) \ No newline at end of file diff --git a/docs/runtimeverification-wasm-semantics/tests/binary/start.wat b/docs/runtimeverification-wasm-semantics/tests/binary/start.wat new file mode 100644 index 000000000..6336cd421 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/binary/start.wat @@ -0,0 +1,3 @@ +(module + (func $first) + (start $first)) diff --git a/docs/runtimeverification-wasm-semantics/tests/binary/tables.wat b/docs/runtimeverification-wasm-semantics/tests/binary/tables.wat new file mode 100644 index 000000000..5dfd2780f --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/binary/tables.wat @@ -0,0 +1,2 @@ +(module + (table 1 funcref)) \ No newline at end of file diff --git a/docs/runtimeverification-wasm-semantics/tests/binary/types.wat b/docs/runtimeverification-wasm-semantics/tests/binary/types.wat new file mode 100644 index 000000000..cf2597774 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/binary/types.wat @@ -0,0 +1,5 @@ +(module + (type (func )) + (type (func (param i32 i64 f32 f64) (result i32))) + (type $foo (func (param i32 i64 f32 f64) (result i32))) +) diff --git a/docs/runtimeverification-wasm-semantics/tests/conformance/look_for_supported.sh b/docs/runtimeverification-wasm-semantics/tests/conformance/look_for_supported.sh new file mode 100644 index 000000000..cf5e90197 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/conformance/look_for_supported.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +# Execute the script from project top directory. + +# This script considers all conformance tests currently listed as unparseable or unsupported, and tries parsing and executing them in turn. +# If both steps are successfull, the confromance test is deleted from the lists. +# If either step fails, the conformance test is added to the correct list and removed from the other. + +unparsefile="tests/conformance/unparseable.txt" + +unparseable=$(cat $unparsefile) +backends="llvm" + +# Try all unparseable files, see if any new one parses and add them to the unsupported lists. +# Keep the files sorted and without duplicates so the diff is easy to follow. +for shortname in $unparseable $unsupported; do + file=tests/wasm-tests/test/core/$shortname + + echo Trying to parse: $shortname + echo =============== + + make $file.parse + if [ $? -eq 0 ] + then + sed --in-place "/^$shortname\$/d" $unparsefile + for backend in $backends; do + unsuppfile="tests/conformance/unsupported-$backend.txt" + echo $shortname >> $unsuppfile + sort -u $unsuppfile -o $unsuppfile + done + else + echo $shortname >> $unparsefile + sort -u $unparsefile -o $unparsefile + for backend in $backends; do + unsuppfile="tests/conformance/unsupported-$backend.txt" + sed --in-place "/^$shortname\$/d" $unsuppfile + done + echo "Unparseable: $shortname\n" + fi +done + +# Go over the unsupported files, see if any has become supported. +for backend in $backends; do + unsuppfile="tests/conformance/unsupported-$backend.txt" + unsupported=$(cat $unsuppfile) + for shortname in $unsupported; do + + file=tests/wasm-tests/test/core/$shortname + + echo Trying to run: $shortname + echo ============= + + make $file.run-term TEST_CONCRETE_BACKEND=$backend + if [ $? -eq 0 ] + then + # Now supported, remove. + sed --in-place "/^$shortname\$/d" $unparsefile + sed --in-place "/^$shortname\$/d" $unsuppfile + sort -u $unsuppfile -o $unsuppfile + echo "New supported ($backend): $shortname\n" + else + echo "Unsupported ($backend): $shortname\n" + fi + done +done diff --git a/docs/runtimeverification-wasm-semantics/tests/conformance/unparseable.txt b/docs/runtimeverification-wasm-semantics/tests/conformance/unparseable.txt new file mode 100644 index 000000000..e9238bbaf --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/conformance/unparseable.txt @@ -0,0 +1,14 @@ +br_table.wast +comments.wast +conversions.wast +endianness.wast +f32_cmp.wast +f32.wast +f64_cmp.wast +f64.wast +float_exprs.wast +float_literals.wast +globals.wast +memory.wast +names.wast +skip-stack-guard-page.wast diff --git a/docs/runtimeverification-wasm-semantics/tests/conformance/unsupported-llvm.txt b/docs/runtimeverification-wasm-semantics/tests/conformance/unsupported-llvm.txt new file mode 100644 index 000000000..19dabfd83 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/conformance/unsupported-llvm.txt @@ -0,0 +1,15 @@ +address.wast +align.wast +call_indirect.wast +const.wast +data.wast +elem.wast +f32_bitwise.wast +f64_bitwise.wast +float_memory.wast +float_misc.wast +imports.wast +left-to-right.wast +memory_redundancy.wast +memory_trap.wast +traps.wast diff --git a/docs/runtimeverification-wasm-semantics/tests/failing.simple b/docs/runtimeverification-wasm-semantics/tests/failing.simple new file mode 100644 index 000000000..c7693992e --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/failing.simple @@ -0,0 +1,4 @@ +tests/simple/f64_bitwise-cs.wast +tests/simple/f64_cmp-cs.wast +tests/simple/f64-cs.wast +tests/simple/fconversions-c.wast diff --git a/docs/runtimeverification-wasm-semantics/tests/interactive/config.xml b/docs/runtimeverification-wasm-semantics/tests/interactive/config.xml new file mode 100644 index 000000000..5c6804def --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/interactive/config.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/docs/runtimeverification-wasm-semantics/tests/interactive/fac.wast b/docs/runtimeverification-wasm-semantics/tests/interactive/fac.wast new file mode 100644 index 000000000..228ac0dd8 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/interactive/fac.wast @@ -0,0 +1,52 @@ +// (c) 2015 Andreas Rossberg + +(module + // Recursive factorial + (func (param i32) (result i32) + (local) // opt + (return + (if + (eq.i32 (getlocal 0) (const.i32 0)) + (const.i32 1) + (mul.i32 (getlocal 0) (call 0 (sub.i32 (getlocal 0) (const.i32 1)))) + ) + ) + ) + + // Recursive factorial with implicit return value + (func (param i32) (result i32) + (local) // opt + (if + (eq.i32 (getlocal 0) (const.i32 0)) + (const.i32 1) + (mul.i32 (getlocal 0) (call 0 (sub.i32 (getlocal 0) (const.i32 1)))) + ) + ) + + // Iterative factorial + (func (param i32) (result i32) + (local i32) + (setlocal 1 (const.i32 1)) + (label + (loop + (if + (eq.i32 (getlocal 0) (const.i32 0)) + (break 0) + (block + (setlocal 1 (mul.i32 (getlocal 0) (getlocal 1))) + (setlocal 0 (sub.i32 (getlocal 0) (const.i32 1))) + ) + ) + ) + ) + (return (getlocal 1)) + ) + + (export 0 1 2) + + (memory 0) +) + +(invoke 0 (const.i32 3)) +(invoke 1 (const.i32 3)) +(invoke 2 (const.i32 3)) diff --git a/docs/runtimeverification-wasm-semantics/tests/interactive/fac.wast.out b/docs/runtimeverification-wasm-semantics/tests/interactive/fac.wast.out new file mode 100644 index 000000000..166be640d --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/interactive/fac.wast.out @@ -0,0 +1,3 @@ +6 +6 +6 diff --git a/docs/runtimeverification-wasm-semantics/tests/interactive/memory.wast b/docs/runtimeverification-wasm-semantics/tests/interactive/memory.wast new file mode 100644 index 000000000..d0c5ba4d3 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/interactive/memory.wast @@ -0,0 +1,69 @@ +// (c) 2015 Andreas Rossberg + +(module + // Aligned read/write + (func (param) (result i32) + (local i32 i32 i32) + (setlocal 0 (const.i32 10)) + (label + (loop + (if + (eq.i32 (getlocal 0) (const.i32 0)) + (break) + ) + (setlocal 2 (mul.i32 (getlocal 0) (const.i32 4))) + (setnears.i32 (getlocal 2) (getlocal 0)) + (setlocal 1 (getnears.i32 (getlocal 2))) + (if + (neq.i32 (getlocal 0) (getlocal 1)) + (return (const.i32 0)) + ) + (setlocal 0 (sub.i32 (getlocal 0) (const.i32 1))) + ) + ) + (return (const.i32 1)) + ) + + // Unaligned read/write + (func (param) (result i32) + (local i32 i32 i32) + (setlocal 0 (const.i32 10)) + (label + (loop + (if + (eq.i32 (getlocal 0) (const.i32 0)) + (break) + ) + (setlocal 2 (getlocal 0)) + (setnearunaligneds.i32 (getlocal 0) (getlocal 2)) + (setlocal 1 (getnearunaligneds.i32 (getlocal 0))) + (if + (neq.i32 (getlocal 2) (getlocal 1)) + (return (const.i32 0)) + ) + (setlocal 0 (sub.i32 (getlocal 0) (const.i32 1))) + ) + ) + (return (const.i32 1)) + ) + + // Memory cast + (func (param) (result f64) + (local) + (setnears.i64 (const.i32 8) (const.i64 -12345)) + (if + (eq.f64 (getnear.f64 (const.i32 8)) (cast.i64.f64 (const.i64 -12345))) + (return (const.f64 0)) + ) + (setfarunaligneds.i16 (const.i64 3) (const.i32 -23423)) + (return (getnear.f64 (const.i32 0))) + ) + + (export 0 1 2) + + (memory 64) +) + +(invoke 0) +(invoke 1) +//(invoke 2) diff --git a/docs/runtimeverification-wasm-semantics/tests/interactive/memory.wast.out b/docs/runtimeverification-wasm-semantics/tests/interactive/memory.wast.out new file mode 100644 index 000000000..6ed281c75 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/interactive/memory.wast.out @@ -0,0 +1,2 @@ +1 +1 diff --git a/docs/runtimeverification-wasm-semantics/tests/interactive/ret.wast b/docs/runtimeverification-wasm-semantics/tests/interactive/ret.wast new file mode 100644 index 000000000..4789abf11 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/interactive/ret.wast @@ -0,0 +1,21 @@ +(module + (func (param) (result i32 i32) + (local) + (return + (const.i32 1) + (const.i32 2) + ) + ) + + (func (param) (result i32) + (local i32 i32) + (destruct 0 1 (call 0)) + (return (add.i32 (getlocal 0) (getlocal 1))) + ) + + (export 0 1) + + (memory 0) +) + +(invoke 1) diff --git a/docs/runtimeverification-wasm-semantics/tests/interactive/ret.wast.out b/docs/runtimeverification-wasm-semantics/tests/interactive/ret.wast.out new file mode 100644 index 000000000..00750edc0 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/interactive/ret.wast.out @@ -0,0 +1 @@ +3 diff --git a/docs/runtimeverification-wasm-semantics/tests/interactive/sum.wast b/docs/runtimeverification-wasm-semantics/tests/interactive/sum.wast new file mode 100644 index 000000000..59de0b855 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/interactive/sum.wast @@ -0,0 +1,25 @@ +(module + (func (param i32) (result i32) + (local i32) + (setlocal 1 (const.i32 0)) + (label + (loop + (if + (eq.i32 (getlocal 0) (const.i32 0)) + (break 0) + (block + (setlocal 1 (add.i32 (getlocal 0) (getlocal 1))) + (setlocal 0 (sub.i32 (getlocal 0) (const.i32 1))) + ) + ) + ) + ) + (return (getlocal 1)) + ) + + (export 0) + + (memory 0) +) + +(invoke 0 (const.i32 10)) diff --git a/docs/runtimeverification-wasm-semantics/tests/interactive/sum.wast.out b/docs/runtimeverification-wasm-semantics/tests/interactive/sum.wast.out new file mode 100644 index 000000000..c3f407c09 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/interactive/sum.wast.out @@ -0,0 +1 @@ +55 diff --git a/docs/runtimeverification-wasm-semantics/tests/proofs/functions-spec.k b/docs/runtimeverification-wasm-semantics/tests/proofs/functions-spec.k new file mode 100644 index 000000000..ed24b5592 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/proofs/functions-spec.k @@ -0,0 +1,64 @@ +requires "kwasm-lemmas.md" + +module FUNCTIONS-LEMMAS + imports KWASM-LEMMAS + + syntax KItem ::= run ( Step ) | done ( Step ) + // --------------------------------------------- + rule run ( S ) => done ( S ) ... + + syntax Step ::= Bytes | Int + // --------------------------- +endmodule + +module FUNCTIONS-SPEC + imports FUNCTIONS-LEMMAS + + claim run ( #wrap(2, #getRange(BM, ADDR, 4)) ) => done ( #getRange(BM, ADDR, 2) ) ... requires 0 <=Int ADDR + + claim run ( #setRange(BM, ADDR, #wrap(2, #getRange(BM, ADDR, 4)), 2) ) => done ( BM ) ... requires 0 <=Int ADDR + + claim run ( #getRange(_BM, _ADDR, 0) ) => done ( 0 ) ... + claim run ( #getRange(_BM, ADDR, _WIDTH) ) => done ( 0 ) ... requires ADDR run ( #getRange(BM, ADDR, 1) modInt 256 ) => done ( #getRange(BM, ADDR , 1) ) ... + claim run ( #getRange(BM, ADDR, 3) modInt 256 ) => done ( #getRange(BM, ADDR , 1) ) ... + claim run ( #getRange(BM, ADDR, 3) modInt (2 ^Int (8 *Int 3)) ) => done ( #getRange(BM, ADDR , 3) ) ... + claim run ( #getRange(BM, ADDR, 7) modInt (2 ^Int (8 *Int 3)) ) => done ( #getRange(BM, ADDR , 3) ) ... + claim run ( #getRange(BM, ADDR, 2) >>Int 8 ) => done ( #getRange(BM, ADDR +Int 1, 1) ) ... requires 0 <=Int ADDR + + claim run ( ( ( #getRange ( BM , ADDR +Int 1 , 1 ) modInt 256 ) +Int ( #getRange ( BM , ADDR , 1 ) modInt 256 < done ( ( #getRange ( BM , ADDR +Int 1 , 1 ) +Int ( #getRange ( BM , ADDR , 1 ) < + + claim run ( ( ( #getRange ( BM , ADDR +Int 1 , 7 ) modInt 256 ) +Int ( #getRange ( BM , ADDR , 8 ) modInt 256 < done ( ( ( #getRange ( BM , ADDR +Int 1 , 1 ) ) +Int ( #getRange ( BM , ADDR , 1 ) < + + claim run ( #setRange(BM, ADDR, #getRange(BM, ADDR +Int 1, 1) +Int (#getRange(BM, ADDR, 1) < done ( #setRange(#setRange(BM, ADDR, #getRange(BM, ADDR +Int 1, 1), 1), ADDR +Int 1, #getRange(BM, ADDR, 1), 1) ) + ... + + requires ADDR >=Int 0 + + claim run ( #setRange ( BM , ADDR modInt 4294967296 , #getRange ( BM , ADDR modInt 4294967296 , #numBytes ( ITYPE ) ) , #numBytes ( ITYPE ) ) ) + => done ( BM ) + ... + + + claim run ( (#getRange(BM, ADDR +Int 1, 1) +Int (#getRange (BM, ADDR, 1) < done ( #getRange(BM, ADDR +Int 1, 1) +Int (#getRange (BM, ADDR, 1) < + + claim run ( #getRange ( #setRange ( BM , ADDR +Int 7 , _VAL , 1 ) , ADDR +Int 3 , 1 ) ) => done ( #getRange ( BM , ADDR +Int 3 , 1 ) ) ... + + claim run ( #getRange ( #setRange ( _BM , ADDR +Int 7 , VAL , 1 ) , ADDR +Int 7 , 1 ) ) => done ( VAL ) ... requires 0 <=Int ADDR andBool 0 <=Int VAL andBool VAL run ( #getRange ( #setRange ( #setRange ( #setRange ( #setRange ( #setRange ( #setRange ( #setRange ( #setRange ( BM , ADDR , #getRange ( BM , ADDR +Int 7 , 1 ) , 1 ) , ADDR +Int 1 , #getRange ( BM , ADDR +Int 6 , 1 ) , 1 ) , ADDR +Int 2 , #getRange ( BM , ADDR +Int 5 , 1 ) , 1 ) , ADDR +Int 3 , #getRange ( BM , ADDR +Int 4 , 1 ) , 1 ) , ADDR +Int 4 , #getRange ( BM , ADDR +Int 3 , 1 ) , 1 ) , ADDR +Int 5 , #getRange ( BM , ADDR +Int 2 , 1 ) , 1 ) , ADDR +Int 6 , #getRange ( BM , ADDR +Int 1 , 1 ) , 1 ) , ADDR +Int 7 , #getRange ( BM , ADDR , 1 ) , 1 ) , ADDR +Int 3 , 1 ) ) + => done ( #getRange ( BM , ADDR +Int 4 , 1 ) ) + ... + + requires 0 <=Int ADDR + +endmodule diff --git a/docs/runtimeverification-wasm-semantics/tests/proofs/locals-spec.k b/docs/runtimeverification-wasm-semantics/tests/proofs/locals-spec.k new file mode 100644 index 000000000..b4afe22eb --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/proofs/locals-spec.k @@ -0,0 +1,10 @@ +requires "kwasm-lemmas.md" + +module LOCALS-SPEC + imports KWASM-LEMMAS + + claim #local.get(X) ~> #local.set(X) => . ... + + X |-> < _ITYPE > (VAL => VAL) + +endmodule diff --git a/docs/runtimeverification-wasm-semantics/tests/proofs/loops-spec.k b/docs/runtimeverification-wasm-semantics/tests/proofs/loops-spec.k new file mode 100644 index 000000000..6f62a7a05 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/proofs/loops-spec.k @@ -0,0 +1,70 @@ +requires "kwasm-lemmas.md" + +module LOOPS-SPEC + imports KWASM-LEMMAS + + // Lemma + claim #br(0) + ~> label // Loop label. + [ .ValTypes ] + { #loop([ .ValTypes ], + #local.get(0) + #local.get(1) + ITYPE.add + #local.set(1) + #local.get(0) + ITYPE.const 1 + ITYPE.sub + #local.tee(0) + ITYPE.eqz + #br_if(1) + #br(0), + _ + ) + } + .ValStack + ~> label [ .ValTypes ] {.Instrs } STACK // Block label. + => . + ... + + _ => STACK + + 0 |-> < ITYPE > (I => 0) + 1 |-> < ITYPE > (X => X +Int ((I *Int (I +Int 1)) /Int 2)) + + requires #inUnsignedRange(ITYPE, I) + andBool I >Int 0 + andBool #inUnsignedRange(ITYPE, X +Int I) + andBool #inUnsignedRange(ITYPE, X +Int ((I *Int (I +Int 1)) /Int 2)) + + // Main claim. + claim + #block([ .ValTypes ], + #loop([ .ValTypes], + #local.get(0) + #local.get(1) + ITYPE.add + #local.set(1) + #local.get(0) + ITYPE.const 1 + ITYPE.sub + #local.tee(0) + ITYPE.eqz + #br_if(1) + #br(0), + _ + ), + _ + ) + => . + ... + + + 0 |-> < ITYPE > (N => 0) + 1 |-> < ITYPE > (0 => (N *Int (N +Int 1)) /Int 2) + + requires #inUnsignedRange(ITYPE, N) + andBool N >Int 0 + andBool #inUnsignedRange(ITYPE, ((N *Int (N +Int 1)) /Int 2)) + +endmodule diff --git a/docs/runtimeverification-wasm-semantics/tests/proofs/memory-spec.k b/docs/runtimeverification-wasm-semantics/tests/proofs/memory-spec.k new file mode 100644 index 000000000..1c8d93beb --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/proofs/memory-spec.k @@ -0,0 +1,38 @@ +requires "kwasm-lemmas.md" + +module MEMORY-SPEC + imports KWASM-LEMMAS + + claim i32.const ADDR ~> i32.const ADDR ~> #load(i64, load32_u, 0) ~> #store(i64, store16, 0) => . ... + CUR + + CUR + 0 |-> MEMADDR + ... + + + MEMADDR + SIZE + _BM + ... + + requires + ADDR +Int #numBytes(i64) <=Int SIZE *Int #pageSize() + andBool #inUnsignedRange(i32, ADDR) + + claim i32.const ADDR ~> i32.const ADDR ~> #load(ITYPE:IValType, load, 0) ~> #store(ITYPE, store, 0) => . ... + CUR + + CUR + 0 |-> MEMADDR + ... + + + MEMADDR + SIZE + _BM + ... + + requires + #get(#chop( ADDR)) +Int #numBytes(ITYPE) <=Int SIZE *Int #pageSize() +endmodule diff --git a/docs/runtimeverification-wasm-semantics/tests/proofs/simple-arithmetic-spec.k b/docs/runtimeverification-wasm-semantics/tests/proofs/simple-arithmetic-spec.k new file mode 100644 index 000000000..0c2ad2fef --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/proofs/simple-arithmetic-spec.k @@ -0,0 +1,18 @@ +requires "kwasm-lemmas.md" + +module SIMPLE-ARITHMETIC-SPEC + imports KWASM-LEMMAS + + claim ITYPE:IValType . const X:Int => . ... + S:ValStack => < ITYPE > X : S + requires #inUnsignedRange(ITYPE, X) + + claim ITYPE:IValType . const X:Int => . ... + S:ValStack => < ITYPE > (X +Int #pow(ITYPE)) : S + requires (#minSigned(ITYPE) <=Int X) andBool (X ITYPE:IValType . const X:Int ~> ITYPE . const Y:Int ~> ITYPE . add => . ... + S:ValStack => < ITYPE > (X +Int Y) : S + requires 0 <=Int X andBool 0 <=Int Y + andBool (X +Int Y) sequenceDefns(#t2aDefns<#freshCtx()>(#wrc20ReverseBytes)) // TODO: Have this pre-loaded in the store. + ~> i32.const ADDR + ~> i32.const ADDR + ~> #load(i64, load, 0) + ~> (invoke NEXTADDR) // TODO: Use `call`. + ~> #store(i64, store, 0) + => . + ... + + CUR + + CUR + #wrc20ReverseBytesTypeIdx |-> #wrc20ReverseBytesType + 0 |-> MEMADDR + _ => ?_ + NEXTFUNCIDX => NEXTFUNCIDX +Int 1 + ... + + .Bag => ?_ + NEXTADDR => NEXTADDR +Int 1 + + MEMADDR + SIZE + BM => ?BM' + ... + + // TODO: Make function out of this tricky side condition. + requires ADDR +Int #numBytes(i64) <=Int SIZE *Int #pageSize() + andBool #inUnsignedRange(i32, ADDR) + ensures #getRange(BM, ADDR +Int 0, 1) ==Int #getRange(?BM', ADDR +Int 7, 1) + andBool #getRange(BM, ADDR +Int 1, 1) ==Int #getRange(?BM', ADDR +Int 6, 1) + andBool #getRange(BM, ADDR +Int 2, 1) ==Int #getRange(?BM', ADDR +Int 5, 1) + andBool #getRange(BM, ADDR +Int 3, 1) ==Int #getRange(?BM', ADDR +Int 4, 1) + andBool #getRange(BM, ADDR +Int 4, 1) ==Int #getRange(?BM', ADDR +Int 3, 1) + andBool #getRange(BM, ADDR +Int 5, 1) ==Int #getRange(?BM', ADDR +Int 2, 1) + andBool #getRange(BM, ADDR +Int 6, 1) ==Int #getRange(?BM', ADDR +Int 1, 1) + andBool #getRange(BM, ADDR +Int 7, 1) ==Int #getRange(?BM', ADDR +Int 0, 1) + +endmodule diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/address-c.wast b/docs/runtimeverification-wasm-semantics/tests/simple/address-c.wast new file mode 100644 index 000000000..ca37275cf --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/address-c.wast @@ -0,0 +1,591 @@ +;; Load i32 data with different offset/align arguments + +(module + (memory 1) + (data (i32.const 0) "abcdefghijklmnopqrstuvwxyz") + + (func (export "8u_good1") (param $i i32) (result i32) + (i32.load8_u offset=0 (local.get $i)) ;; 97 'a' + ) + (func (export "8u_good2") (param $i i32) (result i32) + (i32.load8_u align=1 (local.get $i)) ;; 97 'a' + ) + (func (export "8u_good3") (param $i i32) (result i32) + (i32.load8_u offset=1 align=1 (local.get $i)) ;; 98 'b' + ) + (func (export "8u_good4") (param $i i32) (result i32) + (i32.load8_u offset=2 align=1 (local.get $i)) ;; 99 'c' + ) + (func (export "8u_good5") (param $i i32) (result i32) + (i32.load8_u offset=25 align=1 (local.get $i)) ;; 122 'z' + ) + + (func (export "8s_good1") (param $i i32) (result i32) + (i32.load8_s offset=0 (local.get $i)) ;; 97 'a' + ) + (func (export "8s_good2") (param $i i32) (result i32) + (i32.load8_s align=1 (local.get $i)) ;; 97 'a' + ) + (func (export "8s_good3") (param $i i32) (result i32) + (i32.load8_s offset=1 align=1 (local.get $i)) ;; 98 'b' + ) + (func (export "8s_good4") (param $i i32) (result i32) + (i32.load8_s offset=2 align=1 (local.get $i)) ;; 99 'c' + ) + (func (export "8s_good5") (param $i i32) (result i32) + (i32.load8_s offset=25 align=1 (local.get $i)) ;; 122 'z' + ) + + (func (export "16u_good1") (param $i i32) (result i32) + (i32.load16_u offset=0 (local.get $i)) ;; 25185 'ab' + ) + (func (export "16u_good2") (param $i i32) (result i32) + (i32.load16_u align=1 (local.get $i)) ;; 25185 'ab' + ) + (func (export "16u_good3") (param $i i32) (result i32) + (i32.load16_u offset=1 align=1 (local.get $i)) ;; 25442 'bc' + ) + (func (export "16u_good4") (param $i i32) (result i32) + (i32.load16_u offset=2 align=2 (local.get $i)) ;; 25699 'cd' + ) + (func (export "16u_good5") (param $i i32) (result i32) + (i32.load16_u offset=25 align=2 (local.get $i)) ;; 122 'z\0' + ) + + (func (export "16s_good1") (param $i i32) (result i32) + (i32.load16_s offset=0 (local.get $i)) ;; 25185 'ab' + ) + (func (export "16s_good2") (param $i i32) (result i32) + (i32.load16_s align=1 (local.get $i)) ;; 25185 'ab' + ) + (func (export "16s_good3") (param $i i32) (result i32) + (i32.load16_s offset=1 align=1 (local.get $i)) ;; 25442 'bc' + ) + (func (export "16s_good4") (param $i i32) (result i32) + (i32.load16_s offset=2 align=2 (local.get $i)) ;; 25699 'cd' + ) + (func (export "16s_good5") (param $i i32) (result i32) + (i32.load16_s offset=25 align=2 (local.get $i)) ;; 122 'z\0' + ) + + (func (export "32_good1") (param $i i32) (result i32) + (i32.load offset=0 (local.get $i)) ;; 1684234849 'abcd' + ) + (func (export "32_good2") (param $i i32) (result i32) + (i32.load align=1 (local.get $i)) ;; 1684234849 'abcd' + ) + (func (export "32_good3") (param $i i32) (result i32) + (i32.load offset=1 align=1 (local.get $i)) ;; 1701077858 'bcde' + ) + (func (export "32_good4") (param $i i32) (result i32) + (i32.load offset=2 align=2 (local.get $i)) ;; 1717920867 'cdef' + ) + (func (export "32_good5") (param $i i32) (result i32) + (i32.load offset=25 align=4 (local.get $i)) ;; 122 'z\0\0\0' + ) + + (func (export "8u_bad") (param $i i32) + (drop (i32.load8_u offset=4294967295 (local.get $i))) + ) + (func (export "8s_bad") (param $i i32) + (drop (i32.load8_s offset=4294967295 (local.get $i))) + ) + (func (export "16u_bad") (param $i i32) + (drop (i32.load16_u offset=4294967295 (local.get $i))) + ) + (func (export "16s_bad") (param $i i32) + (drop (i32.load16_s offset=4294967295 (local.get $i))) + ) + (func (export "32_bad") (param $i i32) + (drop (i32.load offset=4294967295 (local.get $i))) + ) +) + +(assert_return (invoke "8u_good1" (i32.const 0)) (i32.const 97)) +(assert_return (invoke "8u_good2" (i32.const 0)) (i32.const 97)) +(assert_return (invoke "8u_good3" (i32.const 0)) (i32.const 98)) +(assert_return (invoke "8u_good4" (i32.const 0)) (i32.const 99)) +(assert_return (invoke "8u_good5" (i32.const 0)) (i32.const 122)) + +(assert_return (invoke "8s_good1" (i32.const 0)) (i32.const 97)) +(assert_return (invoke "8s_good2" (i32.const 0)) (i32.const 97)) +(assert_return (invoke "8s_good3" (i32.const 0)) (i32.const 98)) +(assert_return (invoke "8s_good4" (i32.const 0)) (i32.const 99)) +(assert_return (invoke "8s_good5" (i32.const 0)) (i32.const 122)) + +(assert_return (invoke "16u_good1" (i32.const 0)) (i32.const 25185)) +(assert_return (invoke "16u_good2" (i32.const 0)) (i32.const 25185)) +(assert_return (invoke "16u_good3" (i32.const 0)) (i32.const 25442)) +(assert_return (invoke "16u_good4" (i32.const 0)) (i32.const 25699)) +(assert_return (invoke "16u_good5" (i32.const 0)) (i32.const 122)) + +(assert_return (invoke "16s_good1" (i32.const 0)) (i32.const 25185)) +(assert_return (invoke "16s_good2" (i32.const 0)) (i32.const 25185)) +(assert_return (invoke "16s_good3" (i32.const 0)) (i32.const 25442)) +(assert_return (invoke "16s_good4" (i32.const 0)) (i32.const 25699)) +(assert_return (invoke "16s_good5" (i32.const 0)) (i32.const 122)) + +(assert_return (invoke "32_good1" (i32.const 0)) (i32.const 1684234849)) +(assert_return (invoke "32_good2" (i32.const 0)) (i32.const 1684234849)) +(assert_return (invoke "32_good3" (i32.const 0)) (i32.const 1701077858)) +(assert_return (invoke "32_good4" (i32.const 0)) (i32.const 1717920867)) +(assert_return (invoke "32_good5" (i32.const 0)) (i32.const 122)) + +(assert_return (invoke "8u_good1" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "8u_good2" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "8u_good3" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "8u_good4" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "8u_good5" (i32.const 65507)) (i32.const 0)) + +(assert_return (invoke "8s_good1" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "8s_good2" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "8s_good3" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "8s_good4" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "8s_good5" (i32.const 65507)) (i32.const 0)) + +(assert_return (invoke "16u_good1" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "16u_good2" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "16u_good3" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "16u_good4" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "16u_good5" (i32.const 65507)) (i32.const 0)) + +(assert_return (invoke "16s_good1" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "16s_good2" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "16s_good3" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "16s_good4" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "16s_good5" (i32.const 65507)) (i32.const 0)) + +(assert_return (invoke "32_good1" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "32_good2" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "32_good3" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "32_good4" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "32_good5" (i32.const 65507)) (i32.const 0)) + +(assert_return (invoke "8u_good1" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "8u_good2" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "8u_good3" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "8u_good4" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "8u_good5" (i32.const 65508)) (i32.const 0)) + +(assert_return (invoke "8s_good1" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "8s_good2" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "8s_good3" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "8s_good4" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "8s_good5" (i32.const 65508)) (i32.const 0)) + +(assert_return (invoke "16u_good1" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "16u_good2" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "16u_good3" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "16u_good4" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "16u_good5" (i32.const 65508)) (i32.const 0)) + +(assert_return (invoke "16s_good1" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "16s_good2" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "16s_good3" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "16s_good4" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "16s_good5" (i32.const 65508)) (i32.const 0)) + +(assert_return (invoke "32_good1" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "32_good2" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "32_good3" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "32_good4" (i32.const 65508)) (i32.const 0)) +(assert_trap (invoke "32_good5" (i32.const 65508)) "out of bounds memory access") + +(assert_trap (invoke "8u_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "8s_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "16u_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "16s_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "32_bad" (i32.const 0)) "out of bounds memory access") + +(assert_trap (invoke "8u_bad" (i32.const 1)) "out of bounds memory access") +(assert_trap (invoke "8s_bad" (i32.const 1)) "out of bounds memory access") +(assert_trap (invoke "16u_bad" (i32.const 1)) "out of bounds memory access") +(assert_trap (invoke "16s_bad" (i32.const 1)) "out of bounds memory access") +(assert_trap (invoke "32_bad" (i32.const 1)) "out of bounds memory access") + +(assert_malformed + (module quote + "(memory 1)" + "(func (drop (i32.load offset=4294967296 (i32.const 0))))" + ) + "i32 constant" +) + +;; Load i64 data with different offset/align arguments + +(module + (memory 1) + (data (i32.const 0) "abcdefghijklmnopqrstuvwxyz") + + (func (export "8u_good1") (param $i i32) (result i64) + (i64.load8_u offset=0 (local.get $i)) ;; 97 'a' + ) + (func (export "8u_good2") (param $i i32) (result i64) + (i64.load8_u align=1 (local.get $i)) ;; 97 'a' + ) + (func (export "8u_good3") (param $i i32) (result i64) + (i64.load8_u offset=1 align=1 (local.get $i)) ;; 98 'b' + ) + (func (export "8u_good4") (param $i i32) (result i64) + (i64.load8_u offset=2 align=1 (local.get $i)) ;; 99 'c' + ) + (func (export "8u_good5") (param $i i32) (result i64) + (i64.load8_u offset=25 align=1 (local.get $i)) ;; 122 'z' + ) + + (func (export "8s_good1") (param $i i32) (result i64) + (i64.load8_s offset=0 (local.get $i)) ;; 97 'a' + ) + (func (export "8s_good2") (param $i i32) (result i64) + (i64.load8_s align=1 (local.get $i)) ;; 97 'a' + ) + (func (export "8s_good3") (param $i i32) (result i64) + (i64.load8_s offset=1 align=1 (local.get $i)) ;; 98 'b' + ) + (func (export "8s_good4") (param $i i32) (result i64) + (i64.load8_s offset=2 align=1 (local.get $i)) ;; 99 'c' + ) + (func (export "8s_good5") (param $i i32) (result i64) + (i64.load8_s offset=25 align=1 (local.get $i)) ;; 122 'z' + ) + + (func (export "16u_good1") (param $i i32) (result i64) + (i64.load16_u offset=0 (local.get $i)) ;; 25185 'ab' + ) + (func (export "16u_good2") (param $i i32) (result i64) + (i64.load16_u align=1 (local.get $i)) ;; 25185 'ab' + ) + (func (export "16u_good3") (param $i i32) (result i64) + (i64.load16_u offset=1 align=1 (local.get $i)) ;; 25442 'bc' + ) + (func (export "16u_good4") (param $i i32) (result i64) + (i64.load16_u offset=2 align=2 (local.get $i)) ;; 25699 'cd' + ) + (func (export "16u_good5") (param $i i32) (result i64) + (i64.load16_u offset=25 align=2 (local.get $i)) ;; 122 'z\0' + ) + + (func (export "16s_good1") (param $i i32) (result i64) + (i64.load16_s offset=0 (local.get $i)) ;; 25185 'ab' + ) + (func (export "16s_good2") (param $i i32) (result i64) + (i64.load16_s align=1 (local.get $i)) ;; 25185 'ab' + ) + (func (export "16s_good3") (param $i i32) (result i64) + (i64.load16_s offset=1 align=1 (local.get $i)) ;; 25442 'bc' + ) + (func (export "16s_good4") (param $i i32) (result i64) + (i64.load16_s offset=2 align=2 (local.get $i)) ;; 25699 'cd' + ) + (func (export "16s_good5") (param $i i32) (result i64) + (i64.load16_s offset=25 align=2 (local.get $i)) ;; 122 'z\0' + ) + + (func (export "32u_good1") (param $i i32) (result i64) + (i64.load32_u offset=0 (local.get $i)) ;; 1684234849 'abcd' + ) + (func (export "32u_good2") (param $i i32) (result i64) + (i64.load32_u align=1 (local.get $i)) ;; 1684234849 'abcd' + ) + (func (export "32u_good3") (param $i i32) (result i64) + (i64.load32_u offset=1 align=1 (local.get $i)) ;; 1701077858 'bcde' + ) + (func (export "32u_good4") (param $i i32) (result i64) + (i64.load32_u offset=2 align=2 (local.get $i)) ;; 1717920867 'cdef' + ) + (func (export "32u_good5") (param $i i32) (result i64) + (i64.load32_u offset=25 align=4 (local.get $i)) ;; 122 'z\0\0\0' + ) + + (func (export "32s_good1") (param $i i32) (result i64) + (i64.load32_s offset=0 (local.get $i)) ;; 1684234849 'abcd' + ) + (func (export "32s_good2") (param $i i32) (result i64) + (i64.load32_s align=1 (local.get $i)) ;; 1684234849 'abcd' + ) + (func (export "32s_good3") (param $i i32) (result i64) + (i64.load32_s offset=1 align=1 (local.get $i)) ;; 1701077858 'bcde' + ) + (func (export "32s_good4") (param $i i32) (result i64) + (i64.load32_s offset=2 align=2 (local.get $i)) ;; 1717920867 'cdef' + ) + (func (export "32s_good5") (param $i i32) (result i64) + (i64.load32_s offset=25 align=4 (local.get $i)) ;; 122 'z\0\0\0' + ) + + (func (export "64_good1") (param $i i32) (result i64) + (i64.load offset=0 (local.get $i)) ;; 0x6867666564636261 'abcdefgh' + ) + (func (export "64_good2") (param $i i32) (result i64) + (i64.load align=1 (local.get $i)) ;; 0x6867666564636261 'abcdefgh' + ) + (func (export "64_good3") (param $i i32) (result i64) + (i64.load offset=1 align=1 (local.get $i)) ;; 0x6968676665646362 'bcdefghi' + ) + (func (export "64_good4") (param $i i32) (result i64) + (i64.load offset=2 align=2 (local.get $i)) ;; 0x6a69686766656463 'cdefghij' + ) + (func (export "64_good5") (param $i i32) (result i64) + (i64.load offset=25 align=8 (local.get $i)) ;; 122 'z\0\0\0\0\0\0\0' + ) + + (func (export "8u_bad") (param $i i32) + (drop (i64.load8_u offset=4294967295 (local.get $i))) + ) + (func (export "8s_bad") (param $i i32) + (drop (i64.load8_s offset=4294967295 (local.get $i))) + ) + (func (export "16u_bad") (param $i i32) + (drop (i64.load16_u offset=4294967295 (local.get $i))) + ) + (func (export "16s_bad") (param $i i32) + (drop (i64.load16_s offset=4294967295 (local.get $i))) + ) + (func (export "32u_bad") (param $i i32) + (drop (i64.load32_u offset=4294967295 (local.get $i))) + ) + (func (export "32s_bad") (param $i i32) + (drop (i64.load32_s offset=4294967295 (local.get $i))) + ) + (func (export "64_bad") (param $i i32) + (drop (i64.load offset=4294967295 (local.get $i))) + ) +) + +(assert_return (invoke "8u_good1" (i32.const 0)) (i64.const 97)) +(assert_return (invoke "8u_good2" (i32.const 0)) (i64.const 97)) +(assert_return (invoke "8u_good3" (i32.const 0)) (i64.const 98)) +(assert_return (invoke "8u_good4" (i32.const 0)) (i64.const 99)) +(assert_return (invoke "8u_good5" (i32.const 0)) (i64.const 122)) + +(assert_return (invoke "8s_good1" (i32.const 0)) (i64.const 97)) +(assert_return (invoke "8s_good2" (i32.const 0)) (i64.const 97)) +(assert_return (invoke "8s_good3" (i32.const 0)) (i64.const 98)) +(assert_return (invoke "8s_good4" (i32.const 0)) (i64.const 99)) +(assert_return (invoke "8s_good5" (i32.const 0)) (i64.const 122)) + +(assert_return (invoke "16u_good1" (i32.const 0)) (i64.const 25185)) +(assert_return (invoke "16u_good2" (i32.const 0)) (i64.const 25185)) +(assert_return (invoke "16u_good3" (i32.const 0)) (i64.const 25442)) +(assert_return (invoke "16u_good4" (i32.const 0)) (i64.const 25699)) +(assert_return (invoke "16u_good5" (i32.const 0)) (i64.const 122)) + +(assert_return (invoke "16s_good1" (i32.const 0)) (i64.const 25185)) +(assert_return (invoke "16s_good2" (i32.const 0)) (i64.const 25185)) +(assert_return (invoke "16s_good3" (i32.const 0)) (i64.const 25442)) +(assert_return (invoke "16s_good4" (i32.const 0)) (i64.const 25699)) +(assert_return (invoke "16s_good5" (i32.const 0)) (i64.const 122)) + +(assert_return (invoke "32u_good1" (i32.const 0)) (i64.const 1684234849)) +(assert_return (invoke "32u_good2" (i32.const 0)) (i64.const 1684234849)) +(assert_return (invoke "32u_good3" (i32.const 0)) (i64.const 1701077858)) +(assert_return (invoke "32u_good4" (i32.const 0)) (i64.const 1717920867)) +(assert_return (invoke "32u_good5" (i32.const 0)) (i64.const 122)) + +(assert_return (invoke "32s_good1" (i32.const 0)) (i64.const 1684234849)) +(assert_return (invoke "32s_good2" (i32.const 0)) (i64.const 1684234849)) +(assert_return (invoke "32s_good3" (i32.const 0)) (i64.const 1701077858)) +(assert_return (invoke "32s_good4" (i32.const 0)) (i64.const 1717920867)) +(assert_return (invoke "32s_good5" (i32.const 0)) (i64.const 122)) + +(assert_return (invoke "64_good1" (i32.const 0)) (i64.const 0x6867666564636261)) +(assert_return (invoke "64_good2" (i32.const 0)) (i64.const 0x6867666564636261)) +(assert_return (invoke "64_good3" (i32.const 0)) (i64.const 0x6968676665646362)) +(assert_return (invoke "64_good4" (i32.const 0)) (i64.const 0x6a69686766656463)) +(assert_return (invoke "64_good5" (i32.const 0)) (i64.const 122)) + +(assert_return (invoke "8u_good1" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "8u_good2" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "8u_good3" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "8u_good4" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "8u_good5" (i32.const 65503)) (i64.const 0)) + +(assert_return (invoke "8s_good1" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "8s_good2" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "8s_good3" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "8s_good4" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "8s_good5" (i32.const 65503)) (i64.const 0)) + +(assert_return (invoke "16u_good1" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "16u_good2" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "16u_good3" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "16u_good4" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "16u_good5" (i32.const 65503)) (i64.const 0)) + +(assert_return (invoke "16s_good1" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "16s_good2" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "16s_good3" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "16s_good4" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "16s_good5" (i32.const 65503)) (i64.const 0)) + +(assert_return (invoke "32u_good1" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "32u_good2" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "32u_good3" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "32u_good4" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "32u_good5" (i32.const 65503)) (i64.const 0)) + +(assert_return (invoke "32s_good1" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "32s_good2" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "32s_good3" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "32s_good4" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "32s_good5" (i32.const 65503)) (i64.const 0)) + +(assert_return (invoke "64_good1" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "64_good2" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "64_good3" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "64_good4" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "64_good5" (i32.const 65503)) (i64.const 0)) + +(assert_return (invoke "8u_good1" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "8u_good2" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "8u_good3" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "8u_good4" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "8u_good5" (i32.const 65504)) (i64.const 0)) + +(assert_return (invoke "8s_good1" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "8s_good2" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "8s_good3" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "8s_good4" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "8s_good5" (i32.const 65504)) (i64.const 0)) + +(assert_return (invoke "16u_good1" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "16u_good2" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "16u_good3" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "16u_good4" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "16u_good5" (i32.const 65504)) (i64.const 0)) + +(assert_return (invoke "16s_good1" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "16s_good2" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "16s_good3" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "16s_good4" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "16s_good5" (i32.const 65504)) (i64.const 0)) + +(assert_return (invoke "32u_good1" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "32u_good2" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "32u_good3" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "32u_good4" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "32u_good5" (i32.const 65504)) (i64.const 0)) + +(assert_return (invoke "32s_good1" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "32s_good2" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "32s_good3" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "32s_good4" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "32s_good5" (i32.const 65504)) (i64.const 0)) + +(assert_return (invoke "64_good1" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "64_good2" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "64_good3" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "64_good4" (i32.const 65504)) (i64.const 0)) +(assert_trap (invoke "64_good5" (i32.const 65504)) "out of bounds memory access") + +(assert_trap (invoke "8u_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "8s_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "16u_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "16s_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "32u_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "32s_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "64_bad" (i32.const 0)) "out of bounds memory access") + +(assert_trap (invoke "8u_bad" (i32.const 1)) "out of bounds memory access") +(assert_trap (invoke "8s_bad" (i32.const 1)) "out of bounds memory access") +(assert_trap (invoke "16u_bad" (i32.const 1)) "out of bounds memory access") +(assert_trap (invoke "16s_bad" (i32.const 1)) "out of bounds memory access") +(assert_trap (invoke "32u_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "32s_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "64_bad" (i32.const 1)) "out of bounds memory access") + +;; Load f32 data with different offset/align arguments + +;; (module +;; (memory 1) +;; (data (i32.const 0) "\00\00\00\00\00\00\a0\7f\01\00\d0\7f") + +;; (func (export "32_good1") (param $i i32) (result f32) +;; (f32.load offset=0 (local.get $i)) ;; 0.0 '\00\00\00\00' +;; ) +;; (func (export "32_good2") (param $i i32) (result f32) +;; (f32.load align=1 (local.get $i)) ;; 0.0 '\00\00\00\00' +;; ) +;; (func (export "32_good3") (param $i i32) (result f32) +;; (f32.load offset=1 align=1 (local.get $i)) ;; 0.0 '\00\00\00\00' +;; ) +;; (func (export "32_good4") (param $i i32) (result f32) +;; (f32.load offset=2 align=2 (local.get $i)) ;; 0.0 '\00\00\00\00' +;; ) +;; (func (export "32_good5") (param $i i32) (result f32) +;; (f32.load offset=8 align=4 (local.get $i)) ;; nan:0x500001 '\01\00\d0\7f' +;; ) +;; (func (export "32_bad") (param $i i32) +;; (drop (f32.load offset=4294967295 (local.get $i))) +;; ) +;; ) + +;; (assert_return (invoke "32_good1" (i32.const 0)) (f32.const 0.0)) +;; (assert_return (invoke "32_good2" (i32.const 0)) (f32.const 0.0)) +;; (assert_return (invoke "32_good3" (i32.const 0)) (f32.const 0.0)) +;; (assert_return (invoke "32_good4" (i32.const 0)) (f32.const 0.0)) +;; (assert_return (invoke "32_good5" (i32.const 0)) (f32.const nan:0x500001)) + +;; (assert_return (invoke "32_good1" (i32.const 65524)) (f32.const 0.0)) +;; (assert_return (invoke "32_good2" (i32.const 65524)) (f32.const 0.0)) +;; (assert_return (invoke "32_good3" (i32.const 65524)) (f32.const 0.0)) +;; (assert_return (invoke "32_good4" (i32.const 65524)) (f32.const 0.0)) +;; (assert_return (invoke "32_good5" (i32.const 65524)) (f32.const 0.0)) + +;; (assert_return (invoke "32_good1" (i32.const 65525)) (f32.const 0.0)) +;; (assert_return (invoke "32_good2" (i32.const 65525)) (f32.const 0.0)) +;; (assert_return (invoke "32_good3" (i32.const 65525)) (f32.const 0.0)) +;; (assert_return (invoke "32_good4" (i32.const 65525)) (f32.const 0.0)) +;; (assert_trap (invoke "32_good5" (i32.const 65525)) "out of bounds memory access") + +;; (assert_trap (invoke "32_bad" (i32.const 0)) "out of bounds memory access") +;; (assert_trap (invoke "32_bad" (i32.const 1)) "out of bounds memory access") + +;; ;; Load f64 data with different offset/align arguments + +;; (module +;; (memory 1) +;; (data (i32.const 0) "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\f4\7f\01\00\00\00\00\00\fc\7f") + +;; (func (export "64_good1") (param $i i32) (result f64) +;; (f64.load offset=0 (local.get $i)) ;; 0.0 '\00\00\00\00\00\00\00\00' +;; ) +;; (func (export "64_good2") (param $i i32) (result f64) +;; (f64.load align=1 (local.get $i)) ;; 0.0 '\00\00\00\00\00\00\00\00' +;; ) +;; (func (export "64_good3") (param $i i32) (result f64) +;; (f64.load offset=1 align=1 (local.get $i)) ;; 0.0 '\00\00\00\00\00\00\00\00' +;; ) +;; (func (export "64_good4") (param $i i32) (result f64) +;; (f64.load offset=2 align=2 (local.get $i)) ;; 0.0 '\00\00\00\00\00\00\00\00' +;; ) +;; (func (export "64_good5") (param $i i32) (result f64) +;; (f64.load offset=18 align=8 (local.get $i)) ;; nan:0xc000000000001 '\01\00\00\00\00\00\fc\7f' +;; ) +;; (func (export "64_bad") (param $i i32) +;; (drop (f64.load offset=4294967295 (local.get $i))) +;; ) +;; ) + +;; (assert_return (invoke "64_good1" (i32.const 0)) (f64.const 0.0)) +;; (assert_return (invoke "64_good2" (i32.const 0)) (f64.const 0.0)) +;; (assert_return (invoke "64_good3" (i32.const 0)) (f64.const 0.0)) +;; (assert_return (invoke "64_good4" (i32.const 0)) (f64.const 0.0)) +;; (assert_return (invoke "64_good5" (i32.const 0)) (f64.const nan:0xc000000000001)) + +;; (assert_return (invoke "64_good1" (i32.const 65510)) (f64.const 0.0)) +;; (assert_return (invoke "64_good2" (i32.const 65510)) (f64.const 0.0)) +;; (assert_return (invoke "64_good3" (i32.const 65510)) (f64.const 0.0)) +;; (assert_return (invoke "64_good4" (i32.const 65510)) (f64.const 0.0)) +;; (assert_return (invoke "64_good5" (i32.const 65510)) (f64.const 0.0)) + +;; (assert_return (invoke "64_good1" (i32.const 65511)) (f64.const 0.0)) +;; (assert_return (invoke "64_good2" (i32.const 65511)) (f64.const 0.0)) +;; (assert_return (invoke "64_good3" (i32.const 65511)) (f64.const 0.0)) +;; (assert_return (invoke "64_good4" (i32.const 65511)) (f64.const 0.0)) +;; (assert_trap (invoke "64_good5" (i32.const 65511)) "out of bounds memory access") + +;; (assert_trap (invoke "64_bad" (i32.const 0)) "out of bounds memory access") +;; (assert_trap (invoke "64_bad" (i32.const 1)) "out of bounds memory access") + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/arithmetic.wast b/docs/runtimeverification-wasm-semantics/tests/simple/arithmetic.wast new file mode 100644 index 000000000..e51fd81f3 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/arithmetic.wast @@ -0,0 +1,182 @@ +(i32.const 5) +(i32.const 7) +(i32.add) +#assertTopStack < i32 > 12 "add" + +(i32.const 5) +(i32.const 7) +(i32.sub) +#assertTopStack < i32 > -2 "sub" + +(i32.const 15) +(i32.const 3) +(i32.mul) +#assertTopStack < i32 > 45 "mul" + +(i32.const 15) +(i32.const 3) +(i32.div_u) +#assertTopStack < i32 > 5 "div_u1" + +(i32.const 15) +(i32.const 2) +(i32.div_u) +#assertTopStack < i32 > 7 "div_u2" + +(i32.const 15) +(i32.const 0) +(i32.div_u) +#assertTrap "div_u3" + +(i32.const 15) +(i32.const 3) +(i32.rem_u) +#assertTopStack < i32 > 0 "rem_u1" + +(i32.const 15) +(i32.const 2) +(i32.rem_u) +#assertTopStack < i32 > 1 "rem_u2" + +(i32.const 15) +(i32.const 0) +(i32.rem_u) +#assertTrap "rem_u3" + +(i32.const 10) +(i32.const 3) +(i32.div_s) +#assertTopStack < i32 > 3 "i32.div_s 1" + +(i32.const 10) +(i32.const 4) +(i32.div_s) +#assertTopStack < i32 > 2 "i32.div_s 2" + +(i32.const 10) +(i32.const 0) +(i32.div_s) +#assertTrap "i32.div_s 3" + +(i32.const #pow1(i32)) +(i32.const #pow(i32) -Int 1) +(i32.div_s) +#assertTrap "i32.div_s 4" + +(i32.const 10) +(i32.const 5) +(i32.div_s) +#assertTopStack < i32 > 2 "div_s" + +(i32.const 91) +(i32.const 13) +(i32.rem_s) +#assertTopStack 0 "rem_s" + +(i32.const -91) +(i32.const -13) +(i32.rem_s) +#assertTopStack 0 "rem_s" + +(i32.const -1) +(i32.const -3) +(i32.rem_s) +#assertTopStack -1 "rem_s" + +(i32.const 10) +(i32.const 0) +(i32.rem_s) +#assertTrap "rem_s" + +(i32.const #pow1(i32)) +(i32.const #pow(i32) -Int 1) +(i32.rem_s) +#assertTopStack 0 "rem_s edge case" + +;; The following tests were generated using the reference OCaml WASM interpreter. + +(i32.const 10) +(i32.const 3) +(i32.rem_s) +#assertTopStack < i32 > 1 "i32.rem_s 1" + +(i32.const 10) +(i32.const 4) +(i32.rem_s) +#assertTopStack < i32 > 2 "i32.rem_s 2" + +(i32.const 10) +(i32.const 5) +(i32.rem_s) +#assertTopStack < i32 > 0 "i32.rem_s 3" + +(i32.const -10) +(i32.const 3) +(i32.div_s) +#assertTopStack < i32 > -3 "i32.div_s 3" + +(i32.const -10) +(i32.const 4) +(i32.div_s) +#assertTopStack < i32 > -2 "i32.div_s 4" + +(i32.const -10) +(i32.const 5) +(i32.div_s) +#assertTopStack < i32 > -2 "i32.div_s 5" + +(i32.const -10) +(i32.const 3) +(i32.rem_s) +#assertTopStack < i32 > -1 "i32.rem_s 4" + +(i32.const -10) +(i32.const 4) +(i32.rem_s) +#assertTopStack < i32 > -2 "i32.rem_s 5" + +(i32.const -10) +(i32.const 5) +(i32.rem_s) +#assertTopStack < i32 > 0 "i32.rem_s 6" + +(i32.const -10) +(i32.const -3) +(i32.div_s) +#assertTopStack < i32 > 3 "i32.div_s 6" + +(i32.const -10) +(i32.const -4) +(i32.div_s) +#assertTopStack < i32 > 2 "i32.div_s 7" + +(i32.const -10) +(i32.const -5) +(i32.div_s) +#assertTopStack < i32 > 2 "i32.div_s 8" + +(i32.const -10) +(i32.const -3) +(i32.rem_s) +#assertTopStack < i32 > -1 "i32.rem_s 7" + +(i32.const -10) +(i32.const -4) +(i32.rem_s) +#assertTopStack < i32 > -2 "i32.rem_s 8" + +(i32.const -10) +(i32.const -5) +(i32.rem_s) +#assertTopStack < i32 > 0 "i32.rem_s 9" + +(i32.add (i32.const 3) (i32.const 4)) +#assertTopStack < i32 > 7 "simple add folded" + +(i32.sub (i32.const 3) (i32.const 4)) +#assertTopStack < i32 > -1 "simple sub, order dependent folded" + +(i32.sub (i32.mul (i32.const 5) (i32.const 7)) (i32.const 4)) +#assertTopStack < i32 > 31 "mul nested in sub folded" + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/bitwise.wast b/docs/runtimeverification-wasm-semantics/tests/simple/bitwise.wast new file mode 100644 index 000000000..0791e6676 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/bitwise.wast @@ -0,0 +1,152 @@ +(i32.const 20) +(i32.const 18) +(i32.and) +#assertTopStack < i32 > 16 "and" + +(i32.const 20) +(i32.const 18) +(i32.or) +#assertTopStack < i32 > 22 "or" + +(i32.const 20) +(i32.const 18) +(i32.xor) +#assertTopStack < i32 > 6 "xor" + +(i32.const 2) +(i32.const 1) +(i32.shl) +#assertTopStack < i32 > 4 "shl 1" + +(i32.const 2) +(i32.const #pow1(i32) +Int 1) +(i32.shl) +#assertTopStack < i32 > 4 "shl 2" + +(i32.const #pow1(i32)) +(i32.const 2) +(i32.shr_u) +#assertTopStack < i32 > 2 ^Int 29 "shr_u 1" + +(i32.const 2) +(i32.const 2) +(i32.shr_u) +#assertTopStack < i32 > 0 "shr_u 2" + +(i32.const #pow(i32) -Int 2) +(i32.const 1) +(i32.shr_s) +#assertTopStack < i32 > #pow(i32) -Int 1 "shr_s 1" + +(i32.const 2) +(i32.const 2) +(i32.shr_s) +#assertTopStack < i32 > 0 "shr_s 2" + +(i32.const #pow1(i32) +Int 2) +(i32.const 3) +(i32.rotl) +#assertTopStack < i32 > 20 "rotl" + +(i32.const #pow1(i32) +Int 16) +(i32.const 3) +(i32.rotr) +#assertTopStack < i32 > 2 ^Int 28 +Int 2 "rotr" + +;; clz + +(i32.const #pow1(i32)) +(i32.clz) +#assertTopStack < i32 > 0 "clz #pow1(i32)" +(i64.const #pow1(i64)) +(i64.clz) +#assertTopStack < i64 > 0 "clz #pow1(i62)" + +(i32.const 0) +(i32.clz) +#assertTopStack < i32 > 32 "clz 0" +(i64.const 0) +(i64.clz) +#assertTopStack < i64 > 64 "clz 0" + +(i32.const 1) +(i32.clz) +#assertTopStack < i32 > 31 "clz 1" +(i64.const 1) +(i64.clz) +#assertTopStack < i64 > 63 "clz 1" + +(i32.const 2 ^Int 32 -Int 1) +(i32.clz) +#assertTopStack < i32 > 0 "clz 2^32 - 1" +(i64.const 2 ^Int 64 -Int 1) +(i64.clz) +#assertTopStack < i64 > 0 "clz 2^64 - 1" + +(i32.const 2 ^Int 31 -Int 1) +(i32.clz) +#assertTopStack < i32 > 1 "clz 2^31 - 1" +(i64.const 2 ^Int 63 -Int 1) +(i64.clz) +#assertTopStack < i64 > 1 "clz 2^63 - 1" + +;; ctz +(i32.const #pow1(i32)) +(i32.ctz) +#assertTopStack < i32 > 31 "ctz #pow1(i32)" +(i64.const #pow1(i64)) +(i64.ctz) +#assertTopStack < i64 > 63 "ctz #pow1(i32)" + +(i32.const 0) +(i32.ctz) +#assertTopStack < i32 > 32 "ctz 0" +(i64.const 0) +(i64.ctz) +#assertTopStack < i64 > 64 "ctz 0" + +(i32.const 1) +(i32.ctz) +#assertTopStack < i32 > 0 "ctz 1" +(i64.const 1) +(i64.ctz) +#assertTopStack < i64 > 0 "ctz 1" + +(i32.const 2 ^Int 32 -Int 1) +(i32.ctz) +#assertTopStack < i32 > 0 "ctz 2^32 - 1" +(i64.const 2 ^Int 64 -Int 1) +(i64.ctz) +#assertTopStack < i64 > 0 "ctz 2^64 - 1" + +;; popcnt + +(i32.const #pow1(i32)) +(i32.popcnt) +#assertTopStack < i32 > 1 "popcnt #pow1(i32)" +(i64.const #pow1(i64)) +(i64.popcnt) +#assertTopStack < i64 > 1 "popcnt #pow1(i32)" + +(i32.const 0) +(i32.popcnt) +#assertTopStack < i32 > 0 "popcnt 0" +(i64.const 0) +(i64.popcnt) +#assertTopStack < i64 > 0 "popcnt 0" + +(i32.const 1) +(i32.popcnt) +#assertTopStack < i32 > 1 "popcnt 1" +(i64.const 1) +(i64.popcnt) +#assertTopStack < i64 > 1 "popcnt 1" + +(i32.const 2 ^Int 32 -Int 1) +(i32.popcnt) +#assertTopStack < i32 > 32 "popcnt 2^32 - 1" +(i64.const 2 ^Int 64 -Int 1) +(i64.popcnt) +#assertTopStack < i64 > 64 "popcnt 2^64 - 1" + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/branching.wast b/docs/runtimeverification-wasm-semantics/tests/simple/branching.wast new file mode 100644 index 000000000..ee7121b0c --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/branching.wast @@ -0,0 +1,20 @@ +(module + (func (export "as-if-cond") (param i32) (result i32) + (block (result i32) + (if (result i32) + (br_if 0 (i32.const 1) (local.get 0)) + (then (i32.const 2)) + (else (i32.const 3)) + ) + ) + ) + + (func (export "to-top-level0") (br 0)) + (func (export "to-top-level1") (block (br 0))) +) + +(assert_return (invoke "as-if-cond" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "to-top-level0")) +(assert_return (invoke "to-top-level1")) + +#clearConfig \ No newline at end of file diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/comments.wast b/docs/runtimeverification-wasm-semantics/tests/simple/comments.wast new file mode 100644 index 000000000..2071e8b9e --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/comments.wast @@ -0,0 +1,23 @@ +(i32.const 7) +;; this should be ignored +(i32.const 8) ;; this should be ignored as well +(i32.add) + +(; +all this text +should be ignored +;) + +#assertTopStack < i32 > 15 "dummy test 1" + +(i32.const -3) +(i32.const 6) (; comment at end of line ;) +(i32.add) +#assertTopStack < i32 > 3 "dummy test 2" + +(i32.const -3) +(i32.(;comment in the middle;)const 6) +(i32.add) +#assertTopStack < i32 > 3 "dummy test 2" + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/comparison.wast b/docs/runtimeverification-wasm-semantics/tests/simple/comparison.wast new file mode 100644 index 000000000..ae7f0f441 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/comparison.wast @@ -0,0 +1,115 @@ +(i32.const 0) +(i32.eqz) +#assertTopStack < i32 > 1 "eqz1" + +(i32.const 3) +(i32.eqz) +#assertTopStack < i32 > 0 "eqz2" + +(i32.eqz (i32.const 3)) +#assertTopStack < i32 > 0 "eqz folded" + +(i32.const 3) +(i32.const 3) +(i32.eq) +#assertTopStack < i32 > 1 "eq1" + +(i32.const 3) +(i32.const 4) +(i32.eq) +#assertTopStack < i32 > 0 "eq2" + +(i32.const 3) +(i32.const 3) +(i32.ne) +#assertTopStack < i32 > 0 "ne1" + +(i32.const 3) +(i32.const 4) +(i32.ne) +#assertTopStack < i32 > 1 "ne2" + +(i32.const 2) +(i32.const 32) +(i32.lt_u) +#assertTopStack < i32 > 1 "lt_u" + +(i32.lt_u (i32.const 32) (i32.const 2)) +#assertTopStack < i32 > 0 "lt_u" + +(i32.const 2) +(i32.const 32) +(i32.gt_u) +#assertTopStack < i32 > 0 "gt_u" + +(i32.const #pow1(i32) +Int 7) +(i32.const #pow1(i32) +Int 15) +(i32.lt_s) +#assertTopStack < i32 > 1 "lt_s 1" + +(i32.const -32) +(i32.const 32) +(i32.lt_s) +#assertTopStack < i32 > 1 "lt_s 2" + +(i32.const #pow1(i32) +Int 7) +(i32.const #pow1(i32) +Int 15) +(i32.gt_s) +#assertTopStack < i32 > 0 "gt_s 1" + +(i32.const -32) +(i32.const 32) +(i32.gt_s) +#assertTopStack < i32 > 0 "gt_s 2" + +(i32.const 2) +(i32.const 32) +(i32.le_u) +#assertTopStack < i32 > 1 "le_u 1" + +(i32.const 32) +(i32.const 32) +(i32.le_u) +#assertTopStack < i32 > 1 "le_u 2" + +(i32.const 2) +(i32.const 32) +(i32.ge_u) +#assertTopStack < i32 > 0 "ge_u 1" + +(i32.const 32) +(i32.const 32) +(i32.ge_u) +#assertTopStack < i32 > 1 "ge_u 2" + +(i32.const #pow1(i32) +Int 7) +(i32.const #pow1(i32) +Int 15) +(i32.le_s) +#assertTopStack < i32 > 1 "le_s 1" + +(i32.const 32) +(i32.const 32) +(i32.le_s) +#assertTopStack < i32 > 1 "le_s 2" + +(i32.const -32) +(i32.const 32) +(i32.le_s) +#assertTopStack < i32 > 1 "le_s 3" + +(i32.const #pow1(i32) +Int 7) +(i32.const #pow1(i32) +Int 15) +(i32.ge_s) +#assertTopStack < i32 > 0 "ge_s 1" + +(i32.const 32) +(i32.const 32) +(i32.ge_s) +#assertTopStack < i32 > 1 "ge_s 2" + +(i32.const -32) +(i32.const 32) +(i32.ge_s) +#assertTopStack < i32 > 0 "ge_s 3" + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/constants.wast b/docs/runtimeverification-wasm-semantics/tests/simple/constants.wast new file mode 100644 index 000000000..f221feda1 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/constants.wast @@ -0,0 +1,78 @@ +;; Integers +;; -------- + +(i32.const 3) +#assertTopStack < i32 > 3 "i32 1" + +(i32.const 5) +#assertTopStack < i32 > 5 "i32 parens" + +(i64.const 71) +#assertTopStack < i64 > 71 "i64" + +(i32.const #unsigned(i32, -5)) +#assertTopStack < i32 > #pow(i32) -Int 5 "i32 manual unsigned" + +(i32.const #pow(i32) -Int 5) +#assertTopStack < i32 > -5 "i32 manual unsigned" + +(i32.const -5) +#assertTopStack < i32 > #unsigned(i32, -5) "i32 signed constant" + +(i32.const #unsigned(i32, -5)) +#assertTopStack < i32 > -5 "i32 signed assert" + +(i32.const #pow(i32) +Int 1) +#assertTopStack < i32 > 1 "i32 overflow" + +(i32.const -1) +#assertTopStackExactly < i32 > #pow(i32) -Int 1 "i32 overflow" + +(i64.const -1) +#assertTopStackExactly < i64 > #pow(i64) -Int 1 "i62 overflow" + +;; Floating point +;; -------------- + +(f32.const 3.245) +#assertTopStack < f32 > 3.245 "f32" + +(f64.const 3.234523) +#assertTopStack < f64 > 3.234523 "f32 parens" + +(f64.const 1.21460644e+52) +#assertTopStack < f64 > 1.21460644e+52 "f64 scientific 1" + +(f64.const 1.6085927714e-321) +#assertTopStack < f64 > 1.6085927714e-321 "f64 scientific 2" + +(f64.const 1.63176601e-302) +#assertTopStack < f64 > 1.63176601e-302 "f64 scientific 3" + +;; Below examples do not work with current float parser +;; (f64.const 0x1.da21c460a6f44p+52) +;; (f64.const 0x1.60859d2e7714ap-321) +;; (f64.const 0x1.e63f1b7b660e1p-302) + +;; Helper conversions +;; ------------------ + +(i32.const #unsigned(i32, #signed(i32, 0))) +#assertTopStack < i32 > 0 "#unsigned . #signed 1" + +(i32.const #unsigned(i32, #signed(i32, #pow1(i32)))) +#assertTopStack < i32 > #pow1(i32) "#unsigned . #signed 2" + +(i32.const #unsigned(i32, #signed(i32, #pow(i32) -Int 1))) +#assertTopStack < i32 > #pow(i32) -Int 1 "#unsigned . #signed 3" + +(i64.const #unsigned(i64, #signed(i64, 0))) +#assertTopStack < i64 > 0 "#unsigned . #signed 4" + +(i64.const #unsigned(i64, #signed(i64, #pow1(i64)))) +#assertTopStack < i64 > #pow1(i64) "#unsigned . #signed 5" + +(i64.const #unsigned(i64, #signed(i64, #pow(i64) -Int 1))) +#assertTopStack < i64 > #pow(i64) -Int 1 "#unsigned . #signed 6" + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/control-flow.wast b/docs/runtimeverification-wasm-semantics/tests/simple/control-flow.wast new file mode 100644 index 000000000..9b7eadab2 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/control-flow.wast @@ -0,0 +1,209 @@ +;; Unreachable +( unreachable ) +#assertTrap "unreachable" + +;; Blocks + +block (result i32 i32 i32) + (i32.const 1) + (i32.const 2) + (i32.const 3) +end +#assertStack < i32 > 3 : < i32 > 2 : < i32 > 1 : .ValStack "block 1" + +block (result i32 i32) + (i32.const 1) + (i32.const 2) + (i32.const 3) + (drop) +end +#assertStack < i32 > 2 : < i32 > 1 : .ValStack "block 2" + +block (result i32 i32) + (i32.const 1) + (i32.const 2) + (i32.const 3) +end +#assertStack < i32 > 3 : < i32 > 2 : .ValStack "block 3 (invalid)" + +;; (block (result i32) +;; (i32.const 1) +;; ) +;; #assertTopStack < i32 > 1 "block with named result 1" + +;; (block result i64 i32 +;; (i32.const 2) +;; (i32.const 1) +;; (i64.const 5) +;; ) +;; #assertStack < i64 > 5 : < i32 > 1 : .ValStack "block with named result 2" + +;; Breaks + +(i32.const 1) +(i32.const 2) +block + (i32.const 3) + (br 0) + (i32.const 4) + (br 0) +end +#assertStack < i32 > 2 : < i32 > 1 : .ValStack "br 1" + +(i32.const 1) +(i32.const 2) +block + (i32.const 3) + block (result i32 i32) + (i32.const 4) + (i32.const 5) + (br 1) + end + (i32.const 6) + (br 0) +end +#assertStack < i32 > 2 : < i32 > 1 : .ValStack "br 2" + +(i32.const 1) +(i32.const 2) +block (result i32 i32) + (i32.const 3) + block (result i32 i32) + (i32.const 4) + (i32.const 5) + (br 1) + end + (i32.const 6) + (br 0) +end +#assertStack < i32 > 5 : < i32 > 4 : < i32 > 2 : < i32 > 1 : .ValStack "br 3" + +(i32.const 1) +(i32.const 2) +block (result i32 i32) + (i32.const 3) + block + (i32.const 4) + (i32.const 5) + (br 1) + end + (i32.const 6) + (br 0) +end +#assertStack < i32 > 5 : < i32 > 4 : < i32 > 2 : < i32 > 1 : .ValStack "br 4" + +(i32.const 1) +(i32.const 2) +block (result i32) + (i32.const 3) + (i32.const 0) + (br_if 0) + (i32.const 4) + (br 0) +end +#assertStack < i32 > 4 : < i32 > 2 : < i32 > 1 : .ValStack "br_if 1 false" + +(i32.const 1) +(i32.const 2) +block (result i32) + (i32.const 3) + (i32.const 1) + (br_if 0) + (i32.const 4) + (br 0) +end +#assertStack < i32 > 3 : < i32 > 2 : < i32 > 1 : .ValStack "br_if 1 true" + +(i32.const 1) +(i32.const 2) +block + (i32.const 3) + (i32.const 1) + (br_if 0) + (i32.const 4) + (br 0) +end +#assertStack < i32 > 2 : < i32 > 1 : .ValStack "br_if 2 true" + +;; Conditional + +(i32.const 1) +if (result i32) i32.const 1 else i32.const -1 end +#assertTopStack < i32 > 1 "if true" + +(i32.const 0) +if (result i32) i32.const 1 else i32.const -1 end +#assertTopStack < i32 > -1 "if false" + +;; (i32.const -1) +;; (if (i32.const 0) (then)) +;; #assertTopStack < i32 > -1 "if folded false empty" + +;; (i32.const -1) +;; (if (result i32) (i32.const 1) (then (i32.const 1)) (else (i32.const 2))) +;; #assertStack < i32 > 1 : < i32 > -1 : .ValStack "if folded true" + +;; (i32.const -1) +;; (if (result i32) (i32.const 0) (then (i32.const 1)) (else (i32.const 2))) +;; #assertStack < i32 > 2 : < i32 > -1 : .ValStack "if folded false" + +;; (if (result i32) (i32.const 1) (then (unreachable)) (else (i32.const 1))) +;; #assertTrap "if lazy first branch true" + +;; (if (result i32) (i32.const 0) (then (unreachable)) (else (i32.const 1))) +;; #assertTopStack < i32 > 1 "if lazy first branch false" + +;; (if (result i32) (i32.const 1) (then (i32.const -1)) (else (unreachable))) +;; #assertTopStack < i32 > -1 "if lazy second branch true" + +;; (if (result i32) (i32.const 0) (then (i32.const -1)) (else (unreachable))) +;; #assertTrap "if lazy second branch false" + +;; (if (result i32) (unreachable) (then (i32.const -1)) (else (unreachable))) +;; #assertTrap "if strict condition" + +;; Looping + +init_locals < i32 > 10 : < i32 > 0 : .ValStack +loop + (local.get 0) + (local.get 1) + (i32.add) + (local.set 1) + (local.get 0) + (i32.const 1) + (i32.sub) + (local.tee 0) + (br_if 0) +end +#assertLocal 0 < i32 > 0 "sum 1 -> 10 loop" +#assertLocal 1 < i32 > 55 "sum 1 -> 10 loop" + +init_locals < i32 > 10 : < i32 > 0 : .ValStack +block + loop + (local.get 0) + (local.get 1) + (i32.add) + (local.set 1) + (local.get 0) + (i32.const 1) + (i32.sub) + (local.tee 0) + (i32.eqz) + (br_if 1) + (br 0) + end +end +#assertLocal 0 < i32 > 0 "sum 1 -> 10 loop concrete syntax" +#assertLocal 1 < i32 > 55 "sum 1 -> 10 loop concrete syntax" + +;; Stack Underflow +;; TODO: We need to give semantics to stack underflow (though it could not happen with a validated program). +;; We need `trap` semantics first. +;; (i32.const 0) +;; block [ i32 i32 ] +;; (i32.const 7) +;; end + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/conversion.wast b/docs/runtimeverification-wasm-semantics/tests/simple/conversion.wast new file mode 100644 index 000000000..c697026fe --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/conversion.wast @@ -0,0 +1,47 @@ +;; Wrap. + +(i64.const 4294967296) ;; 2^32 +(i32.wrap_i64) +#assertTopStack < i32 > 0 "wrap 2^32" + +(i64.const 4294967295) ;; 2^32 - 1 +(i32.wrap_i64) +#assertTopStack < i32 > 4294967295 "wrap 2^32 - 1" + +(i32.wrap_i64 (i64.const 4294967298)) +#assertTopStack < i32 > 2 "folded wrap 2^32 + 2" + +;; Extend. + +(i32.const 4294967295) ;; 2^32 - 1 +(i64.extend_i32_u) +#assertTopStack < i64 > 4294967295 "extend unsig" + +(i32.const -1) ;; 2^32 - 1 +(i64.extend_i32_s) +#assertTopStack < i64 > -1 "extend sig" + +(i64.extend_i32_s (i32.const 15)) +#assertTopStack < i64 > 15 "folded extend sig" + +(module + (func (export "i64.extend_i32_s") (param $x i32) (result i64) (i64.extend_i32_s (local.get $x))) + (func (export "i64.extend_i32_u") (param $x i32) (result i64) (i64.extend_i32_u (local.get $x))) + (func (export "i32.wrap_i64") (param $x i64) (result i32) (i32.wrap_i64 (local.get $x))) +) + +(assert_return (invoke "i64.extend_i32_s" (i32.const 0)) (i64.const 0)) +(assert_return (invoke "i64.extend_i32_s" (i32.const 10000)) (i64.const 10000)) +(assert_return (invoke "i64.extend_i32_s" (i32.const -10000)) (i64.const -10000)) +(assert_return (invoke "i64.extend_i32_s" (i32.const -1)) (i64.const -1)) +(assert_return (invoke "i64.extend_i32_s" (i32.const 0x7fffffff)) (i64.const 0x000000007fffffff)) +(assert_return (invoke "i64.extend_i32_s" (i32.const 0x80000000)) (i64.const 0xffffffff80000000)) + +(assert_return (invoke "i64.extend_i32_u" (i32.const 0)) (i64.const 0)) +(assert_return (invoke "i64.extend_i32_u" (i32.const 10000)) (i64.const 10000)) +(assert_return (invoke "i64.extend_i32_u" (i32.const -10000)) (i64.const 0x00000000ffffd8f0)) +(assert_return (invoke "i64.extend_i32_u" (i32.const -1)) (i64.const 0xffffffff)) +(assert_return (invoke "i64.extend_i32_u" (i32.const 0x7fffffff)) (i64.const 0x000000007fffffff)) +(assert_return (invoke "i64.extend_i32_u" (i32.const 0x80000000)) (i64.const 0x0000000080000000)) + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/data.wast b/docs/runtimeverification-wasm-semantics/tests/simple/data.wast new file mode 100644 index 000000000..b036ad7ff --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/data.wast @@ -0,0 +1,91 @@ +;; Instantiating with data + +(module +(memory $an-ident (data "WASM" "2\2E0")) +) + +(memory.size) + +#assertTopStack < i32 > 1 "size of stack" +#assertMemoryData (0, 87) "text to ascii W" +#assertMemoryData (1, 65) "text to ascii A" +#assertMemoryData (2, 83) "text to ascii S" +#assertMemoryData (3, 77) "text to ascii M" +#assertMemoryData (4, 50) "text to ascii 2" +#assertMemoryData (5, 46) "text to ascii ." +#assertMemoryData (6, 48) "text to ascii 0" +#assertMemory $an-ident 1 1 "memorys string length" + +#clearConfig + +(module +(memory 1 1) +(data (offset (i32.const 100)) "W" "AS" "M") +) +#assertMemoryData (100, 87) "text to ascii W" +#assertMemoryData (101, 65) "text to ascii A" +#assertMemoryData (102, 83) "text to ascii S" +#assertMemoryData (103, 77) "text to ascii M" +#assertMemory 0 1 1 "memorys string length" + +#clearConfig + +(module +(memory (data)) +) +#clearConfig + +(module +(memory (data "W")) +) +#assertMemoryData (0, 87) "text to ascii W" +#assertMemory 0 1 1 "memorys string length" + +#clearConfig + +(module +(memory (data "\"\t\n\n\t\'\"\r\u{090A}")) +) +#assertMemoryData (0, 34) "text to ascii special" +#assertMemoryData (1, 9) "text to ascii special" +#assertMemoryData (2, 10) "text to ascii special" +#assertMemoryData (3, 10) "text to ascii special" +#assertMemoryData (4, 9) "text to ascii special" +#assertMemoryData (5, 39) "text to ascii special" +#assertMemoryData (6, 34) "text to ascii special" + +(module + (memory $m 1 1) + (data (offset (i32.const 0)) "\00") + (data (offset (nop) (i32.const 1)) "\01") + (data (offset (i32.const 2) (nop)) "\02") + (data $m (offset (i32.const 3)) "\03") + (data $m (offset (nop) (i32.const 4)) "\04") + (data $m (offset (i32.const 5) (nop)) "\05") + + (data (offset (i32.const 6 (nop))) "\06") + (data $m (offset (i32.const 7 (nop))) "\07") + + (global $g i32 (i32.const 8)) + (global $h i32 (i32.const 9)) + + (data (offset (global.get $g)) "\08") + (data $m (offset (global.get $h)) "\09") + + (func $main (local i32) + (local.set 0 (i32.const 9)) + loop + (i32.load8_u (local.get 0)) + (local.get 0) + (if (i32.ne) (then (unreachable))) + (i32.sub (local.get 0) (i32.const 1)) + (local.tee 0) + (i32.eqz) + (br_if 1) + (br 0) + end + ) + + (start $main) +) +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/desugaring.wast b/docs/runtimeverification-wasm-semantics/tests/simple/desugaring.wast new file mode 100644 index 000000000..d3a730506 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/desugaring.wast @@ -0,0 +1,61 @@ +(module + (func (export "foo") (result i32) (i32.const 0)) + (func (export "bar") (result i32) (i32.const 1)) +) + +(register "a") + +;; Test that imports get ordered correctly. +;; Function "bar" should get index 0, since it is imported first. +(module + (func (import "a" "bar") (result i32)) + (import "a" "foo" (func (result i32))) + (export "bar" (func 0) ) +) + +(assert_return (invoke "bar") (i32.const 1)) + +;; Test that data initializations get ordered correctly. +;; The results of the inlined `data` should overwrite the results of the non-inlined data. +(module + (data (offset (i32.const 0)) "b") + (memory (data "a")) + (func (export "baz") (result i32) + (i32.load (i32.const 0)) + ) +) + +(assert_return (invoke "baz") (i32.const 97)) + +;; Same as above but for `elem` +(module + (elem (offset (i32.const 0)) 0) + (table funcref (elem 1)) + (func (result i32) (i32.const 0)) + (func (result i32) (i32.const 1)) + (func (export "biz") (result i32) + (call_indirect (result i32) (i32.const 0)) + ) +) + +(assert_return (invoke "biz") (i32.const 1)) + +;; Regression test: A module with hex integers inside a function after a `table` with inline `elem`. +(module + (table funcref (elem)) + (func (export "break-inner") (result i32) + (local i32) + (local.set 0 (i32.const 0)) + (local.set 0 (i32.add (local.get 0) (block (result i32) (block (result i32) (br 1 (i32.const 0x1)))))) + (local.set 0 (i32.add (local.get 0) (block (result i32) (block (br 0)) (i32.const 0x2)))) + (local.set 0 + (i32.add (local.get 0) (block (result i32) (i32.ctz (br 0 (i32.const 0x4))))) + ) + (local.set 0 + (i32.add (local.get 0) (block (result i32) (i32.ctz (block (result i32) (br 1 (i32.const 0x8)))))) + ) + (local.get 0) + ) +) + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/f64-cs.wast b/docs/runtimeverification-wasm-semantics/tests/simple/f64-cs.wast new file mode 100644 index 000000000..690686e27 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/f64-cs.wast @@ -0,0 +1,153 @@ +;; Test all the f64 operators on major boundary values and all special +;; values (except comparison and bitwise operators, which are tested in +;; f64_bitwise.wast and f64_cmp.wast). + +(module + (func (export "add") (param $x f64) (param $y f64) (result f64) (f64.add (local.get $x) (local.get $y))) + (func (export "sub") (param $x f64) (param $y f64) (result f64) (f64.sub (local.get $x) (local.get $y))) + (func (export "mul") (param $x f64) (param $y f64) (result f64) (f64.mul (local.get $x) (local.get $y))) + (func (export "div") (param $x f64) (param $y f64) (result f64) (f64.div (local.get $x) (local.get $y))) + (func (export "sqrt") (param $x f64) (result f64) (f64.sqrt (local.get $x))) + (func (export "min") (param $x f64) (param $y f64) (result f64) (f64.min (local.get $x) (local.get $y))) + (func (export "max") (param $x f64) (param $y f64) (result f64) (f64.max (local.get $x) (local.get $y))) + (func (export "ceil") (param $x f64) (result f64) (f64.ceil (local.get $x))) + (func (export "floor") (param $x f64) (result f64) (f64.floor (local.get $x))) + (func (export "trunc") (param $x f64) (result f64) (f64.trunc (local.get $x))) + (func (export "nearest") (param $x f64) (result f64) (f64.nearest (local.get $x))) +) + +(assert_return (invoke "add" (f64.const -0.000000e+00) (f64.const -0.000000e+00)) (f64.const -0.000000e+00)) +(assert_return (invoke "add" (f64.const -0.000000e+00) (f64.const -2.225074e-308)) (f64.const -2.225074e-308)) +(assert_return (invoke "add" (f64.const 5.000000e-01) (f64.const -Infinity)) (f64.const -Infinity)) +(assert_return (invoke "add" (f64.const 5.000000e-01) (f64.const Infinity)) (f64.const Infinity)) + +(assert_return (invoke "sub" (f64.const -0.000000e+00) (f64.const -0.000000e+00)) (f64.const 0.000000e+00)) +(assert_return (invoke "sub" (f64.const 0.000000e+00) (f64.const 1.797693e+308)) (f64.const -1.797693e+308)) +(assert_return (invoke "sub" (f64.const -0.000000e+00) (f64.const -Infinity)) (f64.const Infinity)) +(assert_return (invoke "sub" (f64.const -0.000000e+00) (f64.const Infinity)) (f64.const -Infinity)) +(assert_return (invoke "sub" (f64.const 5.000000e-01) (f64.const -1.797693e+308)) (f64.const 1.797693e+308)) +(assert_return (invoke "sub" (f64.const 5.000000e-01) (f64.const 1.797693e+308)) (f64.const -1.797693e+308)) +(assert_return (invoke "sub" (f64.const -5.000000e-01) (f64.const -Infinity)) (f64.const Infinity)) +(assert_return (invoke "sub" (f64.const 1.000000e+00) (f64.const 1.797693e+308)) (f64.const -1.797693e+308)) +(assert_return (invoke "sub" (f64.const Infinity) (f64.const 1.000000e+00)) (f64.const Infinity)) +(assert_return (invoke "sub" (f64.const -Infinity) (f64.const -6.283185e+00)) (f64.const -Infinity)) +(assert_return (invoke "mul" (f64.const -4.940656e-324) (f64.const -0.000000e+00)) (f64.const 0.000000e+00)) +(assert_return (invoke "mul" (f64.const 2.225074e-308) (f64.const -2.225074e-308)) (f64.const -0.000000e+00)) +(assert_return (invoke "mul" (f64.const 2.225074e-308) (f64.const 2.225074e-308)) (f64.const 0.000000e+00)) +(assert_return (invoke "mul" (f64.const -2.225074e-308) (f64.const -5.000000e-01)) (f64.const 1.112537e-308)) +(assert_return (invoke "mul" (f64.const -2.225074e-308) (f64.const 5.000000e-01)) (f64.const -1.112537e-308)) +(assert_return (invoke "mul" (f64.const 2.225074e-308) (f64.const -5.000000e-01)) (f64.const -1.112537e-308)) +(assert_return (invoke "mul" (f64.const 5.000000e-01) (f64.const -5.000000e-01)) (f64.const -2.500000e-01)) +(assert_return (invoke "div" (f64.const -0.000000e+00) (f64.const -4.940656e-324)) (f64.const 0.000000e+00)) +(assert_return (invoke "div" (f64.const 0.000000e+00) (f64.const 5.000000e-01)) (f64.const 0.000000e+00)) +(assert_return (invoke "div" (f64.const -0.000000e+00) (f64.const -1.000000e+00)) (f64.const 0.000000e+00)) +(assert_return (invoke "div" (f64.const -0.000000e+00) (f64.const 1.000000e+00)) (f64.const -0.000000e+00)) +(assert_return (invoke "div" (f64.const 0.000000e+00) (f64.const Infinity)) (f64.const 0.000000e+00)) +;; (assert_return_canonical_nan (invoke "div" (f64.const -0x0p+0) (f64.const -nan))) +;; (assert_return_arithmetic_nan (invoke "div" (f64.const -0x0p+0) (f64.const -nan:0x4000000000000))) +;; (assert_return_canonical_nan (invoke "div" (f64.const -0x0p+0) (f64.const nan))) +;; (assert_return_arithmetic_nan (invoke "div" (f64.const -0x0p+0) (f64.const nan:0x4000000000000))) +;; (assert_return_canonical_nan (invoke "div" (f64.const 0x0p+0) (f64.const -nan))) +;; (assert_return_arithmetic_nan (invoke "div" (f64.const 0x0p+0) (f64.const -nan:0x4000000000000))) +;; (assert_return_canonical_nan (invoke "div" (f64.const 0x0p+0) (f64.const nan))) +;; (assert_return_arithmetic_nan (invoke "div" (f64.const 0x0p+0) (f64.const nan:0x4000000000000))) +(assert_return (invoke "div" (f64.const -4.940656e-308) (f64.const -0.000000e+00)) (f64.const Infinity)) +(assert_return (invoke "div" (f64.const 4.940656e-308) (f64.const -0.000000e+00)) (f64.const -Infinity)) +(assert_return (invoke "div" (f64.const 2.225074e-308) (f64.const 0.000000e+00)) (f64.const Infinity)) +(assert_return (invoke "div" (f64.const -2.225074e-308) (f64.const -4.940656e-324)) (f64.const 4.503600e+15)) +(assert_return (invoke "div" (f64.const 2.225074e-308) (f64.const Infinity)) (f64.const 0.000000e+00)) +(assert_return (invoke "min" (f64.const 0.000000e+00) (f64.const -1.797693e+308)) (f64.const -1.797693e+308)) +(assert_return (invoke "min" (f64.const 0.000000e+00) (f64.const 1.797693e+308)) (f64.const 0.000000e+00)) +(assert_return (invoke "min" (f64.const -0.000000e+00) (f64.const -Infinity)) (f64.const -Infinity)) +(assert_return (invoke "min" (f64.const -4.940656e-324) (f64.const -1.797693e+308)) (f64.const -1.797693e+308)) +(assert_return (invoke "max" (f64.const 0.000000e+00) (f64.const 2.225074e-308)) (f64.const 2.225074e-308)) +(assert_return (invoke "max" (f64.const -0.000000e+00) (f64.const -5.000000e-01)) (f64.const -0.000000e+00)) +(assert_return (invoke "max" (f64.const -0.000000e+00) (f64.const 5.000000e-01)) (f64.const 5.000000e-01)) +(assert_return (invoke "max" (f64.const 0.000000e+00) (f64.const -5.000000e-01)) (f64.const 0.000000e+00)) +(assert_return (invoke "max" (f64.const 0.000000e+00) (f64.const 5.000000e-01)) (f64.const 5.000000e-01)) +(assert_return (invoke "max" (f64.const -0.000000e+00) (f64.const -1.000000e+00)) (f64.const -0.000000e+00)) +(assert_return (invoke "max" (f64.const -0.000000e+00) (f64.const 1.000000e+00)) (f64.const 1.000000e+00)) +(assert_return (invoke "max" (f64.const 0.000000e+00) (f64.const -1.000000e+00)) (f64.const 0.000000e+00)) +(assert_return (invoke "max" (f64.const 0.000000e+00) (f64.const 1.000000e+00)) (f64.const 1.000000e+00)) +(assert_return (invoke "max" (f64.const 1.000000e+00) (f64.const 1.797693e+308)) (f64.const 1.797693e+308)) +(assert_return (invoke "max" (f64.const -1.000000e+00) (f64.const -Infinity)) (f64.const -1.000000e+00)) +(assert_return (invoke "max" (f64.const -1.000000e+00) (f64.const Infinity)) (f64.const Infinity)) +;; (assert_return_canonical_nan (invoke "sqrt" (f64.const -0x1p-1))) +(assert_return (invoke "sqrt" (f64.const 5.000000e-01)) (f64.const 7.071068e-01)) +;; (assert_return_canonical_nan (invoke "sqrt" (f64.const -0x1p+0))) +(assert_return (invoke "sqrt" (f64.const 1.000000e+00)) (f64.const 1.000000e+00)) +;; (assert_return_canonical_nan (invoke "sqrt" (f64.const -0x1.921fb54442d18p+2))) +(assert_return (invoke "floor" (f64.const -0.000000e+00)) (f64.const -0.000000e+00)) +(assert_return (invoke "floor" (f64.const 0.000000e+00)) (f64.const 0.000000e+00)) +(assert_return (invoke "floor" (f64.const -4.940656e-324)) (f64.const -1.000000e+00)) +(assert_return (invoke "floor" (f64.const 4.940656e-324)) (f64.const 0.000000e+00)) +(assert_return (invoke "floor" (f64.const -2.225074e-308)) (f64.const -1.000000e+00)) +(assert_return (invoke "floor" (f64.const 2.225074e-308)) (f64.const 0.000000e+00)) +(assert_return (invoke "floor" (f64.const -5.000000e-01)) (f64.const -1.000000e+00)) +(assert_return (invoke "floor" (f64.const 5.000000e-01)) (f64.const 0.000000e+00)) +(assert_return (invoke "floor" (f64.const -1.000000e+00)) (f64.const -1.000000e+00)) +(assert_return (invoke "floor" (f64.const 1.000000e+00)) (f64.const 1.000000e+00)) +(assert_return (invoke "floor" (f64.const -6.283185e+00)) (f64.const -7.000000e+00)) +(assert_return (invoke "floor" (f64.const 6.283185e+00)) (f64.const 6.000000e+00)) +(assert_return (invoke "floor" (f64.const -1.797693e+308)) (f64.const -1.797693e+308)) +(assert_return (invoke "floor" (f64.const 1.797693e+308)) (f64.const 1.797693e+308)) +(assert_return (invoke "floor" (f64.const -Infinity)) (f64.const -Infinity)) +(assert_return (invoke "floor" (f64.const Infinity)) (f64.const Infinity)) +(assert_return (invoke "ceil" (f64.const -0.000000e+00)) (f64.const -0.000000e+00)) +(assert_return (invoke "ceil" (f64.const 0.000000e+00)) (f64.const 0.000000e+00)) +(assert_return (invoke "ceil" (f64.const -4.940656e-324)) (f64.const -0.000000e+00)) +(assert_return (invoke "ceil" (f64.const 4.940656e-324)) (f64.const 1.000000e+00)) +(assert_return (invoke "ceil" (f64.const -2.225074e-308)) (f64.const -0.000000e+00)) +(assert_return (invoke "ceil" (f64.const 2.225074e-308)) (f64.const 1.000000e+00)) +(assert_return (invoke "ceil" (f64.const -5.000000e-01)) (f64.const -0.000000e+00)) +(assert_return (invoke "ceil" (f64.const 5.000000e-01)) (f64.const 1.000000e+00)) +(assert_return (invoke "ceil" (f64.const -1.000000e+00)) (f64.const -1.000000e+00)) +(assert_return (invoke "ceil" (f64.const 1.000000e+00)) (f64.const 1.000000e+00)) +(assert_return (invoke "ceil" (f64.const -6.283185e+00)) (f64.const -6.000000e+00)) +(assert_return (invoke "ceil" (f64.const 6.283185e+00)) (f64.const 7.000000e+00)) +(assert_return (invoke "ceil" (f64.const -1.797693e+308)) (f64.const -1.797693e+308)) +(assert_return (invoke "ceil" (f64.const 1.797693e+308)) (f64.const 1.797693e+308)) +(assert_return (invoke "ceil" (f64.const -Infinity)) (f64.const -Infinity)) +(assert_return (invoke "ceil" (f64.const Infinity)) (f64.const Infinity)) +;; (assert_return_canonical_nan (invoke "ceil" (f64.const -nan))) +;; (assert_return_arithmetic_nan (invoke "ceil" (f64.const -nan:0x4000000000000))) +;; (assert_return_canonical_nan (invoke "ceil" (f64.const nan))) +;; (assert_return_arithmetic_nan (invoke "ceil" (f64.const nan:0x4000000000000))) +(assert_return (invoke "trunc" (f64.const -0.000000e+00)) (f64.const -0.000000e+00)) +(assert_return (invoke "trunc" (f64.const 0.000000e+00)) (f64.const 0.000000e+00)) +(assert_return (invoke "trunc" (f64.const -4.940656e-324)) (f64.const -0.000000e+00)) +(assert_return (invoke "trunc" (f64.const 4.940656e-324)) (f64.const 0.000000e+00)) +(assert_return (invoke "trunc" (f64.const -2.225074e-308)) (f64.const -0.000000e+00)) +(assert_return (invoke "trunc" (f64.const 2.225074e-308)) (f64.const 0.000000e+00)) +(assert_return (invoke "trunc" (f64.const -5.000000e-01)) (f64.const -0.000000e+00)) +(assert_return (invoke "trunc" (f64.const 5.000000e-01)) (f64.const 0.000000e+00)) +(assert_return (invoke "trunc" (f64.const -1.000000e+00)) (f64.const -1.000000e+00)) +(assert_return (invoke "trunc" (f64.const 1.000000e+00)) (f64.const 1.000000e+00)) +(assert_return (invoke "trunc" (f64.const -6.283185e+00)) (f64.const -6.000000e+00)) +(assert_return (invoke "trunc" (f64.const 6.283185e+00)) (f64.const 6.000000e+00)) +(assert_return (invoke "trunc" (f64.const -1.797693e+308)) (f64.const -1.797693e+308)) +(assert_return (invoke "trunc" (f64.const 1.797693e+308)) (f64.const 1.797693e+308)) +(assert_return (invoke "trunc" (f64.const -Infinity)) (f64.const -Infinity)) +(assert_return (invoke "trunc" (f64.const Infinity)) (f64.const Infinity)) +;; (assert_return_canonical_nan (invoke "trunc" (f64.const -nan))) +;; (assert_return_arithmetic_nan (invoke "trunc" (f64.const -nan:0x4000000000000))) +;; (assert_return_canonical_nan (invoke "trunc" (f64.const nan))) +;; (assert_return_arithmetic_nan (invoke "trunc" (f64.const nan:0x4000000000000))) +(assert_return (invoke "nearest" (f64.const -0.000000e+00)) (f64.const -0.000000e+00)) +(assert_return (invoke "nearest" (f64.const 0.000000e+00)) (f64.const 0.000000e+00)) +(assert_return (invoke "nearest" (f64.const -4.940656e-324)) (f64.const -0.000000e+00)) +(assert_return (invoke "nearest" (f64.const 4.940656e-324)) (f64.const 0.000000e+00)) +(assert_return (invoke "nearest" (f64.const -2.225074e-308)) (f64.const -0.000000e+00)) +(assert_return (invoke "nearest" (f64.const 2.225074e-308)) (f64.const 0.000000e+00)) +(assert_return (invoke "nearest" (f64.const -5.000000e-01)) (f64.const -0.000000e+00)) +(assert_return (invoke "nearest" (f64.const 5.000000e-01)) (f64.const 0.000000e+00)) +(assert_return (invoke "nearest" (f64.const -1.000000e+00)) (f64.const -1.000000e+00)) +(assert_return (invoke "nearest" (f64.const 1.000000e+00)) (f64.const 1.000000e+00)) +(assert_return (invoke "nearest" (f64.const -6.283185e+00)) (f64.const -6.000000e+00)) +(assert_return (invoke "nearest" (f64.const 6.283185e+00)) (f64.const 6.000000e+00)) +(assert_return (invoke "nearest" (f64.const -1.797693e+308)) (f64.const -1.797693e+308)) +(assert_return (invoke "nearest" (f64.const 1.797693e+308)) (f64.const 1.797693e+308)) +(assert_return (invoke "nearest" (f64.const -Infinity)) (f64.const -Infinity)) +(assert_return (invoke "nearest" (f64.const Infinity)) (f64.const Infinity)) + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/f64_bitwise-cs.wast b/docs/runtimeverification-wasm-semantics/tests/simple/f64_bitwise-cs.wast new file mode 100644 index 000000000..78c3859da --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/f64_bitwise-cs.wast @@ -0,0 +1,147 @@ +;; Test all the f64 bitwise operators on major boundary values and all special +;; values. + +(module + (func (export "abs") (param $x f64) (result f64) (f64.abs (local.get $x))) + (func (export "neg") (param $x f64) (result f64) (f64.neg (local.get $x))) + (func (export "copysign") (param $x f64) (param $y f64) (result f64) (f64.copysign (local.get $x) (local.get $y))) +) + +(assert_return (invoke "copysign" (f64.const -0.0) (f64.const -0.0)) (f64.const -0.0)) +(assert_return (invoke "copysign" (f64.const -0.0) (f64.const 0.0)) (f64.const 0.0)) +(assert_return (invoke "copysign" (f64.const 0.0) (f64.const -0.0)) (f64.const -0.0)) +(assert_return (invoke "copysign" (f64.const 0.0) (f64.const 0.0)) (f64.const 0.0)) +(assert_return (invoke "copysign" (f64.const -0.0) (f64.const -4.94065645841e-324)) (f64.const -0.0)) +(assert_return (invoke "copysign" (f64.const -0.0) (f64.const 4.94065645841e-324)) (f64.const 0.0)) +(assert_return (invoke "copysign" (f64.const 0.0) (f64.const -4.94065645841e-324)) (f64.const -0.0)) +(assert_return (invoke "copysign" (f64.const 0.0) (f64.const 4.94065645841e-324)) (f64.const 0.0)) +(assert_return (invoke "copysign" (f64.const -0.0) (f64.const -2.22507385851e-308)) (f64.const -0.0)) +(assert_return (invoke "copysign" (f64.const -0.0) (f64.const 2.22507385851e-308)) (f64.const 0.0)) +(assert_return (invoke "copysign" (f64.const 0.0) (f64.const -2.22507385851e-308)) (f64.const -0.0)) +(assert_return (invoke "copysign" (f64.const 0.0) (f64.const 2.22507385851e-308)) (f64.const 0.0)) +(assert_return (invoke "copysign" (f64.const -0.0) (f64.const -0.5)) (f64.const -0.0)) +(assert_return (invoke "copysign" (f64.const -0.0) (f64.const 0.5)) (f64.const 0.0)) +(assert_return (invoke "copysign" (f64.const 0.0) (f64.const -0.5)) (f64.const -0.0)) +(assert_return (invoke "copysign" (f64.const 0.0) (f64.const 0.5)) (f64.const 0.0)) +(assert_return (invoke "copysign" (f64.const -0.0) (f64.const -1.0)) (f64.const -0.0)) +(assert_return (invoke "copysign" (f64.const -0.0) (f64.const 1.0)) (f64.const 0.0)) +(assert_return (invoke "copysign" (f64.const 0.0) (f64.const -1.0)) (f64.const -0.0)) +(assert_return (invoke "copysign" (f64.const 0.0) (f64.const 1.0)) (f64.const 0.0)) +;; (assert_return (invoke "copysign" (f64.const -0x1.921fb54442d18p+2) (f64.const -nan)) (f64.const -0x1.921fb54442d18p+2)) +(assert_return (invoke "copysign" (f64.const -6.28318530718) (f64.const NaN)) (f64.const 6.28318530718)) +;; (assert_return (invoke "copysign" (f64.const 0x1.921fb54442d18p+2) (f64.const -nan)) (f64.const -0x1.921fb54442d18p+2)) +(assert_return (invoke "copysign" (f64.const 6.28318530718) (f64.const NaN)) (f64.const 6.28318530718)) +(assert_return (invoke "copysign" (f64.const -1.79769313486e+308) (f64.const -0.0)) (f64.const -1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const -1.79769313486e+308) (f64.const 0.0)) (f64.const 1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const 1.79769313486e+308) (f64.const -0.0)) (f64.const -1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const 1.79769313486e+308) (f64.const 0.0)) (f64.const 1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const -1.79769313486e+308) (f64.const -4.94065645841e-324)) (f64.const -1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const -1.79769313486e+308) (f64.const 4.94065645841e-324)) (f64.const 1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const 1.79769313486e+308) (f64.const -4.94065645841e-324)) (f64.const -1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const 1.79769313486e+308) (f64.const 4.94065645841e-324)) (f64.const 1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const -1.79769313486e+308) (f64.const -2.22507385851e-308)) (f64.const -1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const -1.79769313486e+308) (f64.const 2.22507385851e-308)) (f64.const 1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const 1.79769313486e+308) (f64.const -2.22507385851e-308)) (f64.const -1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const 1.79769313486e+308) (f64.const 2.22507385851e-308)) (f64.const 1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const -1.79769313486e+308) (f64.const -0.5)) (f64.const -1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const -1.79769313486e+308) (f64.const 0.5)) (f64.const 1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const 1.79769313486e+308) (f64.const -0.5)) (f64.const -1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const 1.79769313486e+308) (f64.const 0.5)) (f64.const 1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const -1.79769313486e+308) (f64.const -1.0)) (f64.const -1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const -1.79769313486e+308) (f64.const 1.0)) (f64.const 1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const 1.79769313486e+308) (f64.const -1.0)) (f64.const -1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const 1.79769313486e+308) (f64.const 1.0)) (f64.const 1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const -1.79769313486e+308) (f64.const -6.28318530718)) (f64.const -1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const -1.79769313486e+308) (f64.const 6.28318530718)) (f64.const 1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const 1.79769313486e+308) (f64.const -6.28318530718)) (f64.const -1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const 1.79769313486e+308) (f64.const 6.28318530718)) (f64.const 1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const -1.79769313486e+308) (f64.const -1.79769313486e+308)) (f64.const -1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const -1.79769313486e+308) (f64.const 1.79769313486e+308)) (f64.const 1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const 1.79769313486e+308) (f64.const -1.79769313486e+308)) (f64.const -1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const 1.79769313486e+308) (f64.const 1.79769313486e+308)) (f64.const 1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const -1.79769313486e+308) (f64.const -Infinity)) (f64.const -1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const -1.79769313486e+308) (f64.const Infinity)) (f64.const 1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const 1.79769313486e+308) (f64.const -Infinity)) (f64.const -1.79769313486e+308)) +(assert_return (invoke "copysign" (f64.const 1.79769313486e+308) (f64.const Infinity)) (f64.const 1.79769313486e+308)) +;; (assert_return (invoke "copysign" (f64.const -0x1.fffffffffffffp+1023) (f64.const -nan)) (f64.const -0x1.fffffffffffffp+1023)) +;; (assert_return (invoke "copysign" (f64.const -nan) (f64.const -0x0p+0)) (f64.const -nan)) +;; (assert_return (invoke "copysign" (f64.const -nan) (f64.const 0x0p+0)) (f64.const nan)) +;; (assert_return (invoke "copysign" (f64.const nan) (f64.const -0x0p+0)) (f64.const -nan)) +(assert_return (invoke "copysign" (f64.const NaN) (f64.const 0.0)) (f64.const NaN)) +;; (assert_return (invoke "copysign" (f64.const -nan) (f64.const -0x0.0000000000001p-1022)) (f64.const -nan)) +;; (assert_return (invoke "copysign" (f64.const -nan) (f64.const 0x0.0000000000001p-1022)) (f64.const nan)) +;; (assert_return (invoke "copysign" (f64.const nan) (f64.const -0x0.0000000000001p-1022)) (f64.const -nan)) +(assert_return (invoke "copysign" (f64.const NaN) (f64.const 4.94065645841e-324)) (f64.const NaN)) +;; (assert_return (invoke "copysign" (f64.const -nan) (f64.const -0x1p-1022)) (f64.const -nan)) +;; (assert_return (invoke "copysign" (f64.const -nan) (f64.const 0x1p-1022)) (f64.const nan)) +;; (assert_return (invoke "copysign" (f64.const nan) (f64.const -0x1p-1022)) (f64.const -nan)) +(assert_return (invoke "copysign" (f64.const NaN) (f64.const 2.22507385851e-308)) (f64.const NaN)) +;; (assert_return (invoke "copysign" (f64.const -nan) (f64.const -0x1p-1)) (f64.const -nan)) +;; (assert_return (invoke "copysign" (f64.const -nan) (f64.const 0x1p-1)) (f64.const nan)) +;; (assert_return (invoke "copysign" (f64.const nan) (f64.const -0x1p-1)) (f64.const -nan)) +(assert_return (invoke "copysign" (f64.const NaN) (f64.const 0.5)) (f64.const NaN)) +;; (assert_return (invoke "copysign" (f64.const -nan) (f64.const -0x1p+0)) (f64.const -nan)) +;; (assert_return (invoke "copysign" (f64.const -nan) (f64.const 0x1p+0)) (f64.const nan)) +;; (assert_return (invoke "copysign" (f64.const nan) (f64.const -0x1p+0)) (f64.const -nan)) +(assert_return (invoke "copysign" (f64.const NaN) (f64.const 1.0)) (f64.const NaN)) +;; (assert_return (invoke "copysign" (f64.const -nan) (f64.const -0x1.921fb54442d18p+2)) (f64.const -nan)) +;; (assert_return (invoke "copysign" (f64.const -nan) (f64.const 0x1.921fb54442d18p+2)) (f64.const nan)) +;; (assert_return (invoke "copysign" (f64.const nan) (f64.const -0x1.921fb54442d18p+2)) (f64.const -nan)) +(assert_return (invoke "copysign" (f64.const NaN) (f64.const 6.28318530718)) (f64.const NaN)) +;; (assert_return (invoke "copysign" (f64.const -nan) (f64.const -0x1.fffffffffffffp+1023)) (f64.const -nan)) +;; (assert_return (invoke "copysign" (f64.const -nan) (f64.const 0x1.fffffffffffffp+1023)) (f64.const nan)) +;; (assert_return (invoke "copysign" (f64.const nan) (f64.const -0x1.fffffffffffffp+1023)) (f64.const -nan)) +(assert_return (invoke "copysign" (f64.const NaN) (f64.const 1.79769313486e+308)) (f64.const NaN)) +;; (assert_return (invoke "copysign" (f64.const -nan) (f64.const -inf)) (f64.const -nan)) +;; (assert_return (invoke "copysign" (f64.const -nan) (f64.const inf)) (f64.const nan)) +;; (assert_return (invoke "copysign" (f64.const nan) (f64.const -inf)) (f64.const -nan)) +(assert_return (invoke "copysign" (f64.const NaN) (f64.const Infinity)) (f64.const NaN)) +;; (assert_return (invoke "copysign" (f64.const -nan) (f64.const -nan)) (f64.const -nan)) +;; (assert_return (invoke "copysign" (f64.const -nan) (f64.const nan)) (f64.const nan)) +;; (assert_return (invoke "copysign" (f64.const nan) (f64.const -nan)) (f64.const -nan)) +(assert_return (invoke "copysign" (f64.const NaN) (f64.const NaN)) (f64.const NaN)) +(assert_return (invoke "abs" (f64.const -0.0)) (f64.const 0.0)) +(assert_return (invoke "abs" (f64.const 0.0)) (f64.const 0.0)) +(assert_return (invoke "abs" (f64.const -4.94065645841e-324)) (f64.const 4.94065645841e-324)) +(assert_return (invoke "abs" (f64.const 4.94065645841e-324)) (f64.const 4.94065645841e-324)) +(assert_return (invoke "abs" (f64.const -2.22507385851e-308)) (f64.const 2.22507385851e-308)) +(assert_return (invoke "abs" (f64.const 2.22507385851e-308)) (f64.const 2.22507385851e-308)) +(assert_return (invoke "abs" (f64.const -0.5)) (f64.const 0.5)) +(assert_return (invoke "abs" (f64.const 0.5)) (f64.const 0.5)) +(assert_return (invoke "abs" (f64.const -1.0)) (f64.const 1.0)) +(assert_return (invoke "abs" (f64.const 1.0)) (f64.const 1.0)) +(assert_return (invoke "abs" (f64.const -6.28318530718)) (f64.const 6.28318530718)) +(assert_return (invoke "abs" (f64.const 6.28318530718)) (f64.const 6.28318530718)) +(assert_return (invoke "abs" (f64.const -1.79769313486e+308)) (f64.const 1.79769313486e+308)) +(assert_return (invoke "abs" (f64.const 1.79769313486e+308)) (f64.const 1.79769313486e+308)) +(assert_return (invoke "abs" (f64.const -Infinity)) (f64.const Infinity)) +(assert_return (invoke "abs" (f64.const Infinity)) (f64.const Infinity)) +;; (assert_return (invoke "abs" (f64.const -nan)) (f64.const nan)) +(assert_return (invoke "abs" (f64.const NaN)) (f64.const NaN)) +(assert_return (invoke "neg" (f64.const -0.0)) (f64.const 0.0)) +(assert_return (invoke "neg" (f64.const 0.0)) (f64.const -0.0)) +(assert_return (invoke "neg" (f64.const -4.94065645841e-324)) (f64.const 4.94065645841e-324)) +(assert_return (invoke "neg" (f64.const 4.94065645841e-324)) (f64.const -4.94065645841e-324)) +(assert_return (invoke "neg" (f64.const -2.22507385851e-308)) (f64.const 2.22507385851e-308)) +(assert_return (invoke "neg" (f64.const 2.22507385851e-308)) (f64.const -2.22507385851e-308)) +(assert_return (invoke "neg" (f64.const -0.5)) (f64.const 0.5)) +(assert_return (invoke "neg" (f64.const 0.5)) (f64.const -0.5)) +(assert_return (invoke "neg" (f64.const -1.0)) (f64.const 1.0)) +(assert_return (invoke "neg" (f64.const 1.0)) (f64.const -1.0)) +(assert_return (invoke "neg" (f64.const -6.28318530718)) (f64.const 6.28318530718)) +(assert_return (invoke "neg" (f64.const 6.28318530718)) (f64.const -6.28318530718)) +(assert_return (invoke "neg" (f64.const -1.79769313486e+308)) (f64.const 1.79769313486e+308)) +(assert_return (invoke "neg" (f64.const 1.79769313486e+308)) (f64.const -1.79769313486e+308)) +(assert_return (invoke "neg" (f64.const -Infinity)) (f64.const Infinity)) +(assert_return (invoke "neg" (f64.const Infinity)) (f64.const -Infinity)) +;; (assert_return (invoke "neg" (f64.const -nan)) (f64.const nan)) +;; (assert_return (invoke "neg" (f64.const nan)) (f64.const -nan)) + + +;; Type check + +;; (assert_invalid (module (func (result f64) (f64.copysign (i64.const 0) (f32.const 0)))) "type mismatch") +;; (assert_invalid (module (func (result f64) (f64.abs (i64.const 0)))) "type mismatch") +;; (assert_invalid (module (func (result f64) (f64.neg (i64.const 0)))) "type mismatch") + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/f64_cmp-cs.wast b/docs/runtimeverification-wasm-semantics/tests/simple/f64_cmp-cs.wast new file mode 100644 index 000000000..b843e667d --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/f64_cmp-cs.wast @@ -0,0 +1,76 @@ +;; Test all the f64 comparison operators on major boundary values and all +;; special values. + +(module + (func (export "eq") (param $x f64) (param $y f64) (result i32) (f64.eq (local.get $x) (local.get $y))) + (func (export "ne") (param $x f64) (param $y f64) (result i32) (f64.ne (local.get $x) (local.get $y))) + (func (export "lt") (param $x f64) (param $y f64) (result i32) (f64.lt (local.get $x) (local.get $y))) + (func (export "le") (param $x f64) (param $y f64) (result i32) (f64.le (local.get $x) (local.get $y))) + (func (export "gt") (param $x f64) (param $y f64) (result i32) (f64.gt (local.get $x) (local.get $y))) + (func (export "ge") (param $x f64) (param $y f64) (result i32) (f64.ge (local.get $x) (local.get $y))) +) + +(assert_return (invoke "eq" (f64.const -0.0) (f64.const -0.0)) (i32.const 1)) +(assert_return (invoke "eq" (f64.const -0.0) (f64.const 0.0)) (i32.const 1)) +(assert_return (invoke "eq" (f64.const 0.0) (f64.const -0.0)) (i32.const 1)) +(assert_return (invoke "eq" (f64.const 0.0) (f64.const 0.0)) (i32.const 1)) +(assert_return (invoke "eq" (f64.const -0.0) (f64.const -4.94065645841e-324)) (i32.const 0)) +(assert_return (invoke "eq" (f64.const -0.0) (f64.const 4.94065645841e-324)) (i32.const 0)) +(assert_return (invoke "eq" (f64.const 0.0) (f64.const -4.94065645841e-324)) (i32.const 0)) +(assert_return (invoke "eq" (f64.const 0.0) (f64.const 4.94065645841e-324)) (i32.const 0)) +(assert_return (invoke "eq" (f64.const -4.94065645841e-324) (f64.const -4.94065645841e-324)) (i32.const 1)) +(assert_return (invoke "eq" (f64.const -4.94065645841e-324) (f64.const 4.94065645841e-324)) (i32.const 0)) +(assert_return (invoke "eq" (f64.const 4.94065645841e-324) (f64.const -4.94065645841e-324)) (i32.const 0)) +(assert_return (invoke "eq" (f64.const 4.94065645841e-324) (f64.const 4.94065645841e-324)) (i32.const 1)) +(assert_return (invoke "ne" (f64.const -0.0) (f64.const 2.22507385851e-308)) (i32.const 1)) +(assert_return (invoke "ne" (f64.const 0.0) (f64.const -2.22507385851e-308)) (i32.const 1)) +(assert_return (invoke "ne" (f64.const 0.0) (f64.const 2.22507385851e-308)) (i32.const 1)) +(assert_return (invoke "ne" (f64.const -0.0) (f64.const -0.5)) (i32.const 1)) +(assert_return (invoke "ne" (f64.const -0.0) (f64.const 0.5)) (i32.const 1)) +(assert_return (invoke "ne" (f64.const 0.0) (f64.const -0.5)) (i32.const 1)) +(assert_return (invoke "ne" (f64.const 0.0) (f64.const 0.5)) (i32.const 1)) +(assert_return (invoke "ne" (f64.const -0.0) (f64.const -1.0)) (i32.const 1)) +(assert_return (invoke "ne" (f64.const -0.0) (f64.const 1.0)) (i32.const 1)) +(assert_return (invoke "ne" (f64.const NaN) (f64.const -0.0)) (i32.const 1)) +(assert_return (invoke "lt" (f64.const -0.0) (f64.const -0.0)) (i32.const 0)) +(assert_return (invoke "lt" (f64.const -0.0) (f64.const 0.0)) (i32.const 0)) +(assert_return (invoke "lt" (f64.const 0.0) (f64.const -0.0)) (i32.const 0)) +(assert_return (invoke "lt" (f64.const 0.0) (f64.const 0.0)) (i32.const 0)) +(assert_return (invoke "lt" (f64.const -0.0) (f64.const -4.94065645841e-324)) (i32.const 0)) +(assert_return (invoke "lt" (f64.const -0.0) (f64.const 4.94065645841e-324)) (i32.const 1)) +(assert_return (invoke "lt" (f64.const 0.0) (f64.const -4.94065645841e-324)) (i32.const 0)) +(assert_return (invoke "lt" (f64.const 0.0) (f64.const 4.94065645841e-324)) (i32.const 1)) +(assert_return (invoke "lt" (f64.const -1.79769313486e+308) (f64.const 6.28318530718)) (i32.const 1)) +(assert_return (invoke "lt" (f64.const 1.79769313486e+308) (f64.const -6.28318530718)) (i32.const 0)) +(assert_return (invoke "lt" (f64.const 1.79769313486e+308) (f64.const 6.28318530718)) (i32.const 0)) +(assert_return (invoke "lt" (f64.const -1.79769313486e+308) (f64.const -1.79769313486e+308)) (i32.const 0)) +(assert_return (invoke "lt" (f64.const -1.79769313486e+308) (f64.const 1.79769313486e+308)) (i32.const 1)) +(assert_return (invoke "lt" (f64.const 1.79769313486e+308) (f64.const -1.79769313486e+308)) (i32.const 0)) +(assert_return (invoke "lt" (f64.const 1.79769313486e+308) (f64.const 1.79769313486e+308)) (i32.const 0)) +(assert_return (invoke "lt" (f64.const -1.79769313486e+308) (f64.const -Infinity)) (i32.const 0)) +(assert_return (invoke "lt" (f64.const -1.79769313486e+308) (f64.const Infinity)) (i32.const 1)) +(assert_return (invoke "lt" (f64.const 1.79769313486e+308) (f64.const -Infinity)) (i32.const 0)) +(assert_return (invoke "lt" (f64.const 1.79769313486e+308) (f64.const Infinity)) (i32.const 1)) +;; (assert_return (invoke "le" (f64.const -0x0.0000000000001p-1022) (f64.const -nan)) (i32.const 0)) +;; (assert_return (invoke "le" (f64.const -0x0.0000000000001p-1022) (f64.const -nan:0x4000000000000)) (i32.const 0)) +(assert_return (invoke "le" (f64.const -4.94065645841e-324) (f64.const NaN)) (i32.const 0)) +;; (assert_return (invoke "le" (f64.const -0x0.0000000000001p-1022) (f64.const nan:0x4000000000000)) (i32.const 0)) +;; (assert_return (invoke "le" (f64.const 0x0.0000000000001p-1022) (f64.const -nan)) (i32.const 0)) +;; (assert_return (invoke "le" (f64.const 0x0.0000000000001p-1022) (f64.const -nan:0x4000000000000)) (i32.const 0)) +(assert_return (invoke "le" (f64.const 4.94065645841e-324) (f64.const NaN)) (i32.const 0)) +(assert_return (invoke "gt" (f64.const -Infinity) (f64.const 1.0)) (i32.const 0)) +(assert_return (invoke "gt" (f64.const Infinity) (f64.const -1.0)) (i32.const 1)) +(assert_return (invoke "gt" (f64.const Infinity) (f64.const 1.0)) (i32.const 1)) +(assert_return (invoke "gt" (f64.const -Infinity) (f64.const -6.28318530718)) (i32.const 0)) +(assert_return (invoke "gt" (f64.const -Infinity) (f64.const 6.28318530718)) (i32.const 0)) +(assert_return (invoke "gt" (f64.const Infinity) (f64.const -6.28318530718)) (i32.const 1)) +(assert_return (invoke "gt" (f64.const Infinity) (f64.const 6.28318530718)) (i32.const 1)) +(assert_return (invoke "ge" (f64.const -0.0) (f64.const -0.0)) (i32.const 1)) +(assert_return (invoke "ge" (f64.const -0.0) (f64.const 0.0)) (i32.const 1)) +(assert_return (invoke "ge" (f64.const 0.0) (f64.const -0.0)) (i32.const 1)) +(assert_return (invoke "ge" (f64.const 0.0) (f64.const 0.0)) (i32.const 1)) +(assert_return (invoke "ge" (f64.const -Infinity) (f64.const 0.0)) (i32.const 0)) +(assert_return (invoke "ge" (f64.const Infinity) (f64.const -0.0)) (i32.const 1)) +(assert_return (invoke "ge" (f64.const Infinity) (f64.const 0.0)) (i32.const 1)) + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/fconversions-c.wast b/docs/runtimeverification-wasm-semantics/tests/simple/fconversions-c.wast new file mode 100644 index 000000000..887df92d4 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/fconversions-c.wast @@ -0,0 +1,314 @@ +(module + (func (export "i32.trunc_f32_s") (param $x f32) (result i32) (i32.trunc_f32_s (local.get $x))) + (func (export "i32.trunc_f32_u") (param $x f32) (result i32) (i32.trunc_f32_u (local.get $x))) + (func (export "i32.trunc_f64_s") (param $x f64) (result i32) (i32.trunc_f64_s (local.get $x))) + (func (export "i32.trunc_f64_u") (param $x f64) (result i32) (i32.trunc_f64_u (local.get $x))) + (func (export "i64.trunc_f32_s") (param $x f32) (result i64) (i64.trunc_f32_s (local.get $x))) + (func (export "i64.trunc_f32_u") (param $x f32) (result i64) (i64.trunc_f32_u (local.get $x))) + (func (export "i64.trunc_f64_s") (param $x f64) (result i64) (i64.trunc_f64_s (local.get $x))) + (func (export "i64.trunc_f64_u") (param $x f64) (result i64) (i64.trunc_f64_u (local.get $x))) + (func (export "f32.convert_i32_s") (param $x i32) (result f32) (f32.convert_i32_s (local.get $x))) + (func (export "f32.convert_i64_s") (param $x i64) (result f32) (f32.convert_i64_s (local.get $x))) + (func (export "f64.convert_i32_s") (param $x i32) (result f64) (f64.convert_i32_s (local.get $x))) + (func (export "f64.convert_i64_s") (param $x i64) (result f64) (f64.convert_i64_s (local.get $x))) + (func (export "f32.convert_i32_u") (param $x i32) (result f32) (f32.convert_i32_u (local.get $x))) + (func (export "f32.convert_i64_u") (param $x i64) (result f32) (f32.convert_i64_u (local.get $x))) + (func (export "f64.convert_i32_u") (param $x i32) (result f64) (f64.convert_i32_u (local.get $x))) + (func (export "f64.convert_i64_u") (param $x i64) (result f64) (f64.convert_i64_u (local.get $x))) + (func (export "f64.promote_f32") (param $x f32) (result f64) (f64.promote_f32 (local.get $x))) + (func (export "f32.demote_f64") (param $x f64) (result f32) (f32.demote_f64 (local.get $x))) +) + +(assert_return (invoke "i32.trunc_f32_s" (f32.const 0.0)) (i32.const 0)) +(assert_return (invoke "i32.trunc_f32_s" (f32.const -0.0)) (i32.const 0)) +(assert_return (invoke "i32.trunc_f32_s" (f32.const 1.401298e-45)) (i32.const 0)) +(assert_return (invoke "i32.trunc_f32_s" (f32.const -1.401298e-45)) (i32.const 0)) +(assert_return (invoke "i32.trunc_f32_s" (f32.const 1.0)) (i32.const 1)) +(assert_return (invoke "i32.trunc_f32_s" (f32.const 1.100000e+00)) (i32.const 1)) +(assert_return (invoke "i32.trunc_f32_s" (f32.const 1.5)) (i32.const 1)) +(assert_return (invoke "i32.trunc_f32_s" (f32.const -1.0)) (i32.const -1)) +(assert_return (invoke "i32.trunc_f32_s" (f32.const -1.100000e+00)) (i32.const -1)) +(assert_return (invoke "i32.trunc_f32_s" (f32.const -1.5)) (i32.const -1)) +(assert_return (invoke "i32.trunc_f32_s" (f32.const -1.9)) (i32.const -1)) +(assert_return (invoke "i32.trunc_f32_s" (f32.const -2.0)) (i32.const -2)) +(assert_return (invoke "i32.trunc_f32_s" (f32.const 2147483520.0)) (i32.const 2147483520)) +(assert_return (invoke "i32.trunc_f32_s" (f32.const -2147483648.0)) (i32.const -2147483648)) +(assert_trap (invoke "i32.trunc_f32_s" (f32.const 2147483648.0)) "integer overflow") +(assert_trap (invoke "i32.trunc_f32_s" (f32.const -2147483904.0)) "integer overflow") +(assert_trap (invoke "i32.trunc_f32_s" (f32.const Infinity)) "integer overflow") +(assert_trap (invoke "i32.trunc_f32_s" (f32.const -Infinity)) "integer overflow") +(assert_trap (invoke "i32.trunc_f32_s" (f32.const NaN)) "invalid conversion to integer") +;; (assert_trap (invoke "i32.trunc_f32_s" (f32.const nan:0x200000)) "invalid conversion to integer") +;; (assert_trap (invoke "i32.trunc_f32_s" (f32.const -nan)) "invalid conversion to integer") +;; (assert_trap (invoke "i32.trunc_f32_s" (f32.const -nan:0x200000)) "invalid conversion to integer") + +(assert_return (invoke "i32.trunc_f32_u" (f32.const 0.0)) (i32.const 0)) +(assert_return (invoke "i32.trunc_f32_u" (f32.const -0.0)) (i32.const 0)) +(assert_return (invoke "i32.trunc_f32_u" (f32.const 1.401298e-45)) (i32.const 0)) +(assert_return (invoke "i32.trunc_f32_u" (f32.const -1.401298e-45)) (i32.const 0)) +(assert_return (invoke "i32.trunc_f32_u" (f32.const 1.0)) (i32.const 1)) +(assert_return (invoke "i32.trunc_f32_u" (f32.const 1.100000e+00)) (i32.const 1)) +(assert_return (invoke "i32.trunc_f32_u" (f32.const 1.5)) (i32.const 1)) +(assert_return (invoke "i32.trunc_f32_u" (f32.const 1.9)) (i32.const 1)) +(assert_return (invoke "i32.trunc_f32_u" (f32.const 2.0)) (i32.const 2)) +(assert_return (invoke "i32.trunc_f32_u" (f32.const 2147483648)) (i32.const -2147483648)) ;; 0x1.00000p+31 -> 8000 0000 +(assert_return (invoke "i32.trunc_f32_u" (f32.const 4294967040.0)) (i32.const -256)) +(assert_return (invoke "i32.trunc_f32_u" (f32.const -9.000000e-01)) (i32.const 0)) +(assert_return (invoke "i32.trunc_f32_u" (f32.const -9.999999e-01)) (i32.const 0)) +(assert_trap (invoke "i32.trunc_f32_u" (f32.const 4294967296.0)) "integer overflow") +(assert_trap (invoke "i32.trunc_f32_u" (f32.const -1.0)) "integer overflow") +(assert_trap (invoke "i32.trunc_f32_u" (f32.const Infinity)) "integer overflow") +(assert_trap (invoke "i32.trunc_f32_u" (f32.const -Infinity)) "integer overflow") +(assert_trap (invoke "i32.trunc_f32_u" (f32.const NaN)) "invalid conversion to integer") +;; (assert_trap (invoke "i32.trunc_f32_u" (f32.const nan:0x200000)) "invalid conversion to integer") +;; (assert_trap (invoke "i32.trunc_f32_u" (f32.const -nan)) "invalid conversion to integer") +;; (assert_trap (invoke "i32.trunc_f32_u" (f32.const -nan:0x200000)) "invalid conversion to integer") + +(assert_return (invoke "i32.trunc_f64_s" (f64.const 0.0)) (i32.const 0)) +(assert_return (invoke "i32.trunc_f64_s" (f64.const -0.0)) (i32.const 0)) +(assert_return (invoke "i32.trunc_f64_s" (f64.const 4.940656e-324)) (i32.const 0)) +(assert_return (invoke "i32.trunc_f64_s" (f64.const -4.940656e-324)) (i32.const 0)) +(assert_return (invoke "i32.trunc_f64_s" (f64.const 1.0)) (i32.const 1)) +(assert_return (invoke "i32.trunc_f64_s" (f64.const 1.100000e+00)) (i32.const 1)) +(assert_return (invoke "i32.trunc_f64_s" (f64.const 1.5)) (i32.const 1)) +(assert_return (invoke "i32.trunc_f64_s" (f64.const -1.0)) (i32.const -1)) +(assert_return (invoke "i32.trunc_f64_s" (f64.const -1.100000e+00)) (i32.const -1)) +(assert_return (invoke "i32.trunc_f64_s" (f64.const -1.5)) (i32.const -1)) +(assert_return (invoke "i32.trunc_f64_s" (f64.const -1.9)) (i32.const -1)) +(assert_return (invoke "i32.trunc_f64_s" (f64.const -2.0)) (i32.const -2)) +(assert_return (invoke "i32.trunc_f64_s" (f64.const 2147483647.0)) (i32.const 2147483647)) +(assert_return (invoke "i32.trunc_f64_s" (f64.const -2147483648.0)) (i32.const -2147483648)) +(assert_trap (invoke "i32.trunc_f64_s" (f64.const 2147483648.0)) "integer overflow") +(assert_trap (invoke "i32.trunc_f64_s" (f64.const -2147483649.0)) "integer overflow") +(assert_trap (invoke "i32.trunc_f64_s" (f64.const Infinity)) "integer overflow") +(assert_trap (invoke "i32.trunc_f64_s" (f64.const -Infinity)) "integer overflow") +(assert_trap (invoke "i32.trunc_f64_s" (f64.const NaN)) "invalid conversion to integer") +;; (assert_trap (invoke "i32.trunc_f64_s" (f64.const nan:0x4000000000000)) "invalid conversion to integer") +;; (assert_trap (invoke "i32.trunc_f64_s" (f64.const -nan)) "invalid conversion to integer") +;; (assert_trap (invoke "i32.trunc_f64_s" (f64.const -nan:0x4000000000000)) "invalid conversion to integer") + +(assert_return (invoke "i32.trunc_f64_u" (f64.const 0.0)) (i32.const 0)) +(assert_return (invoke "i32.trunc_f64_u" (f64.const -0.0)) (i32.const 0)) +(assert_return (invoke "i32.trunc_f64_u" (f64.const 4.940656e-324)) (i32.const 0)) +(assert_return (invoke "i32.trunc_f64_u" (f64.const -4.940656e-324)) (i32.const 0)) +(assert_return (invoke "i32.trunc_f64_u" (f64.const 1.0)) (i32.const 1)) +(assert_return (invoke "i32.trunc_f64_u" (f64.const 1.100000e+00)) (i32.const 1)) +(assert_return (invoke "i32.trunc_f64_u" (f64.const 1.5)) (i32.const 1)) +(assert_return (invoke "i32.trunc_f64_u" (f64.const 1.9)) (i32.const 1)) +(assert_return (invoke "i32.trunc_f64_u" (f64.const 2.0)) (i32.const 2)) +(assert_return (invoke "i32.trunc_f64_u" (f64.const 2147483648)) (i32.const -2147483648)) ;; 0x1.00000p+31 -> 8000 0000 +(assert_return (invoke "i32.trunc_f64_u" (f64.const 4294967295.0)) (i32.const -1)) +(assert_return (invoke "i32.trunc_f64_u" (f64.const -9.000000e-01)) (i32.const 0)) +(assert_return (invoke "i32.trunc_f64_u" (f64.const 1e8)) (i32.const 100000000)) +(assert_trap (invoke "i32.trunc_f64_u" (f64.const 4294967296.0)) "integer overflow") +(assert_trap (invoke "i32.trunc_f64_u" (f64.const -1.0)) "integer overflow") +(assert_trap (invoke "i32.trunc_f64_u" (f64.const 1e16)) "integer overflow") +(assert_trap (invoke "i32.trunc_f64_u" (f64.const 1e30)) "integer overflow") +(assert_trap (invoke "i32.trunc_f64_u" (f64.const 9223372036854775808)) "integer overflow") +(assert_trap (invoke "i32.trunc_f64_u" (f64.const Infinity)) "integer overflow") +(assert_trap (invoke "i32.trunc_f64_u" (f64.const -Infinity)) "integer overflow") +(assert_trap (invoke "i32.trunc_f64_u" (f64.const NaN)) "invalid conversion to integer") +;; (assert_trap (invoke "i32.trunc_f64_u" (f64.const nan:0x4000000000000)) "invalid conversion to integer") +;; (assert_trap (invoke "i32.trunc_f64_u" (f64.const -nan)) "invalid conversion to integer") +;; (assert_trap (invoke "i32.trunc_f64_u" (f64.const -nan:0x4000000000000)) "invalid conversion to integer") + +(assert_return (invoke "i64.trunc_f32_s" (f32.const 0.0)) (i64.const 0)) +(assert_return (invoke "i64.trunc_f32_s" (f32.const -0.0)) (i64.const 0)) +(assert_return (invoke "i64.trunc_f32_s" (f32.const 1.401298e-45)) (i64.const 0)) +(assert_return (invoke "i64.trunc_f32_s" (f32.const -1.401298e-45)) (i64.const 0)) +(assert_return (invoke "i64.trunc_f32_s" (f32.const 1.0)) (i64.const 1)) +(assert_return (invoke "i64.trunc_f32_s" (f32.const 1.100000e+00)) (i64.const 1)) +(assert_return (invoke "i64.trunc_f32_s" (f32.const 1.5)) (i64.const 1)) +(assert_return (invoke "i64.trunc_f32_s" (f32.const -1.0)) (i64.const -1)) +(assert_return (invoke "i64.trunc_f32_s" (f32.const -1.100000e+00)) (i64.const -1)) +(assert_return (invoke "i64.trunc_f32_s" (f32.const -1.5)) (i64.const -1)) +(assert_return (invoke "i64.trunc_f32_s" (f32.const -1.9)) (i64.const -1)) +(assert_return (invoke "i64.trunc_f32_s" (f32.const -2.0)) (i64.const -2)) +(assert_return (invoke "i64.trunc_f32_s" (f32.const 4294967296)) (i64.const 4294967296)) ;; 0x1.00000p+32 -> 1 0000 0000 +(assert_return (invoke "i64.trunc_f32_s" (f32.const -4294967296)) (i64.const -4294967296)) ;; -0x1.00000p+32 -> ffff ffff 0000 0000 +(assert_return (invoke "i64.trunc_f32_s" (f32.const 9223371487098961920.0)) (i64.const 9223371487098961920)) +(assert_return (invoke "i64.trunc_f32_s" (f32.const -9223372036854775808.0)) (i64.const -9223372036854775808)) +(assert_trap (invoke "i64.trunc_f32_s" (f32.const 9223372036854775808.0)) "integer overflow") +(assert_trap (invoke "i64.trunc_f32_s" (f32.const -9223373136366403584.0)) "integer overflow") +(assert_trap (invoke "i64.trunc_f32_s" (f32.const Infinity)) "integer overflow") +(assert_trap (invoke "i64.trunc_f32_s" (f32.const -Infinity)) "integer overflow") +(assert_trap (invoke "i64.trunc_f32_s" (f32.const NaN)) "invalid conversion to integer") +;; (assert_trap (invoke "i64.trunc_f32_s" (f32.const nan:0x200000)) "invalid conversion to integer") +;; (assert_trap (invoke "i64.trunc_f32_s" (f32.const -nan)) "invalid conversion to integer") +;; (assert_trap (invoke "i64.trunc_f32_s" (f32.const -nan:0x200000)) "invalid conversion to integer") + +(assert_return (invoke "i64.trunc_f32_u" (f32.const 0.0)) (i64.const 0)) +(assert_return (invoke "i64.trunc_f32_u" (f32.const -0.0)) (i64.const 0)) +(assert_return (invoke "i64.trunc_f32_u" (f32.const 1.401298e-45)) (i64.const 0)) +(assert_return (invoke "i64.trunc_f32_u" (f32.const -1.401298e-45)) (i64.const 0)) +(assert_return (invoke "i64.trunc_f32_u" (f32.const 1.0)) (i64.const 1)) +(assert_return (invoke "i64.trunc_f32_u" (f32.const 1.100000e+00)) (i64.const 1)) +(assert_return (invoke "i64.trunc_f32_u" (f32.const 1.5)) (i64.const 1)) +(assert_return (invoke "i64.trunc_f32_u" (f32.const 4294967296)) (i64.const 4294967296)) +(assert_return (invoke "i64.trunc_f32_u" (f32.const 18446742974197923840.0)) (i64.const -1099511627776)) +(assert_return (invoke "i64.trunc_f32_u" (f32.const -9.000000e-01)) (i64.const 0)) +(assert_return (invoke "i64.trunc_f32_u" (f32.const -9.999999e-01)) (i64.const 0)) +(assert_trap (invoke "i64.trunc_f32_u" (f32.const 18446744073709551616.0)) "integer overflow") +(assert_trap (invoke "i64.trunc_f32_u" (f32.const -1.0)) "integer overflow") +(assert_trap (invoke "i64.trunc_f32_u" (f32.const Infinity)) "integer overflow") +(assert_trap (invoke "i64.trunc_f32_u" (f32.const -Infinity)) "integer overflow") +(assert_trap (invoke "i64.trunc_f32_u" (f32.const NaN)) "invalid conversion to integer") +;; (assert_trap (invoke "i64.trunc_f32_u" (f32.const nan:0x200000)) "invalid conversion to integer") +;; (assert_trap (invoke "i64.trunc_f32_u" (f32.const -nan)) "invalid conversion to integer") +;; (assert_trap (invoke "i64.trunc_f32_u" (f32.const -nan:0x200000)) "invalid conversion to integer") + +(assert_return (invoke "i64.trunc_f64_s" (f64.const 0.0)) (i64.const 0)) +(assert_return (invoke "i64.trunc_f64_s" (f64.const -0.0)) (i64.const 0)) +(assert_return (invoke "i64.trunc_f64_s" (f64.const 4.940656e-324)) (i64.const 0)) +(assert_return (invoke "i64.trunc_f64_s" (f64.const -4.940656e-324)) (i64.const 0)) +(assert_return (invoke "i64.trunc_f64_s" (f64.const 1.0)) (i64.const 1)) +(assert_return (invoke "i64.trunc_f64_s" (f64.const 1.100000e+00)) (i64.const 1)) +(assert_return (invoke "i64.trunc_f64_s" (f64.const 1.5)) (i64.const 1)) +(assert_return (invoke "i64.trunc_f64_s" (f64.const -1.0)) (i64.const -1)) +(assert_return (invoke "i64.trunc_f64_s" (f64.const -1.100000e+00)) (i64.const -1)) +(assert_return (invoke "i64.trunc_f64_s" (f64.const -1.5)) (i64.const -1)) +(assert_return (invoke "i64.trunc_f64_s" (f64.const -1.9)) (i64.const -1)) +(assert_return (invoke "i64.trunc_f64_s" (f64.const -2.0)) (i64.const -2)) +(assert_return (invoke "i64.trunc_f64_s" (f64.const 4294967296)) (i64.const 4294967296)) ;; 0x1.00000p+32 -> 1 0000 0000 +(assert_return (invoke "i64.trunc_f64_s" (f64.const -4294967296)) (i64.const -4294967296)) ;; -0x1.00000p+32 -> ffff ffff 0000 0000 +(assert_return (invoke "i64.trunc_f64_s" (f64.const 9223372036854774784.0)) (i64.const 9223372036854774784)) +(assert_return (invoke "i64.trunc_f64_s" (f64.const -9223372036854775808.0)) (i64.const -9223372036854775808)) +(assert_trap (invoke "i64.trunc_f64_s" (f64.const 9223372036854775808.0)) "integer overflow") +(assert_trap (invoke "i64.trunc_f64_s" (f64.const -9223372036854777856.0)) "integer overflow") +(assert_trap (invoke "i64.trunc_f64_s" (f64.const Infinity)) "integer overflow") +(assert_trap (invoke "i64.trunc_f64_s" (f64.const -Infinity)) "integer overflow") +(assert_trap (invoke "i64.trunc_f64_s" (f64.const NaN)) "invalid conversion to integer") +;; (assert_trap (invoke "i64.trunc_f64_s" (f64.const nan:0x4000000000000)) "invalid conversion to integer") +;; (assert_trap (invoke "i64.trunc_f64_s" (f64.const -nan)) "invalid conversion to integer") +;; (assert_trap (invoke "i64.trunc_f64_s" (f64.const -nan:0x4000000000000)) "invalid conversion to integer") + +(assert_return (invoke "i64.trunc_f64_u" (f64.const 0.0)) (i64.const 0)) +(assert_return (invoke "i64.trunc_f64_u" (f64.const -0.0)) (i64.const 0)) +(assert_return (invoke "i64.trunc_f64_u" (f64.const 4.940656e-324)) (i64.const 0)) +(assert_return (invoke "i64.trunc_f64_u" (f64.const -4.940656e-324)) (i64.const 0)) +(assert_return (invoke "i64.trunc_f64_u" (f64.const 1.0)) (i64.const 1)) +(assert_return (invoke "i64.trunc_f64_u" (f64.const 1.100000e+00)) (i64.const 1)) +(assert_return (invoke "i64.trunc_f64_u" (f64.const 1.5)) (i64.const 1)) +(assert_return (invoke "i64.trunc_f64_u" (f64.const 4294967295)) (i64.const 0xffffffff)) +(assert_return (invoke "i64.trunc_f64_u" (f64.const 4294967296)) (i64.const 0x100000000)) +(assert_return (invoke "i64.trunc_f64_u" (f64.const 18446744073709549568.0)) (i64.const -2048)) +(assert_return (invoke "i64.trunc_f64_u" (f64.const -9.000000e-01)) (i64.const 0)) +(assert_return (invoke "i64.trunc_f64_u" (f64.const 1e8)) (i64.const 100000000)) +(assert_return (invoke "i64.trunc_f64_u" (f64.const 1e16)) (i64.const 10000000000000000)) +(assert_return (invoke "i64.trunc_f64_u" (f64.const 9223372036854775808)) (i64.const -9223372036854775808)) +(assert_trap (invoke "i64.trunc_f64_u" (f64.const 18446744073709551616.0)) "integer overflow") +(assert_trap (invoke "i64.trunc_f64_u" (f64.const -1.0)) "integer overflow") +(assert_trap (invoke "i64.trunc_f64_u" (f64.const Infinity)) "integer overflow") +(assert_trap (invoke "i64.trunc_f64_u" (f64.const -Infinity)) "integer overflow") +(assert_trap (invoke "i64.trunc_f64_u" (f64.const NaN)) "invalid conversion to integer") +;; (assert_trap (invoke "i64.trunc_f64_u" (f64.const nan:0x4000000000000)) "invalid conversion to integer") +;; (assert_trap (invoke "i64.trunc_f64_u" (f64.const -nan)) "invalid conversion to integer") +;; (assert_trap (invoke "i64.trunc_f64_u" (f64.const -nan:0x4000000000000)) "invalid conversion to integer") + +(assert_return (invoke "f32.convert_i32_s" (i32.const 1)) (f32.const 1.0)) +(assert_return (invoke "f32.convert_i32_s" (i32.const -1)) (f32.const -1.0)) +(assert_return (invoke "f32.convert_i32_s" (i32.const 0)) (f32.const 0.0)) +(assert_return (invoke "f32.convert_i32_s" (i32.const 2147483647)) (f32.const 2147483648)) +(assert_return (invoke "f32.convert_i32_s" (i32.const -2147483648)) (f32.const -2147483648)) +(assert_return (invoke "f32.convert_i32_s" (i32.const 1234567890)) (f32.const 1.234568e+09)) +;; Test rounding directions. +(assert_return (invoke "f32.convert_i32_s" (i32.const 16777217)) (f32.const 16777216.0)) +(assert_return (invoke "f32.convert_i32_s" (i32.const -16777217)) (f32.const -16777216.0)) +(assert_return (invoke "f32.convert_i32_s" (i32.const 16777219)) (f32.const 16777220.0)) +(assert_return (invoke "f32.convert_i32_s" (i32.const -16777219)) (f32.const -16777220.0)) + +(assert_return (invoke "f32.convert_i64_s" (i64.const 1)) (f32.const 1.0)) +(assert_return (invoke "f32.convert_i64_s" (i64.const -1)) (f32.const -1.0)) +(assert_return (invoke "f32.convert_i64_s" (i64.const 0)) (f32.const 0.0)) +(assert_return (invoke "f32.convert_i64_s" (i64.const 9223372036854775807)) (f32.const 9223372036854775807)) +(assert_return (invoke "f32.convert_i64_s" (i64.const -9223372036854775808)) (f32.const -9223372036854775808)) +(assert_return (invoke "f32.convert_i64_s" (i64.const 314159265358979)) (f32.const 3.141593e+14)) ;; PI +;; Test rounding directions. +(assert_return (invoke "f32.convert_i64_s" (i64.const 16777217)) (f32.const 16777216.0)) +(assert_return (invoke "f32.convert_i64_s" (i64.const -16777217)) (f32.const -16777216.0)) +(assert_return (invoke "f32.convert_i64_s" (i64.const 16777219)) (f32.const 16777220.0)) +(assert_return (invoke "f32.convert_i64_s" (i64.const -16777219)) (f32.const -16777220.0)) + +(assert_return (invoke "f64.convert_i32_s" (i32.const 1)) (f64.const 1.0)) +(assert_return (invoke "f64.convert_i32_s" (i32.const -1)) (f64.const -1.0)) +(assert_return (invoke "f64.convert_i32_s" (i32.const 0)) (f64.const 0.0)) +(assert_return (invoke "f64.convert_i32_s" (i32.const 2147483647)) (f64.const 2147483647)) +(assert_return (invoke "f64.convert_i32_s" (i32.const -2147483648)) (f64.const -2147483648)) +(assert_return (invoke "f64.convert_i32_s" (i32.const 987654321)) (f64.const 987654321)) + +(assert_return (invoke "f64.convert_i64_s" (i64.const 1)) (f64.const 1.0)) +(assert_return (invoke "f64.convert_i64_s" (i64.const -1)) (f64.const -1.0)) +(assert_return (invoke "f64.convert_i64_s" (i64.const 0)) (f64.const 0.0)) +(assert_return (invoke "f64.convert_i64_s" (i64.const 9223372036854775807)) (f64.const 9223372036854775807)) +(assert_return (invoke "f64.convert_i64_s" (i64.const -9223372036854775808)) (f64.const -9223372036854775808)) +(assert_return (invoke "f64.convert_i64_s" (i64.const 4669201609102990)) (f64.const 4669201609102990)) ;; Feigenbaum +;; Test rounding directions. +(assert_return (invoke "f64.convert_i64_s" (i64.const 9007199254740993)) (f64.const 9007199254740992)) +(assert_return (invoke "f64.convert_i64_s" (i64.const -9007199254740993)) (f64.const -9007199254740992)) +(assert_return (invoke "f64.convert_i64_s" (i64.const 9007199254740995)) (f64.const 9007199254740996)) +(assert_return (invoke "f64.convert_i64_s" (i64.const -9007199254740995)) (f64.const -9007199254740996)) + +(assert_return (invoke "f32.convert_i32_u" (i32.const 1)) (f32.const 1.0)) +(assert_return (invoke "f32.convert_i32_u" (i32.const 0)) (f32.const 0.0)) +(assert_return (invoke "f32.convert_i32_u" (i32.const 2147483647)) (f32.const 2147483648)) +(assert_return (invoke "f32.convert_i32_u" (i32.const -2147483648)) (f32.const 2147483648)) +(assert_return (invoke "f32.convert_i32_u" (i32.const 0x12345678)) (f32.const 3.054199e+08)) +(assert_return (invoke "f32.convert_i32_u" (i32.const 0xffffffff)) (f32.const 4294967296.0)) +(assert_return (invoke "f32.convert_i32_u" (i32.const 0x80000080)) (f32.const 2.147484e+09)) +(assert_return (invoke "f32.convert_i32_u" (i32.const 0x80000081)) (f32.const 2.147484e+09)) +(assert_return (invoke "f32.convert_i32_u" (i32.const 0x80000082)) (f32.const 2.147484e+09)) +(assert_return (invoke "f32.convert_i32_u" (i32.const 0xfffffe80)) (f32.const 4.294967e+09)) +(assert_return (invoke "f32.convert_i32_u" (i32.const 0xfffffe81)) (f32.const 4.294967e+09)) +(assert_return (invoke "f32.convert_i32_u" (i32.const 0xfffffe82)) (f32.const 4.294967e+09)) +;; Test rounding directions. +(assert_return (invoke "f32.convert_i32_u" (i32.const 16777217)) (f32.const 16777216.0)) +(assert_return (invoke "f32.convert_i32_u" (i32.const 16777219)) (f32.const 16777220.0)) + +(assert_return (invoke "f32.convert_i64_u" (i64.const 1)) (f32.const 1.0)) +(assert_return (invoke "f32.convert_i64_u" (i64.const 0)) (f32.const 0.0)) +(assert_return (invoke "f32.convert_i64_u" (i64.const 9223372036854775807)) (f32.const 9223372036854775807)) +(assert_return (invoke "f32.convert_i64_u" (i64.const -9223372036854775808)) (f32.const 9223372036854775808)) +(assert_return (invoke "f32.convert_i64_u" (i64.const 0xffffffffffffffff)) (f32.const 18446744073709551616.0)) +;; Test rounding directions. +(assert_return (invoke "f32.convert_i64_u" (i64.const 16777217)) (f32.const 16777216.0)) +(assert_return (invoke "f32.convert_i64_u" (i64.const 16777219)) (f32.const 16777220.0)) + +(assert_return (invoke "f64.convert_i32_u" (i32.const 1)) (f64.const 1.0)) +(assert_return (invoke "f64.convert_i32_u" (i32.const 0)) (f64.const 0.0)) +(assert_return (invoke "f64.convert_i32_u" (i32.const 2147483647)) (f64.const 2147483647)) +(assert_return (invoke "f64.convert_i32_u" (i32.const -2147483648)) (f64.const 2147483648)) +(assert_return (invoke "f64.convert_i32_u" (i32.const 0xffffffff)) (f64.const 4294967295.0)) + +(assert_return (invoke "f64.convert_i64_u" (i64.const 1)) (f64.const 1.0)) +(assert_return (invoke "f64.convert_i64_u" (i64.const 0)) (f64.const 0.0)) +(assert_return (invoke "f64.convert_i64_u" (i64.const 9223372036854775807)) (f64.const 9223372036854775807)) +(assert_return (invoke "f64.convert_i64_u" (i64.const -9223372036854775808)) (f64.const 9223372036854775808)) +(assert_return (invoke "f64.convert_i64_u" (i64.const 0xffffffffffffffff)) (f64.const 18446744073709551616.0)) +(assert_return (invoke "f64.convert_i64_u" (i64.const 0x8000000000000400)) (f64.const 9.223372e+18)) +(assert_return (invoke "f64.convert_i64_u" (i64.const 0x8000000000000401)) (f64.const 9.223372e+18)) +(assert_return (invoke "f64.convert_i64_u" (i64.const 0x8000000000000402)) (f64.const 9.223372e+18)) +(assert_return (invoke "f64.convert_i64_u" (i64.const 0xfffffffffffff400)) (f64.const 1.844674e+19)) +(assert_return (invoke "f64.convert_i64_u" (i64.const 0xfffffffffffff401)) (f64.const 1.844674e+19)) +(assert_return (invoke "f64.convert_i64_u" (i64.const 0xfffffffffffff402)) (f64.const 1.844674e+19)) +;; Test rounding directions. +(assert_return (invoke "f64.convert_i64_u" (i64.const 9007199254740993)) (f64.const 9007199254740992)) +(assert_return (invoke "f64.convert_i64_u" (i64.const 9007199254740995)) (f64.const 9007199254740996)) + +(assert_return (invoke "f64.promote_f32" (f32.const 0.0)) (f64.const 0.0)) +(assert_return (invoke "f64.promote_f32" (f32.const -0.0)) (f64.const -0.0)) +(assert_return (invoke "f64.promote_f32" (f32.const 1.123)) (f64.const 1.123)) +(assert_return (invoke "f64.promote_f32" (f32.const Infinity)) (f64.const Infinity)) +(assert_return (invoke "f64.promote_f32" (f32.const -Infinity)) (f64.const -Infinity)) +;; (assert_return_canonical_nan (invoke "f64.promote_f32" (f32.const nan))) +;; (assert_return_arithmetic_nan (invoke "f64.promote_f32" (f32.const nan:0x200000))) +;; (assert_return_canonical_nan (invoke "f64.promote_f32" (f32.const -nan))) +;; (assert_return_arithmetic_nan (invoke "f64.promote_f32" (f32.const -nan:0x200000))) + +(assert_return (invoke "f32.demote_f64" (f64.const 0.0)) (f32.const 0.0)) +(assert_return (invoke "f32.demote_f64" (f64.const -0.0)) (f32.const -0.0)) +(assert_return (invoke "f32.demote_f64" (f64.const 4.940656e-324)) (f32.const 0.0)) +(assert_return (invoke "f32.demote_f64" (f64.const -4.940656e-324)) (f32.const -0.0)) +(assert_return (invoke "f32.demote_f64" (f64.const 1.0)) (f32.const 1.0)) +(assert_return (invoke "f32.demote_f64" (f64.const -1.0)) (f32.const -1.0)) + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/functions_call.wast b/docs/runtimeverification-wasm-semantics/tests/simple/functions_call.wast new file mode 100644 index 000000000..39a804c9f --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/functions_call.wast @@ -0,0 +1,277 @@ +;; Simple add function + +(module + (type $a-cool-type (func (param i32) (param $b i32) ( result i32 ))) +) + +#assertType 0 [ i32 i32 ] -> [ i32 ] +#assertNextTypeIdx 1 + +(module + (type $a-cool-type (func (param i32) (param $b i32) ( result i32 ))) + (func $x (type $a-cool-type) + (local.get 0) + (local.get 1) + (i32.add) + (return) + ) + (export "000" (func 0)) + +;; String-named add function + + (func $add (type $a-cool-type) (param $a i32) (param i32) ( result i32 ) + (local.get $a) + (local.get 1) + (i32.add) + (return) + ) + + ;; Remove return statement, don't use explicit type name + + (func $0 (param $a i32) (param $b i32) result i32 + (local.get $a) + (local.get $b) + (i32.add) + ) + + + (table 1 funcref) + (elem 0 (i32.const 0) 2) + + ;; More complicated function with locals + + (func $1 param i64 i64 i64 result i64 local i64 + (i64.sub (local.get 2) (i64.add (local.get 0) (local.get 1))) + (local.set 3) + (local.get 3) + (return) + ) + + ( export "export-1" (func 3) ) +) + +(assert_return (invoke "000" (i32.const 7) (i32.const 8)) (i32.const 15)) +#assertFunction 0 [ i32 i32 ] -> [ i32 ] [ ] "call function 0 exists" + +#assertFunction 1 [ i32 i32 ] -> [ i32 ] [ ] "function string-named add" +#assertNextTypeIdx 2 + +(assert_return (invoke "export-1" (i64.const 100) (i64.const 43) (i64.const 22)) (i64.const -121)) +#assertFunction 3 [ i64 i64 i64 ] -> [ i64 ] [ i64 ] "call function 1 exists" + + +(i32.const 7) +(i32.const 8) +(i32.const 0) +(call_indirect (type $a-cool-type)) + +#assertTopStack < i32 > 15 "call function 0 no return" +(drop) +#assertFunction 2 [ i32 i32 ] -> [ i32 ] [ ] "call function 0 exists no return" + +;; Function with complicated declaration of types +(module + (func $2 result i32 param i32 i64 param i64 local i32 + (local.get 0) + (return) + ) + (func (export "out-of-order-type-declaration") (result i32) + (i32.const 7) + (i64.const 8) + (i64.const 5) + (call $2) + ) +) +(assert_return (invoke "out-of-order-type-declaration") (i32.const 7)) +#assertFunction 0 [ i32 i64 i64 ] -> [ i32 ] [ i32 ] "out of order type declarations" +#assertNextTypeIdx 2 + +;; Function with empty declarations of types + +(module + (func $0 param i64 i64 result result i64 param local + (local.get 0) + (return) + ) + + (func $1 (param i64 i64) (result i64) + (local.get 0) + (return) + ) + (func (export "cool") (result i64) + i64.const 10 + i64.const 11 + call $1 + ) +) + +(assert_return (invoke "cool") (i64.const 10)) +#assertFunction 1 [ i64 i64 ] -> [ i64 ] [ ] "empty type declarations" +#assertNextTypeIdx 2 + +;; Function with just a name + +(module + (func $3) + (export "return-null" (func $3) ) +) +(assert_return (invoke "return-null")) + +#assertFunction 0 [ ] -> [ ] [ ] "no domain/range or locals" + +(module + (func $add (export "add") + (param i32 i32) + (result i32) + (local.get 0) + (local.get 1) + (i32.add) + (return) + ) + + (func $sub (export "sub") + (param i32 i32) + (result i32) + (local.get 0) + (local.get 1) + (i32.sub) + (return) + ) + + (func $mul (export "mul") + (param i32 i32) + (result i32) + (local.get 0) + (local.get 1) + (i32.mul) + (return) + ) + + (func $xor (export "xor") (param i32 i32) (result i32) + (local.get 0) + (local.get 1) + (i32.xor) + ) +) + +(assert_return (invoke "add" (i32.const 3) (i32.const 5)) (i32.const 8)) +(assert_return (invoke "mul" (i32.const 3) (i32.const 5)) (i32.const 15)) +(assert_return (invoke "sub" (i32.const 12) (i32.const 5)) (i32.const 7)) +(assert_return (invoke "xor" (i32.const 3) (i32.const 5)) (i32.const 6)) + +#assertFunction 0 [ i32 i32 ] -> [ i32 ] [ ] "add function typed correctly" +#assertFunction 1 [ i32 i32 ] -> [ i32 ] [ ] "sub function typed correctly" +#assertFunction 2 [ i32 i32 ] -> [ i32 ] [ ] "mul function typed correctly" +#assertFunction 3 [ i32 i32 ] -> [ i32 ] [ ] "xor function typed correctly" +#assertNextTypeIdx 1 + +(module + (func $f1 (param $a i32) (param i32) (result i32) (local $c i32) + (local.get $a) + (local.get 1) + (i32.add) + (local.set $c) + (local.get $a) + (local.get $c) + (i32.mul) + (return) + ) + + (func $f2 (param i32 i32 i32 ) (result i32) (local i32 i32) + (local.get 0) + (local.get 2) + (call $f1) + (local.get 1) + (call $f1) + (local.get 0) + (i32.mul) + (return) + ) + + (func (export "nested-method-call") (result i32) + (i32.const 3) + (i32.const 5) + (call $f1) + (i32.const 5) + (i32.const 8) + (call $f2) + ) + +) + +(assert_return (invoke "nested-method-call") (i32.const 14247936)) +#assertFunction 0 [ i32 i32 ] -> [ i32 ] [ i32 ] "inner calling method" +#assertFunction 1 [ i32 i32 i32 ] -> [ i32 ] [ i32 i32 ] "outer calling method" + +(module + (func $func (param i32 i32) (result i32) (local.get 0)) + (func (export "aaa") (result i32) + (block (result i32) + (call $func + (block (result i32) (i32.const 1)) (i32.const 2) + ) + ) + ) +) + +(assert_return (invoke "aaa") (i32.const 1)) + +(module + (func $2 (export "cool-align-1") (export "cool-align-2") result i32 param i32 i64 param i64 local i32 + (local.get 0) + (return) + ) +) + +(assert_return (invoke "cool-align-1" (i32.const 7) (i64.const 8) (i64.const 3)) (i32.const 7)) +(assert_return (invoke "cool-align-2" (i32.const 1) (i64.const 5) (i64.const 7)) (i32.const 1)) + +#assertFunction 0 [ i32 i64 i64 ] -> [ i32 ] [ i32 ] "out of order type declarations" + +(module + (func (export "foo") (result i32) + (block $a (result i32) + (block $b (result i32) + (call $bar) + (i32.const 0) + (br $b) + ) + (drop) + (i32.const 1) + ) + ) + + (func $bar + (block $b (block $a (br $a))) + ) +) + +(assert_return (invoke "foo") (i32.const 1)) + +;; Check type is correctly desugared. + +(module + (func $1 param i64 i64 i64 result i64 local i64 + (i64.sub (local.get 2) (i64.add (local.get 0) (local.get 1))) + (local.set 3) + (local.get 3) + (return) + ) + + ( export "export-1" (func $1) ) + + (func $2 param i64 i64 i64 result i64 local i64 + (i64.sub (local.get 2) (i64.add (local.get 0) (local.get 1))) + (local.set 3) + (local.get 3) + (return) + ) +) + +(assert_return (invoke "export-1" (i64.const 100) (i64.const 43) (i64.const 22)) (i64.const -121)) +#assertFunction 0 [ i64 i64 i64 ] -> [ i64 ] [ i64 ] "call function 1 exists" +#assertType 0 [ i64 i64 i64 ] -> [ i64 ] +;; Check type was only added once. +#assertNextTypeIdx 1 + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/identifiers.wast b/docs/runtimeverification-wasm-semantics/tests/simple/identifiers.wast new file mode 100644 index 000000000..a983365ab --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/identifiers.wast @@ -0,0 +1,76 @@ +;; tests of function identifier names + +(module + (func $oeauth + (param i32 i32) + (result i32) + (local.get 0) + (local.get 1) + (i32.add) + (return) + ) + + (func $023eno!thu324 + (param i32 i32) + (result i32) + (local.get 0) + (local.get 1) + (i32.add) + (return) + ) + + (func $02$3e%no!t&hu324 + (param i32 i32) + (result i32) + (local.get 0) + (local.get 1) + (i32.add) + (return) + ) + + (func $02$3e%no!t&hu3'24*32++2ao-eunth + (param i32 i32) + (result i32) + (local.get 0) + (local.get 1) + (i32.add) + (return) + ) + + (func $02$3e%no!t&hu3'24*32++2ao-eu//nh? + (param i32 i32) + (result i32) + (local.get 0) + (local.get 1) + (i32.add) + (return) + ) + + (func $aenuth_ae`st|23~423 + (param i32 i32) + (result i32) + (local.get 0) + (local.get 1) + (i32.add) + (return) + ) + + (func $bioi::..@@?^ + (param i32 i32) + (result i32) + (local.get 0) + (local.get 1) + (i32.add) + (return) + ) +) + +#assertFunction 0 [ i32 i32 ] -> [ i32 ] [ ] "simple function name" +#assertFunction 1 [ i32 i32 ] -> [ i32 ] [ ] "identifier function name 1" +#assertFunction 2 [ i32 i32 ] -> [ i32 ] [ ] "identifier function name 2" +#assertFunction 3 [ i32 i32 ] -> [ i32 ] [ ] "identifier function name 3" +#assertFunction 4 [ i32 i32 ] -> [ i32 ] [ ] "identifier function name 3" +#assertFunction 5 [ i32 i32 ] -> [ i32 ] [ ] "identifier function name 3" +#assertFunction 6 [ i32 i32 ] -> [ i32 ] [ ] "identifier function name 3" + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/imports.wast b/docs/runtimeverification-wasm-semantics/tests/simple/imports.wast new file mode 100644 index 000000000..fc64b4ab1 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/imports.wast @@ -0,0 +1,31 @@ +(module $a + (global (export "g") (export "glob") (mut i32) (i32.const 42)) + (memory (export "m") (export "mem") (data "A")) + (type $t (func )) + (func (export "f") (export "func")) + (func (export "gunc") (param i64) (param i32) (result i32) (local.get 1)) + ) + +(register "m") + +(module + (import "m" "gunc" (func (type $t))) + (memory (import "m" "mem") 1) + (export "x" (global $x)) + (type $t (func (param i64) (param i32) (result i32))) + (func (import "m" "gunc") (type $t)) + (func (import "m" "f")) + (global $x (import "m" "g") (mut i32)) + (import "m" "g" (global (mut i32))) + (func (export "foo") (result i32) (global.get 0)) + (func (export "mod") (global.set 0 (i32.const 10))) + ) + +(assert_return (invoke "foo") (i32.const 42)) +(invoke "mod") +(invoke $a "f") +(assert_return (invoke "foo") (i32.const 10)) +(assert_return (get $a "g") (i32.const 10)) +(assert_return (get "x") (i32.const 10)) + +#clearConfig \ No newline at end of file diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/integers.wast b/docs/runtimeverification-wasm-semantics/tests/simple/integers.wast new file mode 100644 index 000000000..812bb251f --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/integers.wast @@ -0,0 +1,16 @@ +(module + (func (export "add0") (param $x i32) (result i32) (i32.add (local.get $x) (i32.const 0))) +) + +(assert_return (invoke "add0" (i32.const 123)) (i32.const 123)) +(assert_return (invoke "add0" (i32.const +123)) (i32.const 1_2_3)) +(assert_return (invoke "add0" (i32.const +123)) (i32.const 1_2_3)) +(assert_return (invoke "add0" (i32.const -1_23)) (i32.const -12_3)) +(assert_return (invoke "add0" (i32.const -0x11)) (i32.const -17)) +(assert_return (invoke "add0" (i32.const -0x1_1)) (i32.const -1_7)) +(assert_return (invoke "add0" (i32.const 0xF_FF_F)) (i32.const 65535)) +(assert_return (invoke "add0" (i32.const 0xF_FF_F)) (i32.const 65_535)) +(assert_return (invoke "add0" (i32.const -0xF_F111_1)) (i32.const -16716049)) +(assert_return (invoke "add0" (i32.const -0xAABBCCDD)) (i32.const -0xA_A_B_B_C_C_D_D)) + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/memory.wast b/docs/runtimeverification-wasm-semantics/tests/simple/memory.wast new file mode 100644 index 000000000..5610f5ca3 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/memory.wast @@ -0,0 +1,198 @@ +( memory 34) +#assertMemory 0 34 .Int "memory initial 2" + +#clearConfig + +( memory $a-memory 34) +#assertMemory $a-memory 34 .Int "memory initial 2" + +#clearConfig + +( memory 4 10 ) +#assertMemory 0 4 10 "memory initial 3" + +#clearConfig + +( memory $more-memory 4 10 ) +#assertMemory $more-memory 4 10 "memory initial 3" + +#clearConfig + +( memory $mem 0 10 ) +(memory.size) +#assertTopStack 0 "memory.size 1" +#assertMemory $mem 0 10 "memory ungrown" + +#clearConfig + +( memory $mem 0 10 ) +(memory.grow (i32.const 10)) +(memory.size) +#assertStack 10 : < i32 > 0 : .ValStack "memory grow" +(memory.grow (i32.const 1)) +#assertTopStack -1 "memory grow" +#assertMemory $mem 10 10 "memory grown" + +#clearConfig + +( memory #maxMemorySize()) +(memory.grow (i32.const 1)) +#assertTopStack -1 "memory grow max too large" +#assertMemory 0 #maxMemorySize() .Int "memory grow max too large" + +#clearConfig + +( memory 0 ) +(memory.grow (i32.const #maxMemorySize())) +(memory.size) +#assertStack #maxMemorySize() : < i32 > 0 : .ValStack "memory grow unbounded" +(memory.grow (i32.const 1)) +(memory.size) +#assertStack #maxMemorySize() : < i32 > -1 : .ValStack "memory grow unbounded" +#assertMemory 0 #maxMemorySize() .Int "memory grown unbounded" + +;; Store and load + +#clearConfig + +(memory 1) +(i32.const 1) +(i64.const 1) +(i64.store offset=2) +#assertMemoryData (3, 1) "store is little endian" +(i32.const 1) +(i64.const 257) +(i64.store8 offset=2) +#assertMemoryData (3, 1) "store8" +(i32.const 1) +(i64.const 65537) +(i64.store16 offset=2) +#assertMemoryData (3, 1) "store16" +(i32.const 1) +(i64.const #pow(i32) +Int 1) +(i64.store16 offset=2) +#assertMemoryData (3, 1) "store32" +#assertMemory 0 1 .Int "" + +#clearConfig + +(memory $foo 0) +(i32.const 0) +(i32.const 0) +(i32.store8) +#assertTrap "store to 0 size memory" +#assertMemory $foo 0 .Int "" + +#clearConfig + +(memory 1) +(i32.const 65535) +(i32.const 1) +(i32.store8) +#assertMemoryData (65535, 1) "store to memory edge" +(i32.const 65535) +(i32.const 1) +(i32.store16) +#assertTrap "store outside of size memory" +#assertMemory 0 1 .Int "" + +#clearConfig + +(memory 1) +(i32.const 15) +(i64.const #pow(i32) -Int 1) +(i64.store) +(i32.const 15) +(i32.load8_u) +#assertTopStack 255 "load8 unsigned" +(i32.const 15) +(i32.load8_s ) +#assertTopStack -1 "load8 signed" +(i32.const 16) +(i32.load16_u ) +#assertTopStack 65535 "load16 unsigned" +(i32.const 16) +(i32.load16_s ) +#assertTopStack -1 "load16 signed" +(i32.const 15) +(i64.load32_u ) +#assertTopStack #pow(i32) -Int 1 "load32 unsigned1" +(i32.const 15) +(i64.load32_s ) +#assertTopStack -1 "load32 signed1" +(i32.const 17) +(i64.load32_u ) +#assertTopStack 65535 "load32 unsigned2" +(i32.const 17) +(i64.load32_u ) +#assertTopStack 65535 "load32 signed2" +#assertMemoryData (15, 255) "" +#assertMemoryData (16, 255) "" +#assertMemoryData (17, 255) "" +#assertMemoryData (18, 255) "" +#assertMemory 0 1 .Int "" + +;; Updating + +#clearConfig + +(memory 1) +(i32.const 1) +(i64.const #pow(i64) -Int 1) +(i64.store) +(i32.const 5) (i32.const 0) +(i32.store ) +(i32.const 3) (i32.const 0) +(i32.store16 ) +(i32.const 1) (i32.const 0) +(i32.store8 ) +(i32.const 2) (i32.const 0) +(i32.store8 ) +#assertMemory 0 1 .Int "Zero updates erases memory" + +#clearConfig + +(memory 1) +(i32.const 1) (i64.const #pow(i64) -Int 1) +(i64.store ) +(i32.const 2) (i32.const 0) +(i32.store8 ) +(i32.const 4) (i32.const 0) +(i32.store ) +#assertMemoryData (1, 255) "" +#assertMemoryData (3, 255) "" +#assertMemoryData (8, 255) "" +#assertMemory 0 1 .Int "Zero updates don't over-erase" + +#clearConfig + +(module + (memory 0) +) + +(module + (memory (data "A")) +) + +#assertMemoryData (0, 65) "" + +(module + (memory 1) + (func $start (i32.store (i32.const 0) (i32.const 42))) + (start $start) +) + +#assertMemoryData 1 (0, 65) "Start didn't modify other memory" +#assertMemoryData (0, 42) "Start function modified its own memory" + +(module + (memory 0) + + (func (export "load_at_zero") (result i32) (i32.load (i32.const 0))) + (func (export "store_at_zero") (i32.store (i32.const 0) (i32.const 2))) +) + +(assert_trap (invoke "store_at_zero") "out of bounds memory access") +(assert_trap (invoke "load_at_zero") "out of bounds memory access") + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/modules.wast b/docs/runtimeverification-wasm-semantics/tests/simple/modules.wast new file mode 100644 index 000000000..4266b9c8a --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/modules.wast @@ -0,0 +1,58 @@ +(module $myMod) + +#assertNamedModule $myMod "named empty module" + +(module $anotherName) + +(register "a module name") + +#assertRegistrationNamed "a module name" $anotherName "registration1" +#assertNamedModule $anotherName "named registered module" + +(module $myMod2) + +(module) + +(module $myMod3) + +(register "a module name 2" $myMod2) +(register "another module name" $myMod3) +(register "third module name") + +#assertRegistrationNamed "another module name" $myMod3 "registration3" +#assertRegistrationNamed "a module name 2" $myMod2 "registration4" +#assertRegistrationUnnamed "third module name" "registration5" + +(assert_malformed + (module quote "(func block end $l)") + "mismatching label" +) + +(assert_malformed + (module quote "(func block $a end $l)") + "mismatching label" +) + +#clearConfig + +;; Test ordering of definitions in modules. + +(module + (start $main) ;; Should initialize memory position 1. + (elem (i32.const 1) $store) + (data (i32.const 100) "ba") + (data (i32.const 100) "c") ;; Should overwrite previous, leaving "5 1" as memory bytes + (func) + (func $main (call_indirect (i32.const 1))) ;; Should call $store. + (func $store (i32.store (i32.const 1) (i32.const 42))) + (func $get (export "get") (result i32) + (i32.add (i32.load (i32.const 1)) (i32.load (i32.const 100))) ;; For checking both data initialization. + ) + (memory 10 10) + (elem (i32.const 0) 0) + (table 2 funcref) +) + +(assert_return (invoke "get") i32.const 24973 ) + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/polymorphic.wast b/docs/runtimeverification-wasm-semantics/tests/simple/polymorphic.wast new file mode 100644 index 000000000..e507b17bf --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/polymorphic.wast @@ -0,0 +1,68 @@ +;; drop + +(i32.const 15) +(drop) +#assertStack .ValStack "drop i32" + +(i64.const 15) +(drop) +#assertStack .ValStack "drop i64" + +(f32.const 15.0) +(drop) +#assertStack .ValStack "drop f32" + +(f64.const 15.0) +(drop) +#assertStack .ValStack "drop f64" + +(i32.const 5) +(drop (i32.const 1)) +#assertTopStack < i32 > 5 "folded drop" + +;; select + +(i32.const -1) +(i32.const 1) +(i32.const 1) +(select) +#assertTopStack < i32 > -1 "select i32 true" + +(i32.const -1) +(i32.const 1) +(i32.const 0) +(select) +#assertTopStack < i32 > 1 "select i32 false" + +(i64.const -1) +(i64.const 1) +(i32.const 1) +(select) +#assertTopStack < i64 > -1 "select i64 true" + +(i64.const -1) +(i64.const 1) +(i32.const 0) +(select) +#assertTopStack < i64 > 1 "select i64 false" + +(select (i32.const 1) (i32.const 0) (i32.const 1)) +#assertTopStack < i32 > 1 "folded select i32" + +(select (i64.const 1) (i64.const 0) (i32.const 0)) +#assertTopStack < i64 > 0 "folded select i64" + +(select (unreachable) (i64.const -1) (i32.const 0)) +#assertTrap "select strict in first branch" + +(select (i64.const 1) (unreachable) (i32.const 0)) +#assertTrap "select strict in second branch" +#assertTopStack < i64 > 1 "select strict in second branch" + +(select (i64.const 1) (i64.const -1) (unreachable)) +#assertTrap "select strict in condition" +#assertTopStack < i64 > -1 "select strict in condition" +(drop) +#assertTopStack < i64 > 1 "select strict in condition" + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/start.wast b/docs/runtimeverification-wasm-semantics/tests/simple/start.wast new file mode 100644 index 000000000..5301f14b8 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/start.wast @@ -0,0 +1,38 @@ +(module + (memory 1) + (func $inc + (i32.store8 + (i32.const 0) + (i32.add + (i32.load8_u (i32.const 0)) + (i32.const 1))) + ) + (func $main + (i32.store (i32.const 0) (i32.const 65)) + (call $inc) + (call $inc) + (call $inc) + ) + (start $main) +) + +#assertMemoryData (0, 68) "start inc" +#assertFunction 0 [ ] -> [ ] [ ] "$inc" +#assertFunction 1 [ ] -> [ ] [ ] "$main" +#assertMemory 0 1 .Int "" + +#clearConfig + +(module + (func $foo (unreachable)) + (start $foo) +) +#assertTrap "Trap propagates through start invocation" +#assertFunction 0 [ ] -> [ ] [ ] "" + +(assert_trap + (module (func $main (unreachable)) (start $main)) + "unreachable" +) + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/table.wast b/docs/runtimeverification-wasm-semantics/tests/simple/table.wast new file mode 100644 index 000000000..fe7972b0b --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/table.wast @@ -0,0 +1,112 @@ +(module ( table 0 funcref ) ) +#assertTable 0 0 .Int "table initial 1" + +(module ( table $named 4 funcref) ) +#assertTable $named 4 .Int "table initial 2" + +(module ( table 14 21 funcref ) ) +#assertTable 0 14 21 "table initial 3" + +(module ( + table $named2 funcref (elem $f $g $k)) + (func $f) (func $g) (func $k) +) +#assertTableElem (0, 0) "table elem 0" +#assertTableElem (1, 1) "table elem 1" +#assertTableElem (2, 2) "table elem 2" +#assertTable $named2 3 3 "table one with elements" + +(module + ( elem 0 (i32.const 1) $f $g) + ( table 4 funcref) + (func $f) (func $g) +) + +#assertTableElem (1, 3) "table elem 1" +#assertTableElem (2, 4) "table elem 2" +#assertTable 0 4 .Int "table two with elements" + +(module + ( elem (i32.const 1) func $f $g) + ( table 4 funcref) + (func $f) (func $g) +) + +#assertTableElem (1, 5) "table elem 1" +#assertTableElem (2, 6) "table elem 2" +#assertTable 0 4 .Int "table two with elements" + +(module + (type $out-i32 (func (result i32))) + (table $tab 10 funcref) + (elem (i32.const 8) $const-i32-a) + (elem (i32.const 9) $const-i32-b) + (func $const-i32-a (type $out-i32) (i32.const 65)) + (func $const-i32-b (type $out-i32) (i32.const 66)) + (func (export "call-7") (type $out-i32) + (call_indirect (type $out-i32) (i32.const 7)) + ) + (func (export "call-8") (type $out-i32) + (call_indirect (type $out-i32) (i32.const 8)) + ) + (func (export "call-9") (type $out-i32) + (call_indirect (type $out-i32) (i32.const 9)) + ) +) + +(invoke "call-8") + +#assertTopStack < i32> 65 "call_indirect_result1" + +(invoke "call-9") + +#assertTopStack < i32> 66 "call_indirect_result2" + +#assertFunction 0 [ ] -> [ i32 ] [ ] "call function 1 exists" +#assertFunction 1 [ ] -> [ i32 ] [ ] "call function 2 exists" +#assertFunction 2 [ ] -> [ i32 ] [ ] "call function 3 exists" +#assertFunction 3 [ ] -> [ i32 ] [ ] "call function 4 exists" +#assertFunction 4 [ ] -> [ i32 ] [ ] "call function 5 exists" +#assertTableElem (8, 7) "table elem 8" +#assertTableElem (9, 8) "table elem 9" +#assertTable $tab 10 .Int "table three with elements" + +;; Test offset unfolding. + +(module + (table $t 10 funcref) + (type $typ (func)) + (func) + (elem (offset (i32.const 0)) 0) + (elem (offset (nop) (i32.const 1)) 0) + (elem (offset (i32.const 2) (nop)) 0) + (elem $t (offset (i32.const 3)) 0) + (elem $t (offset (nop) (i32.const 4)) 0) + (elem $t (offset (i32.const 5) (nop)) 0) + + (elem (offset (i32.const 6 (nop))) 0) + (elem $t (offset (i32.const 7 (nop))) 0) + + (global $g i32 (i32.const 8)) + (global $h i32 (i32.const 9)) + + (elem (offset (global.get $g)) 0) + (elem $t (offset (global.get $h)) 0) + + (func $main (local i32) + (local.set 0 (i32.const 7)) + loop + (local.get 0) + (call_indirect (type $typ)) + (i32.sub (local.get 0) (i32.const 1)) + (local.tee 0) + (i32.eqz) + (br_if 1) + (br 0) + end + ) + + (start $main) +) + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/text2abstract.wast b/docs/runtimeverification-wasm-semantics/tests/simple/text2abstract.wast new file mode 100644 index 000000000..d7f9e4a2d --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/text2abstract.wast @@ -0,0 +1,157 @@ +(module) + +(module $id + (func (export "foo") (param $a i32) (result i32) + local.get $a + ) + + (func (export "bar") (param $a i32) (result i32) + block (result i32) + local.get $a + end + ) + + (func (export "baz") (param $a i32) (result i32) + loop (result i32) + local.get $a + end + ) + + (func (export "baf") (param $a i32) (result i32) + i32.const 1 + if (result i32) + local.get $a + else + local.get $a + end + ) + + (func (export "bag") (param $a i32) (result i32) + i32.const 0 + if (result i32) + local.get $a + else + local.get $a + end + ) + + (func $far (param $a i32) (result i32) (local $b i64) + local.get $a + ) +) + +(assert_return (invoke "foo" (i32.const 7)) (i32.const 7)) +(assert_return (invoke "bar" (i32.const 7)) (i32.const 7)) +(assert_return (invoke "baz" (i32.const 7)) (i32.const 7)) +(assert_return (invoke "baf" (i32.const 7)) (i32.const 7)) +(assert_return (invoke "bag" (i32.const 7)) (i32.const 7)) + +#assertFunction 5 [ i32 ] -> [ i32 ] [ i64 ] "identifiers are erased inside module" + +#clearConfig + +;; With sugared blocks + +(module $id + +(type $i32->i32 (func (param i32) (result i32))) + + (func (export "foo") (param $a i32) (result i32) + (local.get $a) + ) + + (func (export "bar") (type 0) (param $a i32) (result i32) + (block (result i32) + (local.get $a)) + ) + + (func (export "baz") (type $i32->i32) (param $a i32) (result i32) + (loop (result i32) + (local.get $a)) + ) + + (func (export "baf") (param $a i32) (result i32) + i32.const 1 + (if (result i32) + (then (local.get $a)) + (else (local.get $a)) + ) + ) + + (func (export "bag") (param $a i32) (result i32) + i32.const 0 + (if (result i32) + (then (local.get $a)) + (else (local.get $a)) + ) + ) +) + +(assert_return (invoke "foo" (i32.const 7)) (i32.const 7)) +(assert_return (invoke "bar" (i32.const 7)) (i32.const 7)) +(assert_return (invoke "baz" (i32.const 7)) (i32.const 7)) +(assert_return (invoke "baf" (i32.const 7)) (i32.const 7)) +(assert_return (invoke "bag" (i32.const 7)) (i32.const 7)) + +#clearConfig + +(type (func (param i32) (result i32))) + +(func $foo (type 0) (param $a i32) (result i32) + local.get $a +) +(export "foo" (func 0)) + +(func $bar (type 0) (param $a i32) (result i32) + block (result i32) + local.get $a + end +) + (export "bar" (func 1)) + +(func $baz (type 0) (param $a i32) (result i32) + loop (result i32) + local.get $a + end +) +(export "baz" (func 2)) + +(func $baf (type 0) (param $a i32) (result i32) + i32.const 1 + if (result i32) + local.get $a + else + local.get $a + end +) +(export "baf" (func 3)) + +(func $bag (type 0) (param $a i32) (result i32) + i32.const 0 + if (result i32) + local.get $a + else + local.get $a + end +) +(export "bag" (func 4)) + +(func $far (type 0) (param $a i32) (result i32) (local $b i64) + local.get $a +) + +(assert_return (invoke "foo" (i32.const 7)) (i32.const 7)) +(assert_return (invoke "bar" (i32.const 7)) (i32.const 7)) +(assert_return (invoke "baz" (i32.const 7)) (i32.const 7)) +(assert_return (invoke "baf" (i32.const 7)) (i32.const 7)) +(assert_return (invoke "bag" (i32.const 7)) (i32.const 7)) + +#assertFunction 5 [ i32 ] -> [ i32 ] [ i64 ] "identifiers are erased outside module" + +(type (func)) + +(func $fir (type 1) (local i64) (local $a i32)) + +#assertFunction 6 [ ] -> [ ] [ i64 i32 ] "identifiers are erased inside module" + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/unicode.wast b/docs/runtimeverification-wasm-semantics/tests/simple/unicode.wast new file mode 100644 index 000000000..33cf24122 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/unicode.wast @@ -0,0 +1,39 @@ +(module + (memory 1) + (data (i32.const 0) "~!@#$%\u{1}\u{11}\u{334}\u{EDFF}\u{BBBBB}\u{10EFEF}") + (func (export "load8_u") (param $i i32) (result i64) + (i64.load8_u offset=0 (local.get $i)) + ) + (func (export "load16_u") (param $i i32) (result i64) + (i64.load16_u offset=0 (local.get $i)) + ) + (func (export "load32_u") (param $i i32) (result i64) + (i64.load32_u offset=0 (local.get $i)) + ) +) + +(assert_return (invoke "load8_u" (i32.const 0)) (i64.const 126)) +(assert_return (invoke "load8_u" (i32.const 3)) (i64.const 35)) +(assert_return (invoke "load8_u" (i32.const 6)) (i64.const 1)) +(assert_return (invoke "load8_u" (i32.const 9)) (i64.const 180)) +(assert_return (invoke "load8_u" (i32.const 11)) (i64.const 183)) +(assert_return (invoke "load8_u" (i32.const 14)) (i64.const 187)) +(assert_return (invoke "load8_u" (i32.const 15)) (i64.const 174)) + +(assert_return (invoke "load16_u" (i32.const 0)) (i64.const 8574)) +(assert_return (invoke "load16_u" (i32.const 1)) (i64.const 16417)) +(assert_return (invoke "load16_u" (i32.const 2)) (i64.const 9024)) +(assert_return (invoke "load16_u" (i32.const 4)) (i64.const 9508)) +(assert_return (invoke "load16_u" (i32.const 12)) (i64.const 62143)) +(assert_return (invoke "load16_u" (i32.const 13)) (i64.const 48114)) +(assert_return (invoke "load16_u" (i32.const 16)) (i64.const 62651)) + +(assert_return (invoke "load32_u" (i32.const 2)) (i64.const 623125312)) +(assert_return (invoke "load32_u" (i32.const 3)) (i64.const 19211299)) +(assert_return (invoke "load32_u" (i32.const 6)) (i64.const 3033272577)) +(assert_return (invoke "load32_u" (i32.const 7)) (i64.const 4004826129)) +(assert_return (invoke "load32_u" (i32.const 11)) (i64.const 3153248183)) +(assert_return (invoke "load32_u" (i32.const 16)) (i64.const 3213817019)) +(assert_return (invoke "load32_u" (i32.const 19)) (i64.const 44991)) + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/simple/variables.wast b/docs/runtimeverification-wasm-semantics/tests/simple/variables.wast new file mode 100644 index 000000000..a04736237 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/simple/variables.wast @@ -0,0 +1,62 @@ +;; Test locals + +init_locals < i32 > 0 : < i32 > 0 : < i32 > 0 : .ValStack + +(i32.const 43) +(local.set 0) +#assertLocal 0 < i32 > 43 "set_local" + +(i32.const 55) +(local.set 1) +(local.get 1) +#assertTopStack < i32 > 55 "set_local stack" +#assertLocal 1 < i32 > 55 "set_local" + +(i32.const 67) +(local.tee 2) +#assertTopStack < i32 > 67 "tee_local stack" +#assertLocal 2 < i32 > 67 "tee_local local" + +;; Test globals + +(module + (global (mut i32) (i32.const 0)) + (global $someglobal (mut i32) (i32.const 0)) + + (func + (i32.const 43) + (global.set 0) + ) + + (func (export "set") + (i32.const 55) + (global.set $someglobal) + ) + + (start 0) +) +#assertGlobal 0 < i32 > 43 "set_global" + +(invoke "set") +#assertGlobal $someglobal < i32 > 55 "set_global" + +;; Test global folded forms + +#clearConfig + +(module + (global (mut i32) (i32.const 0)) + (global (mut i32) (i32.const 0)) + + (func + (global.set 1 (i32.const 99)) + (global.set 0 (i32.const 77)) + ) + + (start 0) +) + +#assertGlobal 1 < i32 > 99 "set_global folded" +#assertGlobal 0 < i32 > 77 "set_global folded 2" + +#clearConfig diff --git a/docs/runtimeverification-wasm-semantics/tests/success-k.out b/docs/runtimeverification-wasm-semantics/tests/success-k.out new file mode 100644 index 000000000..c25e493df --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/success-k.out @@ -0,0 +1,3 @@ + + . + diff --git a/docs/runtimeverification-wasm-semantics/tests/success-llvm.out b/docs/runtimeverification-wasm-semantics/tests/success-llvm.out new file mode 100644 index 000000000..873d19486 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/tests/success-llvm.out @@ -0,0 +1,62 @@ + + + . + + + + . + + + .ValStack + + + + .Map + + + .Int + + + + .Map + + + .Map + + + .ModuleInstCellMap + + + 0 + + + + .FuncDefCellMap + + + 0 + + + .TabInstCellMap + + + 0 + + + .MemInstCellMap + + + 0 + + + .GlobalInstCellMap + + + 0 + + + + true + + + diff --git a/docs/runtimeverification-wasm-semantics/wasm-text.md b/docs/runtimeverification-wasm-semantics/wasm-text.md new file mode 100644 index 000000000..a5e8c34d9 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/wasm-text.md @@ -0,0 +1,1082 @@ +WebAssembly Text Format +======================= + +```k +require "wasm.md" +require "data.md" + +module WASM-TEXT-SYNTAX + imports WASM-TEXT + imports WASM-SYNTAX + imports WASM-TOKEN-SYNTAX +endmodule +``` + +Wasm Tokens +----------- + +`WASM-TOKEN-SYNTAX` module defines the tokens used in parsing programs. + +```k +module WASM-TOKEN-SYNTAX +``` + +### Strings + +In WebAssembly, strings are defined differently to K's built-in strings, so we have to write the definition of WebAssembly `WasmString` in a separate module, and use the module just for parsing the program. +Note that you cannot use a normal K `String` in any production definitions, because the definitions of `String` and `WasmString` overlap, and the K tokenizer does not support ambiguity. + +```k + syntax WasmStringToken ::= r"\\\"(([^\\\"\\\\])|(\\\\[0-9a-fA-F]{2})|(\\\\t)|(\\\\n)|(\\\\r)|(\\\\\\\")|(\\\\')|(\\\\\\\\)|(\\\\u\\{[0-9a-fA-F]{1,6}\\}))*\\\"" [token] + // ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- +``` + +### Identifiers + +In WebAssembly, identifiers are defined by the regular expression below. + +```k + syntax IdentifierToken ::= r"\\$[0-9a-zA-Z!$%&'*+/<>?_`|~=:\\@^.-]+" [token] + // ---------------------------------------------------------------------------- +``` + +### Integers + +In WebAssembly, integers could be represented in either the decimal form or hexadecimal form. +In both cases, digits can optionally be separated by underscores. + +```k + syntax WasmIntToken ::= r"[\\+-]?[0-9]+(_[0-9]+)*" [token] + | r"[\\+-]?0x[0-9a-fA-F]+(_[0-9a-fA-F]+)*" [token] + // ------------------------------------------------------------------------ +``` + +### Layout + +WebAssembly allows for block comments using `(;` and `;)`, and line comments using `;;`. +Additionally, white-space is skipped/ignored. +Declaring regular expressions of sort `#Layout` infroms the K lexer to drop these tokens. + +```k + syntax #Layout ::= r"\\(;([^;]|(;+([^;\\)])))*;\\)" [token] + | r";;[^\\n\\r]*" [token] + | r"[\\ \\n\\r\\t]" [token] + // ----------------------------------------------------------- +``` + +```k +endmodule +``` + +Wasm Textual Format Syntax +-------------------------- + +### Values + +```k +module WASM-TEXT-COMMON-SYNTAX + imports WASM-COMMON-SYNTAX + + syntax WasmInt ::= Int + syntax WasmInt ::= WasmIntToken [klabel(WasmInt), avoid, symbol, function] + + syntax Index ::= Identifier + // --------------------------- +``` + +### Instructions + +#### Plain Instructions + +```k + syntax PlainInstr ::= "br" Index + | "br_if" Index + | "br_table" ElemSegment + | "call" Index + | "global.get" Index + | "global.set" Index + | "local.get" Index + | "local.set" Index + | "local.tee" Index + // --------------------------------------- + + syntax PlainInstr ::= IValType "." StoreOpM + | FValType "." StoreOpM + | IValType "." LoadOpM + | FValType "." LoadOpM + syntax StoreOpM ::= StoreOp | StoreOp MemArg + syntax LoadOpM ::= LoadOp | LoadOp MemArg + syntax MemArg ::= OffsetArg | AlignArg | OffsetArg AlignArg + syntax OffsetArg ::= "offset=" WasmInt + syntax AlignArg ::= "align=" WasmInt + // --------------------------------------- +``` + +#### Block Instructions + +```k + syntax Instr ::= BlockInstr + // --------------------------- + + syntax BlockInstr ::= "block" OptionalId TypeDecls Instrs "end" OptionalId + | "loop" OptionalId TypeDecls Instrs "end" OptionalId + | "if" OptionalId TypeDecls Instrs "else" OptionalId Instrs "end" OptionalId + | "if" OptionalId TypeDecls Instrs "end" OptionalId + // ------------------------------------------------------------------------------------------------ +``` + +##### Folded Instructions + +Folded instructions are a syntactic sugar where expressions can be grouped using parentheses for higher readability. + +```k + syntax Instr ::= FoldedInstr + // ---------------------------- +``` + +One type of folded instruction are `PlainInstr`s wrapped in parentheses and optionally includes nested folded instructions to indicate its operands. + +```k + syntax FoldedInstr ::= "(" PlainInstr Instrs ")" + | "(" PlainInstr ")" [prefer] + // --------------------------------------------------------- + + syntax FoldedInstr ::= "(" "block" OptionalId TypeDecls Instrs ")" + | "(" "loop" OptionalId TypeDecls Instrs ")" + | "(" "if" OptionalId TypeDecls Instrs "(" "then" Instrs ")" ")" + | "(" "if" OptionalId TypeDecls Instrs "(" "then" Instrs ")" "(" "else" Instrs ")" ")" + // ----------------------------------------------------------------------------------------------------------- +``` + +### Types + +```k + syntax TypeDefn ::= "(type" OptionalId "(" "func" TypeDecls ")" ")" + // ------------------------------------------------------------------- + + syntax TextLimits ::= Int | Int Int + // ----------------------------------- +``` + +### Exports + +Exports can be declared like regular functions, memories, etc., by giving an inline export declaration. +In that case, it simply desugars to the definition followed by an export of it. +If no identifer is present, one must be introduced so that the export can refer to it. +Note that it is possible to define multiple exports inline, i.e. export a single entity under many names. + +```k + syntax ExportDefn ::= "(" "export" WasmString "(" Externval ")" ")" + // ------------------------------------------------------------------- + + syntax InlineExport ::= "(" "export" WasmString ")" + // ---------------------------------------------------- +``` + +### Imports + +```k + syntax ImportDefn ::= "(" "import" WasmString WasmString ImportDesc ")" + // ----------------------------------------------------------------------- +``` + +Imports can be declared like regular functions, memories, etc., by giving an inline import declaration. + +```k + syntax InlineImport ::= "(" "import" WasmString WasmString ")" + // -------------------------------------------------------------- +``` + +The following is the text format representation of an import specification. + +```k + syntax ImportDesc ::= "(" "func" OptionalId TypeUse ")" [klabel(funcImportDesc)] + | "(" "global" OptionalId TextFormatGlobalType ")" [klabel(globImportDesc)] + | "(" "table" OptionalId TableType ")" [klabel( tabImportDesc)] + | "(" "memory" OptionalId MemType ")" [klabel( memImportDesc)] + // ----------------------------------------------------------------------------------------------- +``` + +### Functions + +```k + syntax FuncDefn ::= "(" "func" OptionalId FuncSpec ")" + syntax FuncSpec ::= TypeUse LocalDecls Instrs + | InlineImport TypeUse + | InlineExport FuncSpec + // ----------------------------------------- +``` + +#### Function Local Declaration + +```k + syntax LocalDecl ::= "(" LocalDecl ")" [bracket] + | "local" ValTypes + | "local" Identifier ValType + syntax LocalDecls ::= List{LocalDecl , ""} [klabel(listLocalDecl)] + // ------------------------------------------------------------------------- +``` + +### Tables + +```k + syntax TableDefn ::= "(" "table" OptionalId TableSpec ")" + syntax TableSpec ::= TableType + | TableElemType "(" "elem" ElemSegment ")" + | InlineImport TableType + | InlineExport TableSpec + // ------------------------------------------- + + syntax TableType ::= TextLimits TableElemType + syntax TableElemType ::= "funcref" + // ---------------------------------- +``` + +### Memories + +```k + syntax MemoryDefn ::= "(" "memory" OptionalId MemorySpec ")" + // ------------------------------------------------------------ + + syntax MemorySpec ::= MemType + // -------------------------------- + + syntax MemorySpec ::= "(" "data" DataString ")" + | InlineImport MemType + | InlineExport MemorySpec + // --------------------------------------------- + + syntax MemType ::= TextLimits + // -------------------------------- +``` + +### Globals + +```k + syntax GlobalDefn ::= "(" "global" OptionalId GlobalSpec ")" + syntax GlobalSpec ::= TextFormatGlobalType Instr + | InlineImport TextFormatGlobalType + | InlineExport GlobalSpec + // --------------------------------------------- + + syntax TextFormatGlobalType ::= ValType | "(" "mut" ValType ")" + // --------------------------------------------------------------- +``` + +### Offset + +The `elem` and `data` initializers take an offset, which is an instruction. +This is not optional. + +```k + syntax Offset ::= "(" "offset" Instrs ")" + // ----------------------------------------- +``` + +The offset can either be specified explicitly with the `offset` key word, or be a single instruction. + +```k + syntax Offset ::= Instrs + // ------------------------ +``` + +### Element Segments + +```k + + syntax ElemDefn ::= "(" "elem" Index Offset ElemSegment ")" + | "(" "elem" Offset ElemSegment ")" + | "(" "elem" Offset "func" ElemSegment ")" + // ------------------------------------------------------------ +``` + +### Data Segments + +```k + syntax DataDefn ::= "(" "data" Index Offset DataString ")" + | "(" "data" Offset DataString ")" + // ---------------------------------------------------- +``` + +### Start Function + +```k + syntax StartDefn ::= "(" "start" Index ")" + // ------------------------------------------ +``` + +### Modules + +Modules are defined as a sequence of definitions, that may come in any order. +The only requirements are that all imports must precede all other definitions, and that there may be at most one start function. + +```k + syntax Stmt ::= ModuleDecl + syntax ModuleDecl ::= "(" "module" OptionalId Defns ")" + // ------------------------------------------------------- +``` + +```k +endmodule +``` + +Translation from Text Format to Core Format +------------------------------------------- + +```k +module WASM-TEXT + imports WASM-TEXT-COMMON-SYNTAX + imports WASM +``` + +The text format is a concrete syntax for Wasm. +It allows specifying instructions in a folded, S-expression like format, and a few other syntactic sugars. +Most instructions, those in the sort `PlainInstr`, have identical keywords in the abstract and concrete syntax, and can be used directly. + +### Text Integers + +All integers given in the text format are automatically turned into regular integers. +That means converting between hexadecimal and decimal when necessary, and removing underscores. + +**TODO**: Symbolic reasoning for sort `WasmIntToken` not tested yet. +In the future should investigate which direction the subsort should go. +(`WasmIntToken` under `Int`/`Int` under `WasmIntToken`) + +```k + rule `WasmInt`(VAL) => WasmIntToken2Int(VAL) + + syntax String ::= WasmIntToken2String ( WasmIntToken ) [function, total, hook(STRING.token2string)] + syntax Int ::= WasmIntTokenString2Int ( String ) [function] + | WasmIntToken2Int ( WasmIntToken ) [function] + // -------------------------------------------------------------------- + rule WasmIntTokenString2Int(S) => String2Base(replaceFirst(S, "0x", ""), 16) requires findString(S, "0x", 0) =/=Int -1 + rule WasmIntTokenString2Int(S) => String2Base( S, 10) requires findString(S, "0x", 0) ==Int -1 + + rule WasmIntToken2Int(VAL) => WasmIntTokenString2Int(replaceAll(WasmIntToken2String(VAL), "_", "")) +``` + +### Identifiers + +When we want to specify an identifier, we can do so with the following helper function. + +```k + syntax IdentifierToken ::= String2Identifier(String) [function, total, hook(STRING.string2token)] + // ------------------------------------------------------------------------------------------------------ +``` + +### Looking up Indices + +In the abstract Wasm syntax, indices are always integers. +In the text format, we extend indices to incorporate identifiers. +We also enable context lookups with identifiers. + +```k + rule #ContextLookup(IDS:Map, ID:Identifier) => {IDS [ ID ]}:>Int + requires ID in_keys(IDS) +``` + +### Desugaring + +The text format is one of the concrete formats of Wasm. +Every concrete format maps to a common structure, described as an abstract syntax. +The function `text2abstract` is a partial function which maps valid programs in the text format to the abstract format. +Some classes of invalid programs, such as those where an identifier appears in a context in which it is not declared, are undefined. + +The function deals with the desugarings which are context dependent. +Other desugarings are either left for runtime or expressed as macros (for now). + +#### Unfolding Abbreviations + +```k + syntax Stmts ::= unfoldStmts ( Stmts ) [function] + syntax Defns ::= unfoldDefns ( Defns ) [function] + | #unfoldDefns ( Defns , Int, TypesInfo ) [function] + // ------------------------------------------------------------------- + rule unfoldStmts(( module OID:OptionalId DS) SS) => ( module OID unfoldDefns(DS) ) unfoldStmts(SS) + rule unfoldStmts(.Stmts) => .Stmts + rule unfoldStmts(S SS) => S unfoldStmts(SS) [owise] + + rule unfoldDefns(DS) => #unfoldDefns(DS, 0, types2indices(DS)) + rule #unfoldDefns(.Defns, _, _) => .Defns + rule #unfoldDefns(D:Defn DS, I, TI) => D #unfoldDefns(DS, I, TI) [owise] + + syntax Defns ::= Defns "appendDefn" Defn [function] + // --------------------------------------------------- + rule (D DS) appendDefn D' => D (DS appendDefn D') + rule .Defns appendDefn D' => D' .Defns +``` + +#### Types + +The text format allows declaring a function without referencing a module-level type for that function. +If there is a module-level type matching the function type, the function is automatically assigned to that type. +The `TypeDecl` of the type is kept, since it may contain parameter identifiers. +If there is no matching module-level type, a new such type is inserted *at the end of the module*. +Since the inserted type is module-level, any subsequent functions declaring the same type will not implicitly generate a new type. + +```k + rule #unfoldDefns(( func _OID:OptionalId (TDECLS:TypeDecls => (type {M [asFuncType(TDECLS)]}:>Int) TDECLS) _LOCALS:LocalDecls _BODY:Instrs ) _DS + , _I + , #ti(... t2i: M)) + requires asFuncType(TDECLS) in_keys(M) + + rule #unfoldDefns(( func _OID:OptionalId (TDECLS:TypeDecls => (type N) TDECLS) _LOCALS:LocalDecls _BODY:Instrs ) (DS => DS appendDefn (type (func TDECLS))) + , _I + , #ti(... t2i: M => M [ asFuncType(TDECLS) <- N ], count: N => N +Int 1)) + requires notBool asFuncType(TDECLS) in_keys(M) + + rule #unfoldDefns(( func OID:OptionalId TUSE:TypeUse LOCALS:LocalDecls BODY) DS, I, TI) + => (( func OID TUSE LOCALS unfoldInstrs(BODY))) + #unfoldDefns(DS, I, TI) + requires notBool isTypeDecls(TUSE) + + rule #unfoldDefns(( import MOD NAME (func OID:OptionalId TDECLS:TypeDecls )) DS, I, #ti(... t2i: M) #as TI) + => (import MOD NAME (func OID (type {M [asFuncType(TDECLS)]}:>Int) TDECLS )) + #unfoldDefns(DS, I, TI) + requires asFuncType(TDECLS) in_keys(M) + + rule #unfoldDefns(( import MOD NAME (func OID:OptionalId TDECLS:TypeDecls)) DS, I, #ti(... t2i: M, count: N)) + => (import MOD NAME (func OID (type N) TDECLS)) + #unfoldDefns(DS appendDefn (type (func TDECLS)), I, #ti(... t2i: M [asFuncType(TDECLS) <- N], count: N +Int 1)) + requires notBool asFuncType(TDECLS) in_keys(M) + + syntax TypesInfo ::= #ti( t2i: Map, count: Int ) + syntax TypesInfo ::= types2indices( Defns ) [function] + | #types2indices( Defns, TypesInfo ) [function] + // ------------------------------------------------------------------ + rule types2indices(DS) => #types2indices(DS, #ti(... t2i: .Map, count: 0)) + + rule #types2indices(.Defns, TI) => TI + + rule #types2indices((type _OID (func TDECLS)) DS, #ti(... t2i: M, count: N)) + => #types2indices(DS, #ti(... t2i: M [ asFuncType(TDECLS) <- (M [ asFuncType(TDECLS) ] orDefault N) ], count: N +Int 1)) + + rule #types2indices(_D DS, M) => #types2indices(DS, M) [owise] +``` + +#### Functions + +```k + rule #unfoldDefns(( func OID:OptionalId (import MOD NAME) TUSE) DS, I, M) + => #unfoldDefns(( import MOD NAME (func OID TUSE) ) DS, I, M) + + rule #unfoldDefns(( func EXPO:InlineExport SPEC:FuncSpec ) DS, I, M) + => #unfoldDefns(( func #freshId(I) EXPO SPEC) DS, I +Int 1, M) + + rule #unfoldDefns(( func ID:Identifier ( export ENAME ) SPEC:FuncSpec ) DS, I, M) + => ( export ENAME ( func ID ) ) #unfoldDefns(( func ID SPEC ) DS, I, M) +``` + +#### Tables + +```k + rule #unfoldDefns(( table funcref ( elem ELEM ) ) DS, I, M) + => #unfoldDefns(( table #freshId(I) funcref ( elem ELEM ) ) DS, I +Int 1, M) + + rule #unfoldDefns(( table ID:Identifier funcref ( elem ELEM ) ) DS, I, M) + => ( table ID #lenElemSegment(ELEM) #lenElemSegment(ELEM) funcref ):TableDefn + ( elem ID (offset (i32.const 0) .Instrs) ELEM ) + #unfoldDefns(DS, I, M) + + rule #unfoldDefns(( table OID:OptionalId (import MOD NAME) TT:TableType ) DS, I, M) + => #unfoldDefns(( import MOD NAME (table OID TT) ) DS, I, M) + + rule #unfoldDefns(( table EXPO:InlineExport SPEC:TableSpec ) DS, I, M) + => #unfoldDefns(( table #freshId(I) EXPO SPEC ) DS, I +Int 1, M) + + rule #unfoldDefns(( table ID:Identifier ( export ENAME ) SPEC:TableSpec ) DS, I, M) + => ( export ENAME ( table ID ) ) #unfoldDefns(( table ID SPEC ) DS, I, M) +``` + +#### Memories + +```k + rule #unfoldDefns(( memory ( data DATA ) ) DS, I, M) + => #unfoldDefns(( memory #freshId(I) ( data DATA ) ) DS, I +Int 1, M) + + rule #unfoldDefns(( memory ID:Identifier ( data DATA ) ) DS, I, M) + => ( memory ID #lengthDataPages(DATA) #lengthDataPages(DATA) ):MemoryDefn + ( data ID (offset (i32.const 0) .Instrs) DATA ) + #unfoldDefns(DS, I, M) + + rule #unfoldDefns(( memory OID:OptionalId (import MOD NAME) MT:MemType ) DS, I, M) + => #unfoldDefns(( import MOD NAME (memory OID MT ) ) DS, I, M) + + rule #unfoldDefns(( memory EXPO:InlineExport SPEC:MemorySpec ) DS, I, M) + => #unfoldDefns(( memory #freshId(I:Int) EXPO SPEC ) DS, I +Int 1, M) + + rule #unfoldDefns(( memory ID:Identifier ( export ENAME ) SPEC:MemorySpec ) DS, I, M) + => ( export ENAME ( memory ID ) ) #unfoldDefns( ( memory ID SPEC ) DS, I, M) + + syntax Int ::= #lengthDataPages ( DataString ) [function] + // --------------------------------------------------------- + rule #lengthDataPages(DS:DataString) => lengthBytes(#DS2Bytes(DS)) up/Int #pageSize() +``` + +#### Globals + +```k + syntax GlobalType ::= asGMut (TextFormatGlobalType) [function] + // -------------------------------------------------------------- + rule asGMut ( (mut T:ValType ) ) => var T + rule asGMut ( T:ValType ) => const T + + rule #unfoldDefns((( global OID TYP:TextFormatGlobalType IS:Instr) => #global(... type: asGMut(TYP), init: unfoldInstrs(IS .Instrs), metadata: OID)) _DS, _I, _M) + + rule #unfoldDefns(( global OID:OptionalId (import MOD NAME) TYP ) DS, I, M) + => #unfoldDefns(( import MOD NAME (global OID TYP ) ) DS, I, M) + + rule #unfoldDefns(( global EXPO:InlineExport SPEC:GlobalSpec ) DS, I, M) + => #unfoldDefns(( global #freshId(I) EXPO SPEC ) DS, I +Int 1, M) + + rule #unfoldDefns(( global ID:Identifier ( export ENAME ) SPEC:GlobalSpec ) DS, I, M) + => ( export ENAME ( global ID ) ) #unfoldDefns(( global ID SPEC ) DS, I, M) +``` + +#### Element Segments + +```k + rule #unfoldDefns(((elem OFFSET func ES) => (elem OFFSET ES)) _DS, _I, _M) + rule #unfoldDefns(((elem OFFSET:Offset ES ) => ( elem 0 OFFSET ES )) _DS, _I, _M) + rule #unfoldDefns(((elem IDX OFFSET:Instrs ES ) => ( elem IDX ( offset OFFSET ) ES )) _DS, _I, _M) + + rule #unfoldDefns((elem IDX (offset IS) ES) DS, I, M) => (elem IDX (offset unfoldInstrs(IS)) ES) #unfoldDefns(DS, I, M) +``` + +#### Data Segments + +```k + rule #unfoldDefns(((data OFFSET:Offset DATA ) => ( data 0 OFFSET DATA )) _DS, _I, _M) + rule #unfoldDefns(((data IDX OFFSET:Instrs DATA ) => ( data IDX ( offset OFFSET ) DATA )) _DS, _I, _M) + + rule #unfoldDefns((data IDX (offset IS) DATA) DS, I, M) => (data IDX (offset unfoldInstrs(IS)) DATA) #unfoldDefns(DS, I, M) +``` + +#### Instructions + +```k + syntax Instrs ::= unfoldInstrs ( Instrs ) [function] + | #unfoldInstrs ( Instrs, Int, Map ) [function] + // --------------------------------------------------------------- + rule unfoldInstrs(IS) => #unfoldInstrs(IS, 0, .Map) + rule #unfoldInstrs(.Instrs, _, _) => .Instrs + rule #unfoldInstrs(I IS, DEPTH, M) => I #unfoldInstrs(IS, DEPTH, M) [owise] + + syntax Instrs ::= Instrs "appendInstrs" Instrs [function] + | #appendInstrs ( Instrs, Instrs ) [function] + | #reverseInstrs ( Instrs, Instrs ) [function] + // -------------------------------------------------------------- + rule IS appendInstrs IS' => #appendInstrs(#reverseInstrs(IS, .Instrs), IS') + + rule #appendInstrs(I IS => IS, IS' => I IS') + rule #appendInstrs(.Instrs , IS') => IS' + + rule #reverseInstrs(.Instrs, ACC) => ACC + rule #reverseInstrs(I IS => IS, ACC => I ACC) +``` + +##### Block Instructions + +In the text format, block instructions can have identifiers attached to them, and branch instructions can refer to these identifiers. +Branching with an identifier is the same as branching to the label with that identifier. +The correct label index is calculated by looking at whih depth the index occured and what depth execution is currently at. + +Conceptually, `br ID => br CURRENT_EXECUTION_DEPTH -Int IDENTIFIER_LABEL_DEPTH -Int 1`. + +```k + rule #unfoldInstrs(br ID:Identifier IS, DEPTH, M) => br DEPTH -Int {M [ ID ]}:>Int -Int 1 #unfoldInstrs(IS, DEPTH, M) + rule #unfoldInstrs(br_if ID:Identifier IS, DEPTH, M) => br_if DEPTH -Int {M [ ID ]}:>Int -Int 1 #unfoldInstrs(IS, DEPTH, M) + rule #unfoldInstrs(br_table ES:ElemSegment IS, DEPTH, M) => br_table elemSegment2Indices(ES, DEPTH, M) #unfoldInstrs(IS, DEPTH, M) + + syntax ElemSegment ::= elemSegment2Indices( ElemSegment, Int, Map ) [function] + // ------------------------------------------------------------------------------ + rule elemSegment2Indices(.ElemSegment , _DEPTH, _M) => .ElemSegment + rule elemSegment2Indices(ID:Identifier ES, DEPTH, M) => DEPTH -Int {M [ ID ]}:>Int -Int 1 elemSegment2Indices(ES, DEPTH, M) + rule elemSegment2Indices(E ES, DEPTH, M) => E elemSegment2Indices(ES, DEPTH, M) [owise] +``` + +There are several syntactic sugars for block instructions, some of which may have identifiers. +The same identifier can optionally be repeated at the end of the block instruction (to mark which block is ending) and on the branches in an `if`. +`if` blocks may omit the `else`-branch, as long as the type declaration is empty. + +```k + rule #unfoldInstrs( (block ID:Identifier TDS IS end _OID' => block TDS IS end) _IS', DEPTH, M => M [ ID <- DEPTH ]) + rule #unfoldInstrs(block TDS:TypeDecls IS end IS', DEPTH, M) => block TDS #unfoldInstrs(IS, DEPTH +Int 1, M) end #unfoldInstrs(IS', DEPTH, M) + + rule #unfoldInstrs( (loop ID:Identifier TDS IS end _OID' => loop TDS IS end) _IS', DEPTH, M => M [ ID <- DEPTH ]) + rule #unfoldInstrs(loop TDS:TypeDecls IS end IS', DEPTH, M) => loop TDS #unfoldInstrs(IS, DEPTH +Int 1, M) end #unfoldInstrs(IS', DEPTH, M) + + // TODO: Only unfold empty else-branch if the type declaration is empty. + rule #unfoldInstrs( (if ID:Identifier TDS IS end _OID'' => if ID TDS IS else .Instrs end) _IS'', _DEPTH, _M) + rule #unfoldInstrs( (if TDS IS end _OID'' => if TDS IS else .Instrs end) _IS'', _DEPTH, _M) + rule #unfoldInstrs( (if ID:Identifier TDS IS else _OID':OptionalId IS' end _OID'' => if TDS IS else IS' end) _IS'', DEPTH, M => M [ ID <- DEPTH ]) + rule #unfoldInstrs(if TDS IS else IS' end IS'', DEPTH, M) => if TDS #unfoldInstrs(IS, DEPTH +Int 1, M) else #unfoldInstrs(IS', DEPTH +Int 1, M) end #unfoldInstrs(IS'', DEPTH, M) +``` + +#### Folded Instructions + +```k + rule #unfoldInstrs(( PI:PlainInstr IS:Instrs ):FoldedInstr IS', DEPTH, M) + => (#unfoldInstrs(IS , DEPTH, M) + appendInstrs #unfoldInstrs(PI .Instrs, DEPTH, M)) + appendInstrs #unfoldInstrs(IS' , DEPTH, M) + rule #unfoldInstrs(( PI:PlainInstr ):FoldedInstr IS', DEPTH, M) + => #unfoldInstrs(PI .Instrs, DEPTH, M) + appendInstrs #unfoldInstrs(IS' , DEPTH, M) +``` + +Another type of folded instruction is control flow blocks wrapped in parentheses, in which case the `end` keyword is omitted. + +```k + rule #unfoldInstrs(((block ID:Identifier TDS IS) => block ID TDS IS end) _IS', _DEPTH, _M) + rule #unfoldInstrs(((block TDS IS) => block TDS IS end) _IS', _DEPTH, _M) + + rule #unfoldInstrs(((loop ID:Identifier TDS IS) => loop ID TDS IS end) _IS', _DEPTH, _M) + rule #unfoldInstrs(((loop TDS IS) => loop TDS IS end) _IS', _DEPTH, _M) + + rule #unfoldInstrs(((if OID:OptionalId TDS COND (then IS)) => (if OID TDS COND (then IS) (else .Instrs))) _IS'', _DEPTH, _M) + rule #unfoldInstrs(((if ID:Identifier TDS COND (then IS) (else IS')) IS'':Instrs) => (COND appendInstrs if ID TDS IS else IS' end IS''), _DEPTH, _M) + rule #unfoldInstrs(((if TDS COND (then IS) (else IS')) IS'':Instrs) => (COND appendInstrs if TDS IS else IS' end IS''), _DEPTH, _M) +``` + +#### Structuring Modules + +The text format allows definitions to appear in any order in a module. +In the abstract format, the module is a record, one for each type of definition. +The following functions convert the text format module, given as a list of definitions, into the abstract format. +In doing so, the respective ordering of all types of definitions are preserved. + +```k + syntax Stmts ::= structureModules ( Stmts ) [function] + // ------------------------------------------------------ + rule structureModules((module OID:OptionalId DS) SS) => structureModule(DS, OID) structureModules(SS) + rule structureModules(.Stmts) => .Stmts + rule structureModules(S SS) => S structureModules(SS) [owise] + + syntax ModuleDecl ::= structureModule ( Defns , OptionalId ) [function] + | #structureModule ( Defns , ModuleDecl ) [function] + // ------------------------------------------------------------------------ + rule structureModule(DEFNS, OID) => #structureModule(#reverseDefns(DEFNS, .Defns), #emptyModule(OID)) + + rule #structureModule(.Defns, SORTED_MODULE) => SORTED_MODULE + + rule #structureModule((T:TypeDefn DS:Defns => DS), #module(... types: TS => T TS)) + rule #structureModule((I:ImportDefn DS:Defns => DS), #module(... importDefns: IS => I IS)) + rule #structureModule((X:FuncDefn DS:Defns => DS), #module(... funcs: FS => X FS)) + rule #structureModule((X:GlobalDefn DS:Defns => DS), #module(... globals: GS => X GS)) + rule #structureModule((T:TableDefn DS:Defns => DS), #module(... tables: TS => T TS)) + rule #structureModule((M:MemoryDefn DS:Defns => DS), #module(... mems: MS => M MS)) + rule #structureModule((E:ExportDefn DS:Defns => DS), #module(... exports: ES => E ES)) + rule #structureModule((I:DataDefn DS:Defns => DS), #module(... data: IS => I IS)) + rule #structureModule((I:ElemDefn DS:Defns => DS), #module(... elem: IS => I IS)) + rule #structureModule((S:StartDefn DS:Defns => DS), #module(... start: .Defns => S .Defns)) + + syntax Defns ::= #reverseDefns(Defns, Defns) [function] + // ------------------------------------------------------- + rule #reverseDefns( .Defns , ACC) => ACC + rule #reverseDefns(D:Defn DS:Defns, ACC) => #reverseDefns(DS, D ACC) +``` + +### Replacing Identifiers and Unfolding Instructions + +The desugaring is done on the module level. +First, if the program is just a list of definitions, that's an abbreviation for a single module. +If not, we distribute the text to abstract transformation out over all the statements in the file. + +**TODO:** + +- Get rid of inline type declarations. + The text format allows specifying the type directly in the function header using the `param` and `result` keywords. + However, these will either be desugared to a new top-level `type` declaration or they must match an existing one. + In the abstract format, a function's type is a pointer to a top-level `type` declaration. + This could either be done by doing an initial pass to gather all type declarations, or they could be desugared locally, which is similar to what we do currently: `(func (type X) TDS:TDecls ... ) => (func (type X))` and `(func TDS:TDecls ...) => (type TDECLS) (func (type NEXT_TYPE_ID))`. +- Remove module names. +- Give the text format and abstract format different sorts, and have `text2abstract` handle the conversion. + Then identifiers and other text-only constructs can be completely removed from the abstract format. + + +#### The Context + +The `Context` contains information of how to map text-level identifiers to corresponding indices. +Record updates can currently not be done in a function rule which also does other updates, so we have helper functions to update specific fields. + +```k + syntax Context ::= ctx(localIds: Map, globalIds: Map, funcIds: Map, typeIds: Map) + | #freshCtx ( ) [function, total] + | #updateLocalIds ( Context , Map ) [function, total] + | #updateLocalIdsAux ( Context , Map , Bool ) [function, total] + | #updateFuncIds ( Context , Map ) [function, total] + | #updateFuncIdsAux ( Context , Map , Bool ) [function, total] + // ------------------------------------------------------------------------------------- + rule #freshCtx ( ) => ctx(... localIds: .Map, globalIds: .Map, funcIds: .Map, typeIds: .Map) + + rule #updateLocalIds(C, M) => #updateLocalIdsAux(C, M, false) + rule #updateLocalIdsAux(ctx(... localIds: (_ => M)), M, false => true) + rule #updateLocalIdsAux(C, _, true) => C + + rule #updateFuncIds(C, M) => #updateFuncIdsAux(C, M, false) + rule #updateFuncIdsAux(ctx(... funcIds: (_ => M)), M, false => true) + rule #updateFuncIdsAux(C, _, true) => C +``` + +#### Traversing the Full Program + +The program is traversed in full once, context being gathered along the way. +Since we do not have polymorphic functions available, we define one function per sort of syntactic construct we need to traverse, and for each type of list we encounter. + +```k + syntax Stmt ::= "#t2aStmt" "<" Context ">" "(" Stmt ")" [function] + syntax ModuleDecl ::= "#t2aModuleDecl" "<" Context ">" "(" ModuleDecl ")" [function] + syntax ModuleDecl ::= "#t2aModule" "<" Context ">" "(" ModuleDecl ")" [function] + syntax Defn ::= "#t2aDefn" "<" Context ">" "(" Defn ")" [function] + // ------------------------------------------------------------------------------------ + rule text2abstract(DS:Defns) => text2abstract(( module DS ) .Stmts) + rule text2abstract(SS) => #t2aStmts<#freshCtx()>(structureModules(unfoldStmts(SS))) [owise] + + rule #t2aStmt(M:ModuleDecl) => #t2aModuleDecl(M) + rule #t2aStmt(D:Defn) => #t2aDefn(D) + rule #t2aStmt(I:Instr) => #t2aInstr(I) + rule #t2aStmt<_>(S) => S [owise] + + rule #t2aModuleDecl<_>(#module(... types: TS, funcs: FS, globals: GS, importDefns: IS) #as M) => #t2aModule(M) + rule #t2aModule(#module(... types: TS, funcs: FS, tables: TABS, mems: MS, globals: GS, elem: EL, data: DAT, start: S, importDefns: IS, exports: ES, metadata: #meta(... id: OID))) + => #module( ... types: #t2aDefns(TS) + , funcs: #t2aDefns(FS) + , tables: #t2aDefns(TABS) + , mems: #t2aDefns(MS) + , globals: #t2aDefns(GS) + , elem: #t2aDefns(EL) + , data: #t2aDefns(DAT) + , start: #t2aDefns(S) + , importDefns: #t2aDefns(IS) + , exports: #t2aDefns(ES) + , metadata: #meta(... id: OID, funcIds: FIDS, filename: .String) + ) +``` + +#### Types + +```k + rule #t2aDefn<_>((type OID (func TDECLS))) => #type(... type: asFuncType(TDECLS), metadata: OID) +``` + +#### Imports + +```k + rule #t2aDefn(( import MOD NAME (func OID:OptionalId (type ID:Identifier) ))) => #import(MOD, NAME, #funcDesc(... id: OID:OptionalId, type: {TIDS[ID]}:>Int)) + rule #t2aDefn(( import MOD NAME (func OID:OptionalId (type ID:Identifier) _:TypeDecls))) => #import(MOD, NAME, #funcDesc(... id: OID:OptionalId, type: {TIDS[ID]}:>Int)) + rule #t2aDefn<_ >(( import MOD NAME (func OID:OptionalId (type IDX:Int) ))) => #import(MOD, NAME, #funcDesc(... id: OID:OptionalId, type: IDX)) + rule #t2aDefn<_ >(( import MOD NAME (func OID:OptionalId (type IDX:Int ) _:TypeDecls))) => #import(MOD, NAME, #funcDesc(... id: OID:OptionalId, type: IDX)) + + rule #t2aDefn<_ >(( import MOD NAME (global OID:OptionalId TYP:TextFormatGlobalType))) => #import(MOD, NAME, #globalDesc(... id: OID:OptionalId, type: asGMut(TYP))) + + rule #t2aDefn<_ >(( import MOD NAME (table OID:OptionalId LIM:TextLimits funcref))) => #import(MOD, NAME, #tableDesc(... id: OID:OptionalId, type: t2aLimits(LIM))) + rule #t2aDefn<_ >(( import MOD NAME (memory OID:OptionalId LIM:TextLimits ))) => #import(MOD, NAME, #memoryDesc(... id: OID:OptionalId, type: t2aLimits(LIM))) +``` + +#### Globals + +```k + rule #t2aDefn(#global(... type: GTYP, init: IS, metadata: OID)) => #global(... type: GTYP, init: #t2aInstrs(IS), metadata: OID) +``` + +#### Functions + +After unfolding, each type use in a function starts with an explicit reference to a module-level function. + +```k + rule #t2aDefn(( func OID:OptionalId T:TypeUse LS:LocalDecls IS:Instrs )) + => #func(... type: typeUse2typeIdx(T, TIDS) + , locals: locals2vectype(LS) + , body: #t2aInstrs <#updateLocalIds(C, #ids2Idxs(T, LS))>(IS) + , metadata: #meta(... id: OID, localIds: #ids2Idxs(T, LS)) + ) + + syntax Int ::= typeUse2typeIdx ( TypeUse , Map ) [function] + // ----------------------------------------------------------- + rule typeUse2typeIdx( (type IDX ) _:TypeDecls => (type IDX), _TIDS ) + + rule typeUse2typeIdx( (type ID:Identifier ) , TIDS ) => {TIDS [ ID ]}:>Int + rule typeUse2typeIdx( (type IDX:Int ) , _TIDS ) => IDX + + syntax VecType ::= locals2vectype ( LocalDecls ) [function] + | #locals2vectype ( LocalDecls , ValTypes ) [function] + // ----------------------------------------------------------------------- + rule locals2vectype(LDECLS) => #locals2vectype(LDECLS, .ValTypes) + + rule #locals2vectype(.LocalDecls , VTYPES) => [ VTYPES ] + rule #locals2vectype(local VTYPES':ValTypes LDECLS:LocalDecls , VTYPES) => #locals2vectype(LDECLS , VTYPES + VTYPES') + rule #locals2vectype(local _ID:Identifier VTYPE:ValType LDECLS:LocalDecls , VTYPES) => #locals2vectype(LDECLS , VTYPES + VTYPE .ValTypes) +``` + +#### Tables + +```k + rule #t2aDefn<_>((table OID:OptionalId LIMITS:TextLimits funcref )) => #table(... limits: t2aLimits(LIMITS), metadata: OID) +``` + +#### Memories + +```k + rule #t2aDefn<_>((memory OID:OptionalId LIMITS:TextLimits )) => #memory(... limits: t2aLimits(LIMITS), metadata: OID) +``` + +```k + syntax Limits ::= t2aLimits(TextLimits) [function, total] + // -------------------------------------------------------------- + rule t2aLimits(MIN:Int) => #limitsMin(MIN) + rule t2aLimits(MIN:Int MAX:Int) => #limits(MIN, MAX) +``` + +#### Start Function + +```k + rule #t2aDefn(( start ID:Identifier )) => #start({FIDS[ID]}:>Int) + requires ID in_keys(FIDS) + rule #t2aDefn<_>(( start I:Int )) => #start(I) +``` + +#### Element Segments + +Wasm currently supports only one table, so we do not need to resolve any identifiers. + +```k + rule #t2aDefn(( elem _:Index (offset IS) ES )) => #elem(0, #t2aInstrs(IS), #t2aElemSegment(ES) ) + + syntax Ints ::= "#t2aElemSegment" "<" Context ">" "(" ElemSegment ")" [function] + // -------------------------------------------------------------------------------- + rule #t2aElemSegment(ID:Identifier ES) => {FIDS[ID]}:>Int #t2aElemSegment(ES) + requires ID in_keys(FIDS) + rule #t2aElemSegment(I:Int ES) => I #t2aElemSegment(ES) + rule #t2aElemSegment<_C>(.ElemSegment) => .Ints +``` + +#### Data Segments + +Wasm currently supports only one memory, so we do not need to resolve any identifiers. + +```k + rule #t2aDefn(( data _:Index (offset IS) DS )) => #data(0, #t2aInstrs(IS), #DS2Bytes(DS)) +``` + +#### Exports + +```k + rule #t2aDefn(( export ENAME ( func ID:Identifier ) )) => #export(ENAME, {IDS[ID]}:>Int) requires ID in_keys(IDS) + rule #t2aDefn(( export ENAME ( global ID:Identifier ) )) => #export(ENAME, {IDS[ID]}:>Int) requires ID in_keys(IDS) + rule #t2aDefn<_>(( export ENAME ( func I:Int ) )) => #export(ENAME, I) + rule #t2aDefn<_>(( export ENAME ( global I:Int ) )) => #export(ENAME, I) + + rule #t2aDefn<_>(( export ENAME ( table _ ) )) => #export(ENAME, 0) + rule #t2aDefn<_>(( export ENAME ( memory _ ) )) => #export(ENAME, 0) +``` + +#### Other Definitions + +```k + rule #t2aDefn<_C>(D:Defn) => D [owise] +``` + +#### Instructions + +```k + syntax Instr ::= "#t2aInstr" "<" Context ">" "(" Instr ")" [function] + // --------------------------------------------------------------------- + rule #t2aInstr(( PI:PlainInstr IS:Instrs ):FoldedInstr) => ({#t2aInstr(PI)}:>PlainInstr #t2aInstrs(IS)) + rule #t2aInstr(( PI:PlainInstr ):FoldedInstr) => #t2aInstr(PI) +``` + +#### Basic Instructions + +```k + rule #t2aInstr<_>(unreachable) => unreachable + rule #t2aInstr<_>(nop) => nop + rule #t2aInstr<_>(br L:Int) => #br(L) + rule #t2aInstr<_>(br_if L:Int) => #br_if(L) + rule #t2aInstr<_>(br_table ES) => #br_table(elemSegment2Ints(ES)) + rule #t2aInstr<_>(return) => return + + rule #t2aInstr(call ID:Identifier) => #call({FIDS[ID]}:>Int) + requires ID in_keys(FIDS) + rule #t2aInstr<_> (call I:Int) => #call(I) + + rule #t2aInstr<_>(call_indirect TU) => call_indirect TU +``` + +#### Parametric Instructions + +```k + rule #t2aInstr<_>(drop) => drop + rule #t2aInstr<_>(select) => select +``` + +#### Variable Instructions + +```k + rule #t2aInstr(local.get ID:Identifier) => #local.get({LIDS[ID]}:>Int) + requires ID in_keys(LIDS) + rule #t2aInstr(local.set ID:Identifier) => #local.set({LIDS[ID]}:>Int) + requires ID in_keys(LIDS) + rule #t2aInstr(local.tee ID:Identifier) => #local.tee({LIDS[ID]}:>Int) + requires ID in_keys(LIDS) + + rule #t2aInstr<_>(local.get I:Int) => #local.get(I) + rule #t2aInstr<_>(local.set I:Int) => #local.set(I) + rule #t2aInstr<_>(local.tee I:Int) => #local.tee(I) + + rule #t2aInstr(global.get ID:Identifier) => #global.get({GIDS[ID]}:>Int) + requires ID in_keys(GIDS) + rule #t2aInstr(global.set ID:Identifier) => #global.set({GIDS[ID]}:>Int) + requires ID in_keys(GIDS) + + rule #t2aInstr<_>(global.get I:Int) => #global.get(I) + rule #t2aInstr<_>(global.set I:Int) => #global.set(I) +``` + +#### Memory Instructions + +`MemArg`s can optionally be passed to `load` and `store` operations. +The `offset` parameter is added to the the address given on the stack, resulting in the "effective address" to store to or load from. +The `align` parameter is for optimization only and is not allowed to influence the semantics, so we ignore it. + +```k + rule #t2aInstr<_>(ITYPE:IValType.OP:StoreOp) => #store(ITYPE, OP, 0) + rule #t2aInstr<_>(ITYPE:IValType.OP:StoreOp MemArg) => #store(ITYPE, OP, #getOffset(MemArg)) + rule #t2aInstr<_>(FTYPE:FValType.OP:StoreOp) => #store(FTYPE, OP, 0) + rule #t2aInstr<_>(FTYPE:FValType.OP:StoreOp MemArg) => #store(FTYPE, OP, #getOffset(MemArg)) + rule #t2aInstr<_>(ITYPE:IValType.OP:LoadOp) => #load(ITYPE, OP, 0) + rule #t2aInstr<_>(ITYPE:IValType.OP:LoadOp MemArg) => #load(ITYPE, OP, #getOffset(MemArg)) + rule #t2aInstr<_>(FTYPE:FValType.OP:LoadOp) => #load(FTYPE, OP, 0) + rule #t2aInstr<_>(FTYPE:FValType.OP:LoadOp MemArg) => #load(FTYPE, OP, #getOffset(MemArg)) + rule #t2aInstr<_>(memory.size) => memory.size + rule #t2aInstr<_>(memory.grow) => memory.grow + + syntax Int ::= #getOffset ( MemArg ) [function, total] + // ----------------------------------------------------------- + rule #getOffset( _:AlignArg) => 0 + rule #getOffset(offset= OS ) => OS + rule #getOffset(offset= OS _:AlignArg) => OS +``` + +#### Numeric Instructions + +```k + rule #t2aInstr<_>(ITYPE:IValType.const I) => ITYPE.const I + rule #t2aInstr<_>(FTYPE:FValType.const N) => FTYPE.const N + rule #t2aInstr<_>(ITYPE.OP:IUnOp) => ITYPE.OP + rule #t2aInstr<_>(FTYPE.OP:FUnOp) => FTYPE.OP + rule #t2aInstr<_>(ITYPE.OP:IBinOp) => ITYPE.OP + rule #t2aInstr<_>(FTYPE.OP:FBinOp) => FTYPE.OP + rule #t2aInstr<_>(ITYPE.OP:TestOp) => ITYPE.OP + rule #t2aInstr<_>(ITYPE.OP:IRelOp) => ITYPE.OP + rule #t2aInstr<_>(FTYPE.OP:FRelOp) => FTYPE.OP + rule #t2aInstr<_>(ATYPE.OP:CvtOp) => ATYPE.OP +``` + +#### Block Instructions + +There are several formats of block instructions, and the text-to-abstract transformation must be distributed over them. +At this point, all branching identifiers should have been resolved, so we can remove the id. + +```k + rule #t2aInstr( block _OID:OptionalId TDS:TypeDecls IS end _OID') => #block(gatherTypes(result, TDS), #t2aInstrs(IS), .Int) + rule #t2aInstr( loop _OID:OptionalId TDS IS end _OID') => #loop(gatherTypes(result, TDS), #t2aInstrs(IS), .Int) + rule #t2aInstr( if _OID:OptionalId TDS IS else _OID':OptionalId IS' end _OID'') => #if(gatherTypes(result, TDS), #t2aInstrs(IS), #t2aInstrs(IS'), .Int) +``` + +#### KWasm Administrative Instructions + +The following instructions are not part of the official Wasm text format. +They are currently supported in KWasm text files, but may be deprecated. + +```k + rule #t2aInstr<_C>(trap) => trap + + rule #t2aInstr(#block(VT:VecType, IS:Instrs, BLOCKINFO)) => #block(VT, #t2aInstrs(IS), BLOCKINFO) + + rule #t2aInstr<_>(init_local I V) => init_local I V + rule #t2aInstr<_>(init_locals VS) => init_locals VS +``` + +#### List Functions + +The following are helper functions. +They distribute the text-to-abstract functions above over lists. + +```k + syntax Stmts ::= "#t2aStmts" "<" Context ">" "(" Stmts ")" [function] + syntax Defns ::= "#t2aDefns" "<" Context ">" "(" Defns ")" [function] + syntax Instrs ::= "#t2aInstrs" "<" Context ">" "(" Instrs ")" [function] + // ------------------------------------------------------------------------------------ + rule #t2aStmts(S:Stmt SS:Stmts) => #t2aStmt(S) #t2aStmts(SS) + rule #t2aStmts<_>(.Stmts) => .Stmts + + rule #t2aDefns(D:Defn DS:Defns) => #t2aDefn(D) #t2aDefns(DS) + rule #t2aDefns<_>(.Defns) => .Defns + + rule #t2aInstrs(I:Instr IS:Instrs) => #t2aInstr(I) #t2aInstrs(IS) + rule #t2aInstrs<_>(.Instrs) => .Instrs +``` + +#### Functions for Gathering Context + +The following are helper functions for gathering and updating context. + +```k + syntax Map ::= #idcTypes ( Defns ) [function] + | #idcTypesAux ( Defns, Int, Map ) [function] + // ---------------------------------------------------------- + rule #idcTypes(DEFNS) => #idcTypesAux(DEFNS, 0, .Map) + + rule #idcTypesAux((type ID:Identifier (func _)) TS => TS, IDX => IDX +Int 1, ACC => ACC [ ID <- IDX ]) requires notBool ID in_keys(ACC) + rule #idcTypesAux((type (func _)) TS => TS, IDX => IDX +Int 1, _ACC) + rule #idcTypesAux(.Defns, _, ACC) => ACC + + syntax Map ::= #idcFuncs ( Defns, Defns ) [function] + | #idcFuncsAux ( Defns, Defns, Int, Map ) [function] + // ----------------------------------------------------------------- + rule #idcFuncs(IMPORTS, DEFNS) => #idcFuncsAux(IMPORTS, DEFNS, 0, .Map) + + rule #idcFuncsAux((import _ _ (func ID:Identifier _)) IS => IS, _FS, IDX => IDX +Int 1, ACC => ACC [ ID <-IDX ]) requires notBool ID in_keys(ACC) + rule #idcFuncsAux((import _ _ (func _)) IS => IS, _FS, IDX => IDX +Int 1, _ACC) + rule #idcFuncsAux(_I IS => IS, _FS, _IDX , _ACC) [owise] + + rule #idcFuncsAux(.Defns, (func ID:Identifier _) FS => FS, IDX => IDX +Int 1, ACC => ACC [ ID <- IDX ]) requires notBool ID in_keys(ACC) + rule #idcFuncsAux(.Defns, (func _:FuncSpec) FS => FS, IDX => IDX +Int 1, _ACC) + rule #idcFuncsAux(.Defns, .Defns, _, ACC) => ACC + + syntax Map ::= #idcGlobals ( Defns, Defns ) [function] + | #idcGlobalsAux ( Defns, Defns, Int, Map ) [function] + // ------------------------------------------------------------------- + rule #idcGlobals(IMPORTS, DEFNS) => #idcGlobalsAux(IMPORTS, DEFNS, 0, .Map) + + rule #idcGlobalsAux((import _ _ (global ID:Identifier _)) IS => IS, _GS, IDX => IDX +Int 1, ACC => ACC [ ID <-IDX ]) requires notBool ID in_keys(ACC) + rule #idcGlobalsAux((import _ _ (global _)) IS => IS, _GS, IDX => IDX +Int 1, _ACC) + rule #idcGlobalsAux(_I IS => IS, _GS, _IDX , _ACC) [owise] + + rule #idcGlobalsAux(.Defns, #global(... metadata: ID:Identifier) GS => GS, IDX => IDX +Int 1, ACC => ACC [ ID <- IDX ]) requires notBool ID in_keys(ACC) + rule #idcGlobalsAux(.Defns, #global(...) GS => GS, IDX => IDX +Int 1, _ACC) [owise] + rule #idcGlobalsAux(.Defns, .Defns, _, ACC) => ACC + + syntax Map ::= #ids2Idxs(TypeUse, LocalDecls) [function, total] + | #ids2Idxs(Int, TypeUse, LocalDecls) [function, total] + // ------------------------------------------------------------------------- + rule #ids2Idxs(TU, LDS) => #ids2Idxs(0, TU, LDS) + + rule #ids2Idxs(_, .TypeDecls, .LocalDecls) => .Map + rule #ids2Idxs(N, (type _) , LDS) => #ids2Idxs(N, .TypeDecls, LDS) + rule #ids2Idxs(N, (type _) TDS, LDS) => #ids2Idxs(N, TDS , LDS) + + rule #ids2Idxs(N, (param ID:Identifier _) TDS, LDS) + => (ID |-> N) #ids2Idxs(N +Int 1, TDS, LDS) + rule #ids2Idxs(N, (param _) TDS, LDS) => #ids2Idxs(N +Int 1, TDS, LDS) + rule #ids2Idxs(N, _TD:TypeDecl TDS, LDS) => #ids2Idxs(N , TDS, LDS) [owise] + + rule #ids2Idxs(N, .TypeDecls, local ID:Identifier _ LDS:LocalDecls) + => (ID |-> N) #ids2Idxs(N +Int 1, .TypeDecls, LDS) + rule #ids2Idxs(N, .TypeDecls, _LD:LocalDecl LDS) => #ids2Idxs(N +Int 1, .TypeDecls, LDS) [owise] +``` + +```k +endmodule +``` diff --git a/docs/runtimeverification-wasm-semantics/wasm.md b/docs/runtimeverification-wasm-semantics/wasm.md new file mode 100644 index 000000000..914c06d68 --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/wasm.md @@ -0,0 +1,1430 @@ +WebAssembly State and Semantics +=============================== + +```k +require "data.md" +require "numeric.md" + +module WASM-SYNTAX + imports WASM-DATA-SYNTAX + imports WASM-COMMON-SYNTAX + imports WASM-NUMERIC-SYNTAX +endmodule +``` + +Common Syntax +------------- + +```k +module WASM-COMMON-SYNTAX + imports WASM-DATA-COMMON-SYNTAX + imports WASM-NUMERIC +``` + +### Text Format + +WebAssmebly code consists of instruction sequences. +The basic abstract syntax contains only the `instr` syntax production. +The text format also specifies the `plaininstr`, which corresponds almost exactly to the the `instr` production. + +Most instructions are plain instructions. + +```k + syntax Instr ::= PlainInstr + // --------------------------- +``` + +### Sequencing + +WebAssembly code consists of sequences of statements (`Stmts`). +In this file we define 3 types of statements: + +- Instruction (`Instr`): Administrative or computational instructions. +- Definitions (`Defn`) : The declarations of `type`, `func`, `table`, `mem` etc. +- The Declaration of a module. + +The sorts `EmptyStmt` and `EmptyStmts` are administrative so that the empty list of `Stmt`, `Instr`, or `Defn` has a unique least sort. + +```k + syntax EmptyStmt + // ---------------- + + syntax Instr ::= EmptyStmt + syntax Defn ::= EmptyStmt + syntax Stmt ::= Instr | Defn + // ----------------------------- + + syntax EmptyStmts ::= List{EmptyStmt , ""} [klabel(listStmt), symbol] + syntax Instrs ::= List{Instr , ""} [klabel(listStmt)] + syntax Defns ::= List{Defn , ""} [klabel(listStmt)] + syntax Stmts ::= List{Stmt , ""} [klabel(listStmt)] + // ------------------------------------------------------------- + + syntax Instrs ::= EmptyStmts + syntax Defns ::= EmptyStmts + syntax Stmts ::= Instrs | Defns + // -------------------------------- +``` + +### Instructions + +**TODO**: Implement `Float` in the format of `-nan`, `nan:0x n:hexnum` and `hexfloat`. + +```k + syntax PlainInstr ::= IValType "." "const" WasmInt [klabel(aIConst), symbol] + | FValType "." "const" Number [klabel(aFConst), symbol] + | IValType "." IUnOp [klabel(aIUnOp), symbol] + | FValType "." FUnOp [klabel(aFUnOp), symbol] + | IValType "." IBinOp [klabel(aIBinOp), symbol] + | FValType "." FBinOp [klabel(aFBinOp), symbol] + | IValType "." TestOp [klabel(aTestOp), symbol] + | IValType "." IRelOp [klabel(aIRelOp), symbol] + | FValType "." FRelOp [klabel(aFRelOp), symbol] + | ValType "." CvtOp [klabel(aCvtOp), symbol] + | "drop" [klabel(aDrop), symbol] + | "select" [klabel(aSelect), symbol] + | "nop" [klabel(aNop), symbol] + | "unreachable" [klabel(aUnreachable), symbol] + | "return" [klabel(aReturn), symbol] + | "memory.size" [klabel(aSize), symbol] + | "memory.grow" [klabel(aGrow), symbol] + // ----------------------------------- + + syntax PlainInstr ::= "call_indirect" TypeUse + syntax TypeUse ::= TypeDecls + | "(type" Index ")" [prefer] // TODO: Remove and move to wasm-text. + | "(type" Index ")" TypeDecls + syntax TypeKeyWord ::= "param" | "result" + syntax TypeDecl ::= "(" TypeDecl ")" [bracket] + | TypeKeyWord ValTypes + | "param" Identifier ValType + syntax TypeDecls ::= List{TypeDecl , ""} [klabel(listTypeDecl)] + // ----------------------------------------------------------------- + + syntax StoreOp ::= "store" [klabel(storeOpStore), symbol] + | "store8" [klabel(storeOpStore8), symbol] + | "store16" [klabel(storeOpStore16), symbol] + | "store32" [klabel(storeOpStore32), symbol] + syntax LoadOp ::= "load" [klabel(loadOpLoad), symbol] + | "load8_u" [klabel(loadOpLoad8_u), symbol] + | "load16_u" [klabel(loadOpLoad16_u), symbol] + | "load32_u" [klabel(loadOpLoad32_u), symbol] + | "load8_s" [klabel(loadOpLoad8_s), symbol] + | "load16_s" [klabel(loadOpLoad16_s), symbol] + | "load32_s" [klabel(loadOpLoad32_s), symbol] + // -------------------------------------------------------------- +``` + +### Definitions at the Module Level + +```k + syntax Defn ::= TypeDefn + | GlobalDefn + | FuncDefn + | TableDefn + | MemoryDefn + | ElemDefn + | DataDefn + | StartDefn + | ExportDefn + | ImportDefn + // -------------------------- +``` + +The following are kept abstract, and can be extended in other formats, such as the text format. + +```k + syntax TypeDefn + syntax GlobalDefn + syntax FuncDefn + syntax TableDefn + syntax MemoryDefn + syntax ElemDefn + syntax DataDefn + syntax StartDefn + syntax ImportDefn + syntax ExportDefn + // ----------------- +``` + +```k +endmodule +``` + +Semantics +--------- + +```k +module WASM + imports WASM-COMMON-SYNTAX + imports WASM-DATA + imports WASM-NUMERIC +``` + +### Configuration + +```k + configuration + + .K + .ValStack + + .Map + .Int + + .Map + .Map + + + 0 + .Map + .Map + 0 + .Map + 0 + .Map + .Map + .Map + .Map + .Map + .Map + 0 + + .String + + .Map + .Map + + + + 0 + + + + 0 + .Instrs:Instrs + .Type + .Type + 0 + + + .Map + + + + 0 + + + 0 + .Int + 0 + .Map + + + 0 + + + 0 + .Int + 0 + .Bytes + + + 0 + + + 0 + undefined + .Mut + + + 0 + + true + +``` + +### Assumptions and invariants + +Integers in K are unbounded. +As an invariant, however, for any integer `< iNN > I:Int` on the stack, `I` is between 0 and `#pow(NN) - 1`. +That way, unsigned instructions can make use of `I` directly, whereas signed instructions may need `#signed(iNN, I)`. + +The highest address in a memory instance divided by the `#pageSize()` constant (defined below) may not exceed the value in the `` cell, if present. + +Since memory data is bytes, all integers in the `Map` in the `` cell are bounded to be between 1 and 255, inclusive. +All places in the data with no entry are considered zero bytes. + +### Translations to Abstract Syntax + +Before execution, the program is translated from the text-format concrete syntax tree into an abstract syntax tree using the following function. +It's full definition is found in the `wasm-text.md` file. + +```k + syntax Stmts ::= text2abstract ( Stmts ) [function] + // --------------------------------------------------- +``` + +Instructions +------------ + +### Sequencing + +```k + syntax K ::= sequenceStmts ( Stmts ) [function] + | sequenceDefns ( Defns ) [function] + | sequenceInstrs ( Instrs ) [function] + // ------------------------------------------------- + rule sequenceStmts(.Stmts) => . + rule sequenceStmts(S SS ) => S ~> sequenceStmts(SS) + + rule sequenceDefns(.Defns) => . + rule sequenceDefns(D DS ) => D ~> sequenceDefns(DS) + + rule sequenceInstrs(.Instrs) => . + rule sequenceInstrs(I IS ) => I ~> sequenceInstrs(IS) +``` + +### Traps + +`trap` is the error mechanism of Wasm. +Traps cause all execution to halt, and can not be caught from within Wasm. +We emulate this by consuming everything in the `` cell that is not a `Stmt`. +Statements are not part of Wasm semantics, but rather of the embedder, and is where traps can be caught. +Thus, a `trap` "bubbles up" (more correctly, to "consumes the continuation") until it reaches a statement which is not an `Instr` or `Def`. + +```k + syntax Instr ::= "trap" + // ----------------------- + rule trap ~> (_L:Label => .) ... + rule trap ~> (_F:Frame => .) ... + rule trap ~> (_I:Instr => .) ... + rule trap ~> (_D:Defn => .) ... +``` + +When a single value ends up on the instruction stack (the `` cell), it is moved over to the value stack (the `` cell). +If the value is the special `undefined`, then `trap` is generated instead. + +```k + rule undefined => trap ... + rule V:Val => . ... + VALSTACK => V : VALSTACK + requires V =/=K undefined +``` + +Common Operator Machinery +------------------------- + +Common machinery for operators is supplied here, based on their categorization. +This allows us to give purely functional semantics to many of the opcodes. + +### Constants + +Constants are moved directly to the value stack. +Function `#unsigned` is called on integers to allow programs to use negative numbers directly. + +```k + rule ITYPE:IValType . const VAL => #chop (< ITYPE > VAL) ... + rule FTYPE:FValType . const VAL => #round( FTYPE , VAL) ... +``` + +### Unary Operations + +When a unary operator is the next instruction, the single argument is loaded from the `` automatically. +An `*UnOp` operator always produces a result of the same type as its operand. + +```k + rule ITYPE . UOP:IUnOp => ITYPE . UOP C1 ... + < ITYPE > C1 : VALSTACK => VALSTACK + rule FTYPE . UOP:FUnOp => FTYPE . UOP C1 ... + < FTYPE > C1 : VALSTACK => VALSTACK +``` + +### Binary Operations + +When a binary operator is the next instruction, the two arguments are loaded from the `` automatically. + +```k + rule ITYPE . BOP:IBinOp => ITYPE . BOP C1 C2 ... + < ITYPE > C2 : < ITYPE > C1 : VALSTACK => VALSTACK + rule FTYPE . BOP:FBinOp => FTYPE . BOP C1 C2 ... + < FTYPE > C2 : < FTYPE > C1 : VALSTACK => VALSTACK +``` + +### Test Operations + +When a test operator is the next instruction, the single argument is loaded from the `` automatically. + +```k + rule TYPE . TOP:TestOp => TYPE . TOP C1 ... + < TYPE > C1 : VALSTACK => VALSTACK +``` + +### Relationship Operations + +When a relationship operator is the next instruction, the two arguments are loaded from the `` automatically. + +```k + rule ITYPE . ROP:IRelOp => ITYPE . ROP C1 C2 ... + < ITYPE > C2 : < ITYPE > C1 : VALSTACK => VALSTACK + rule FTYPE . ROP:FRelOp => FTYPE . ROP C1 C2 ... + < FTYPE > C2 : < FTYPE > C1 : VALSTACK => VALSTACK +``` + +### Conversion Operations + +Conversion Operation convert constant elements at the top of the stack to another type. + +```k + rule TYPE:ValType . CVTOP:Cvti32Op => TYPE . CVTOP C1 ... + < i32 > C1 : VALSTACK => VALSTACK + + rule TYPE:ValType . CVTOP:Cvti64Op => TYPE . CVTOP C1 ... + < i64 > C1 : VALSTACK => VALSTACK + + rule TYPE:ValType . CVTOP:Cvtf32Op => TYPE . CVTOP C1 ... + < f32 > C1 : VALSTACK => VALSTACK + + rule TYPE:ValType . CVTOP:Cvtf64Op => TYPE . CVTOP C1 ... + < f64 > C1 : VALSTACK => VALSTACK +``` + +ValStack Operations +------------------- + +Operator `drop` removes a single item from the ``. +The `select` operator picks one of the second or third stack values based on the first. + +```k + rule drop => . ... + _ : VALSTACK => VALSTACK + + rule select => . ... + + < i32 > C : < TYPE > V2:Number : < TYPE > V1:Number : VALSTACK + => < TYPE > #if C =/=Int 0 #then V1 #else V2 #fi : VALSTACK + +``` + +Structured Control Flow +----------------------- + +`nop` does nothing. + +```k + rule nop => . ... +``` + +`unreachable` causes an immediate `trap`. + +```k + rule unreachable => trap ... +``` + +Labels are administrative instructions used to mark the targets of break instructions. +They contain the continuation to use following the label, as well as the original stack to restore. +The supplied type represents the values that should taken from the current stack. + +A block is the simplest way to create targets for break instructions (ie. jump destinations). +It simply executes the block then records a label with an empty continuation. + +```k + syntax Label ::= "label" VecType "{" Instrs "}" ValStack + // -------------------------------------------------------- + rule label [ TYPES ] { _ } VALSTACK' => . ... + VALSTACK => #take(lengthValTypes(TYPES), VALSTACK) ++ VALSTACK' + + syntax BlockMetaData ::= OptionalInt + // ------------------------------------ + + syntax Instr ::= #block(VecType, Instrs, BlockMetaData) [klabel(aBlock), symbol] + // -------------------------------------------------------------------------------- + rule #block(VECTYP, IS, _) => sequenceInstrs(IS) ~> label VECTYP { .Instrs } VALSTACK ... + VALSTACK => .ValStack +``` + +The `br*` instructions search through the instruction stack (the `` cell) for the correct label index. +Upon reaching it, the label itself is executed. + +Note that, unlike in the WebAssembly specification document, we do not need the special "context" operator here because the value and instruction stacks are separate. + +```k + syntax Instr ::= #br( Int ) [klabel(aBr), symbol] + // ------------------------------------------------- + rule #br(_IDX) ~> (_S:Stmt => .) ... + rule #br(0 ) ~> label [ TYPES ] { IS } VALSTACK' => sequenceInstrs(IS) ... + VALSTACK => #take(lengthValTypes(TYPES), VALSTACK) ++ VALSTACK' + rule #br(N:Int) ~> _L:Label => #br(N -Int 1) ... + requires N >Int 0 + + syntax Instr ::= "#br_if" "(" Int ")" [klabel(aBr_if), symbol] + // -------------------------------------------------------------- + rule #br_if(IDX) => #br(IDX) ... + < _TYPE > VAL : VALSTACK => VALSTACK + requires VAL =/=Int 0 + rule #br_if(_IDX) => . ... + < _TYPE > VAL : VALSTACK => VALSTACK + requires VAL ==Int 0 + + syntax Instr ::= "#br_table" "(" Ints ")" [klabel(aBr_table), symbol] + // --------------------------------------------------------------------- + rule #br_table(ES) => #br(#getInts(ES, minInt(VAL, #lenInts(ES) -Int 1))) ... + < _TYPE > VAL : VALSTACK => VALSTACK +``` + +Finally, we have the conditional and loop instructions. + +```k + syntax Instr ::= #if( VecType, then : Instrs, else : Instrs, blockInfo: BlockMetaData) [klabel(aIf), symbol] + // ------------------------------------------------------------------------------------------------------------ + rule #if(VECTYP, IS, _, _) => sequenceInstrs(IS) ~> label VECTYP { .Instrs } VALSTACK ... + < i32 > VAL : VALSTACK => VALSTACK + requires VAL =/=Int 0 + + rule #if(VECTYP, _, IS, _) => sequenceInstrs(IS) ~> label VECTYP { .Instrs } VALSTACK ... + < i32 > VAL : VALSTACK => VALSTACK + requires VAL ==Int 0 + + syntax Instr ::= #loop(VecType, Instrs, BlockMetaData) [klabel(aLoop), symbol] + // ------------------------------------------------------------------------------ + rule #loop(VECTYP, IS, BLOCKMETA) => sequenceInstrs(IS) ~> label VECTYP { #loop(VECTYP, IS, BLOCKMETA) } VALSTACK ... + VALSTACK => .ValStack +``` + +Variable Operators +------------------ + +### Locals + +The various `init_local` variants assist in setting up the `locals` cell. + +```k + syntax Instr ::= "init_local" Int Val + | "init_locals" ValStack + | "#init_locals" Int ValStack + // -------------------------------------------- + rule init_local INDEX VALUE => . ... + LOCALS => LOCALS [ INDEX <- VALUE ] + + rule init_locals VALUES => #init_locals 0 VALUES ... + + rule #init_locals _ .ValStack => . ... + rule #init_locals N (VALUE : VALSTACK) + => init_local N VALUE + ~> #init_locals (N +Int 1) VALSTACK + ... + +``` + +The `*_local` instructions are defined here. + +```k + syntax Instr ::= "#local.get" "(" Int ")" [klabel(aLocal.get), symbol] + | "#local.set" "(" Int ")" [klabel(aLocal.set), symbol] + | "#local.tee" "(" Int ")" [klabel(aLocal.tee), symbol] + // ---------------------------------------------------------------------- + rule #local.get(I) => . ... + VALSTACK => VALUE : VALSTACK + ... I |-> VALUE ... + + rule #local.set(I) => . ... + VALUE : VALSTACK => VALSTACK + ... I |-> (_ => VALUE) ... + + rule #local.tee(I) => . ... + VALUE : _VALSTACK + ... I |-> (_ => VALUE) ... +``` + +### Globals + +When globals are declared, they must also be given a constant initialization value. +The `GlobalSpec` production is used to define all ways that a global can specified. +Globals can either be specified by giving a type and an initializer expression; or by an import and it's expected type. +The specification can also include export directives. +The importing and exporting parts of specifications are dealt with in the respective sections for import and export. + +```k + syntax GlobalType ::= Mut ValType [klabel(aGlobalType), symbol] + // --------------------------------------------------------------- + + syntax GlobalDefn ::= #global(type: GlobalType, init: Instrs, metadata: OptionalId) [klabel(aGlobalDefn), symbol] + syntax Alloc ::= allocglobal (OptionalId, GlobalType) + // ---------------------------------------------------------- + rule #global(... type: TYP, init: IS, metadata: OID) => sequenceInstrs(IS) ~> allocglobal(OID, TYP) ... + + rule allocglobal(OID:OptionalId, MUT:Mut TYP:ValType) => . ... + < TYP > VAL : STACK => STACK + CUR + + CUR + IDS => #saveId(IDS, OID, NEXTIDX) + NEXTIDX => NEXTIDX +Int 1 + GLOBS => GLOBS [ NEXTIDX <- NEXTADDR ] + ... + + NEXTADDR => NEXTADDR +Int 1 + + ( .Bag + => + NEXTADDR + VAL + MUT + + ) + ... + +``` + +The `get` and `set` instructions read and write globals. + +```k + syntax Instr ::= "#global.get" "(" Int ")" [klabel(aGlobal.get), symbol] + | "#global.set" "(" Int ")" [klabel(aGlobal.set), symbol] + // ------------------------------------------------------------------------ + rule #global.get(IDX) => . ... + VALSTACK => VALUE : VALSTACK + CUR + + CUR + ... IDX |-> GADDR ... + ... + + + GADDR + VALUE + ... + + + rule #global.set(IDX) => . ... + VALUE : VALSTACK => VALSTACK + CUR + + CUR + ... IDX |-> GADDR ... + ... + + + GADDR + _ => VALUE + ... + +``` + +Types +----- + +### Type Gathering + +This defines helper functions that gathers function together. +The function `gatherTypes` keeps the `TypeDecl`s that have the same `TypeKeyWord` as we need and throws away the `TypeDecl` having different `TypeKeyWord`. + +```k + syntax VecType ::= gatherTypes ( TypeKeyWord , TypeDecls ) [function] + | #gatherTypes ( TypeKeyWord , TypeDecls , ValTypes ) [function] + // --------------------------------------------------------------------------------- + rule gatherTypes(TKW , TDECLS:TypeDecls) => #gatherTypes(TKW, TDECLS, .ValTypes) + + rule #gatherTypes( _ , .TypeDecls , TYPES) => [ TYPES ] + rule #gatherTypes(TKW , TKW':TypeKeyWord _:ValTypes TDECLS:TypeDecls , TYPES) => #gatherTypes(TKW, TDECLS, TYPES) requires TKW =/=K TKW' + rule #gatherTypes(TKW , TKW TYPES':ValTypes TDECLS:TypeDecls , TYPES) + => #gatherTypes(TKW , TDECLS:TypeDecls , TYPES + TYPES') + rule #gatherTypes(result , param _ID:Identifier _:ValType TDECLS:TypeDecls , TYPES) => #gatherTypes(result , TDECLS , TYPES) + rule #gatherTypes(param , param _ID:Identifier VTYPE:ValType TDECLS:TypeDecls , TYPES) => #gatherTypes(param , TDECLS , TYPES + VTYPE .ValTypes) +``` + +### Type Use + +A type use is a reference to a type definition. +It may optionally be augmented by explicit inlined parameter and result declarations. +A type use should start with `'(' 'type' x:typeidx ')'` followed by a group of inlined parameter or result declarations. + +```k + syntax FuncType ::= asFuncType ( TypeDecls ) [function, klabel(TypeDeclsAsFuncType)] + | asFuncType ( Map, Map, TypeUse ) [function, klabel(TypeUseAsFuncType) ] + // -------------------------------------------------------------------------------------------- + rule asFuncType(TDECLS:TypeDecls) => gatherTypes(param, TDECLS) -> gatherTypes(result, TDECLS) + rule asFuncType( _ , _ , TDECLS:TypeDecls) => asFuncType(TDECLS) + rule asFuncType(TYPEIDS, TYPES, (type TFIDX )) => {TYPES[#ContextLookup(TYPEIDS ,TFIDX)]}:>FuncType + rule asFuncType(TYPEIDS, TYPES, (type TFIDX ) TDECLS ) => asFuncType(TDECLS) + requires TYPES[#ContextLookup(TYPEIDS, TFIDX)] ==K asFuncType(TDECLS) +``` + +### Type Declaration + +Type could be declared explicitly and could optionally bind with an identifier. +`identifier` for `param` will be used only when the function type is declared when defining a function. +When defining `TypeDefn`, the `identifier` for `param` will be ignored and will not be saved into the module instance. + +```k + syntax TypeDefn ::= #type(type: FuncType, metadata: OptionalId) [klabel(aTypeDefn), symbol] + syntax Alloc ::= alloctype (OptionalId, FuncType) + // ---------------------------------------------------- + rule #type(... type: TYPE, metadata: OID) => alloctype(OID, TYPE) ... + + rule alloctype(OID, TYPE) => . ... + CUR + + CUR + IDS => #saveId(IDS, OID, NEXTIDX) + NEXTIDX => NEXTIDX +Int 1 + TYPES => TYPES [NEXTIDX <- TYPE] + ... + +``` + +Function Declaration and Invocation +----------------------------------- + +### Function Declaration + +Function declarations can look quite different depending on which fields are ommitted and what the context is. +Here, we allow for an "abstract" function declaration using syntax `func_::___`, and a more concrete one which allows arbitrary order of declaration of parameters, locals, and results. +The `FuncSpec` production is used to define all ways that a global can specified. +A function can either be specified by giving a type, what locals it allocates, and a function body; or by an import and it's expected type. +The specification can also include export directives. +The importing and exporting parts of specifications are dealt with in the respective sections for import and export. + +```k + syntax FuncDefn ::= #func(type: Int, locals: VecType, body: Instrs, metadata: FuncMetadata) [klabel(aFuncDefn), symbol] + syntax Alloc ::= allocfunc ( Int , Int , FuncType , VecType , Instrs , FuncMetadata ) + // ---------------------------------------------------------------------------------------- + rule #func(... type: TYPIDX, locals: LOCALS, body: INSTRS, metadata: META) => allocfunc(CUR, NEXTADDR, TYPE, LOCALS, INSTRS, META) ... + CUR + + CUR + ... TYPIDX |-> TYPE ... + NEXTIDX => NEXTIDX +Int 1 + ADDRS => ADDRS [ NEXTIDX <- NEXTADDR ] + ... + + NEXTADDR => NEXTADDR +Int 1 + + rule allocfunc(MOD, ADDR, TYPE, LOCALS, INSTRS, #meta(... id: OID, localIds: LIDS)) => . ... + + ( .Bag + => + ADDR + INSTRS + TYPE + LOCALS + MOD + + OID + LIDS + ... + + + ) + ... + + + syntax FuncMetadata ::= #meta(id: OptionalId, localIds: Map) [klabel(funcMeta), symbol] + // --------------------------------------------------------------------------------------- +``` + +### Function Invocation/Return + +Frames are used to store function return points. +Similar to labels, they sit on the instruction stack (the `` cell), and `return` consumes things following it until hitting it. +Unlike labels, only one frame can be "broken" through at a time. + +```k + syntax Frame ::= "frame" Int ValTypes ValStack Map + // -------------------------------------------------- + rule frame MODIDX' TRANGE VALSTACK' LOCAL' => . ... + VALSTACK => #take(lengthValTypes(TRANGE), VALSTACK) ++ VALSTACK' + _ => LOCAL' + _ => MODIDX' +``` + +When we invoke a function, the element on the top of the stack will become the last parameter of the function. +For example, when we call `(invoke "foo" (i64.const 100) (i64.const 43) (i32.const 22))`, `(i32.const 22)` will be on the top of ``, but it will be the last parameter of this function invocation if this function takes 3 parameters. +That is, whenever we want to `#take` or `#drop` an array of `params`, we need to reverse the array of `params` to make the type of the last parameter matching with the type of the value on the top of stack. +The `#take` function will return the parameter stack in the reversed order, then we need to reverse the stack again to get the actual parameter array we want. + +```k + syntax Instr ::= "(" "invoke" Int ")" + // ------------------------------------- + rule ( invoke FADDR ) + => init_locals #revs(#take(lengthValTypes(TDOMAIN), VALSTACK)) ++ #zero(TLOCALS) + ~> #block([TRANGE], INSTRS, .Int) + ~> frame MODIDX TRANGE #drop(lengthValTypes(TDOMAIN), VALSTACK) LOCAL + ... + + VALSTACK => .ValStack + LOCAL => .Map + MODIDX => MODIDX' + + FADDR + INSTRS + [ TDOMAIN ] -> [ TRANGE ] + [ TLOCALS ] + MODIDX' + ... + + + rule return ~> (_S:Stmt => .) ... + rule return ~> (_L:Label => .) ... + rule (return => .) ~> _FR:Frame ... +``` + +### Function Call + +`call funcidx` and `call_indirect typeidx` are 2 control instructions that invokes a function in the current frame. + +```k + syntax Instr ::= #call(Int) [klabel(aCall), symbol] + // --------------------------------------------------- + rule #call(IDX) => ( invoke FADDR ) ... + CUR + + CUR + ... IDX |-> FADDR ... + ... + +``` + +```k + syntax Instr ::= "#call_indirect" "(" Int ")" [klabel(aCall_indirect), symbol] + // ------------------------------------------------------------------------------ + rule #call_indirect(I) => call_indirect (type I) ... +``` + +TODO: This is kept for compatibility with the text format. +The `TypeUses` should be desugared to use a type-index instead. +But this requires a recursive descent into all the instructions of a function, with feedback up to the top level. +The types need to be inserted at the definitions level, if a previously undeclared type is present in a `call_indirect` function. + +```k + rule call_indirect TUSE:TypeUse => ( invoke FADDR ) ... + CUR + < i32 > IDX : VALSTACK => VALSTACK + + CUR + TYPEIDS + TYPES + 0 |-> ADDR + ... + + + ADDR + ... IDX |-> FADDR ... + ... + + + FADDR + FTYPE + ... + + requires asFuncType(TYPEIDS, TYPES, TUSE) ==K FTYPE + + rule call_indirect TUSE:TypeUse => trap ... + CUR + < i32 > IDX : VALSTACK => VALSTACK + + CUR + TYPEIDS + TYPES + 0 |-> ADDR + ... + + + ADDR + ... IDX |-> FADDR ... + ... + + + FADDR + FTYPE + ... + + requires asFuncType(TYPEIDS, TYPES, TUSE) =/=K FTYPE + + rule call_indirect _TUSE:TypeUse => trap ... + CUR + < i32 > IDX : VALSTACK => VALSTACK + + CUR + 0 |-> ADDR + ... + + + ADDR + TDATA + ... + + requires notBool IDX in_keys(TDATA) +``` + +Table +----- + +The allocation of a new `tableinst`. +Currently at most one table may be defined or imported in a single module. +The only allowed `TableElemType` is "funcref", so we ignore this term in the reducted sort. +The table values are addresses into the store of functions. +The `TableSpec` production is used to define all ways that a global can specified. +A table can either be specified by giving its type (limits and `funcref`); by specifying a vector of its initial `elem`ents; or by an import and its expected type. +The specification can also include export directives. +The importing and exporting parts of specifications are dealt with in the respective sections for import and export. + +```k + syntax TableDefn ::= #table (limits: Limits, metadata: OptionalId) [klabel(aTableDefn), symbol] + syntax Alloc ::= alloctable (OptionalId, Int, OptionalInt) + // ---------------------------------------------------------- + rule #table(... limits: #limitsMin(MIN), metadata: OID) => alloctable(OID, MIN, .Int) ... + requires MIN <=Int #maxTableSize() + rule #table(... limits: #limits(MIN, MAX), metadata: OID) => alloctable(OID, MIN, MAX) ... + requires MIN <=Int #maxTableSize() + andBool MAX <=Int #maxTableSize() + + rule alloctable(ID, MIN, MAX) => . ... + CUR + + CUR + IDS => #saveId(IDS, ID, 0) + .Map => (0 |-> NEXTADDR) + ... + + NEXTADDR => NEXTADDR +Int 1 + + ( .Bag + => + NEXTADDR + MAX + MIN + .Map + + ) + ... + +``` + +Memory +------ + +When memory is allocated, it is put into the store at the next available index. +Memory can only grow in size, so the minimum size is the initial value. +Currently, only one memory may be accessible to a module, and thus the `` cell is an array with at most one value, at index 0. +The `MemorySpec` production is used to define all ways that a global can specified. +A memory can either be specified by giving its type (limits); by specifying a vector of its initial `data`; or by an import and its expected type. +The specification can also include export directives. +The importing and exporting parts of specifications are dealt with in the respective sections for import and export. + +```k + syntax MemoryDefn ::= #memory(limits: Limits, metadata: OptionalId) [klabel(aMemoryDefn), symbol] + syntax Alloc ::= allocmemory (OptionalId, Int, OptionalInt) + // ----------------------------------------------------------- + rule #memory(... limits: #limitsMin(MIN), metadata: OID) => allocmemory(OID, MIN, .Int) ... + requires MIN <=Int #maxMemorySize() + rule #memory(... limits: #limits(MIN, MAX), metadata: OID) => allocmemory(OID, MIN, MAX) ... + requires MIN <=Int #maxMemorySize() + andBool MAX <=Int #maxMemorySize() + + rule allocmemory(ID, MIN, MAX) => . ... + CUR + + CUR + IDS => #saveId(IDS, ID, 0) + .Map => (0 |-> NEXTADDR) + ... + + NEXTADDR => NEXTADDR +Int 1 + + ( .Bag + => + NEXTADDR + MAX + MIN + ... + + ) + ... + +``` + +The assorted store operations take an address of type `i32` and a value. +The `storeX` operations first wrap the the value to be stored to the bit wdith `X`. +The value is encoded as bytes and stored at the "effective address", which is the address given on the stack plus offset. + +```k + syntax Instr ::= #store(ValType, StoreOp, offset : Int) [klabel(aStore), symbol] + | IValType "." StoreOp Int Int + // | FValType "." StoreOp Int Float + | "store" "{" Int Int Number "}" + // ----------------------------------------------- + rule #store(ITYPE:IValType, SOP, OFFSET) => ITYPE . SOP (IDX +Int OFFSET) VAL ... + < ITYPE > VAL : < i32 > IDX : VALSTACK => VALSTACK + + rule store { WIDTH EA VAL } => . ... + CUR + + CUR + 0 |-> ADDR + ... + + + ADDR + SIZE + DATA => #setRange(DATA, EA, VAL, WIDTH) + ... + + requires (EA +Int WIDTH) <=Int (SIZE *Int #pageSize()) + + rule store { WIDTH EA _ } => trap ... + CUR + + CUR + 0 |-> ADDR + ... + + + ADDR + SIZE + ... + + requires (EA +Int WIDTH) >Int (SIZE *Int #pageSize()) + + rule ITYPE . store EA VAL => store { #numBytes(ITYPE) EA VAL } ... + rule _ . store8 EA VAL => store { 1 EA #wrap(1, VAL) } ... + rule _ . store16 EA VAL => store { 2 EA #wrap(2, VAL) } ... + rule i64 . store32 EA VAL => store { 4 EA #wrap(4, VAL) } ... +``` + +The assorted load operations take an address of type `i32`. +The `loadX_sx` operations loads `X` bits from memory, and extend it to the right length for the return value, interpreting the bytes as either signed or unsigned according to `sx`. +The value is fetched from the "effective address", which is the address given on the stack plus offset. +Sort `Signedness` is defined in module `BYTES`. + +```k + syntax Instr ::= #load(ValType, LoadOp, offset : Int) [klabel(aLoad), symbol] + | "load" "{" IValType Int Int Signedness"}" + | "load" "{" IValType Int Int Signedness Bytes"}" + | "load" "{" IValType Int Int Signedness"}" + | IValType "." LoadOp Int + // ---------------------------------------- + rule #load(ITYPE:IValType, LOP, OFFSET) => ITYPE . LOP (IDX +Int OFFSET) ... + < i32 > IDX : VALSTACK => VALSTACK + + rule ITYPE . load EA:Int => load { ITYPE #numBytes(ITYPE) EA Unsigned } ... + rule ITYPE . load8_u EA:Int => load { ITYPE 1 EA Unsigned } ... + rule ITYPE . load16_u EA:Int => load { ITYPE 2 EA Unsigned } ... + rule i64 . load32_u EA:Int => load { i64 4 EA Unsigned } ... + rule ITYPE . load8_s EA:Int => load { ITYPE 1 EA Signed } ... + rule ITYPE . load16_s EA:Int => load { ITYPE 2 EA Signed } ... + rule i64 . load32_s EA:Int => load { i64 4 EA Signed } ... + + rule load { ITYPE WIDTH EA SIGN } => load { ITYPE WIDTH EA SIGN DATA } ... + CUR + + CUR + 0 |-> ADDR + ... + + + ADDR + SIZE + DATA + ... + + requires (EA +Int WIDTH) <=Int (SIZE *Int #pageSize()) + + rule load { _ WIDTH EA _ } => trap ... + CUR + + CUR + 0 |-> ADDR + ... + + + ADDR + SIZE + ... + + requires (EA +Int WIDTH) >Int (SIZE *Int #pageSize()) + + rule load { ITYPE WIDTH EA Signed DATA } => #chop(< ITYPE > #signedWidth(WIDTH, #getRange(DATA, EA, WIDTH))) ... + rule load { ITYPE WIDTH EA Unsigned DATA } => < ITYPE > #getRange(DATA, EA, WIDTH) ... +``` + +The `size` operation returns the size of the memory, measured in pages. + +```k + rule memory.size => < i32 > SIZE ... + CUR + + CUR + 0 |-> ADDR + ... + + + ADDR + SIZE + ... + +``` + +`grow` increases the size of memory in units of pages. +Failure to grow is indicated by pushing -1 to the stack. +Success is indicated by pushing the previous memory size to the stack. +`grow` is non-deterministic and may fail either due to trying to exceed explicit max values, or because the embedder does not have resources available. +By setting the `` field in the configuration to `true`, the sematnics ensure memory growth only fails if the memory in question would exceed max bounds. + +```k + syntax Instr ::= "grow" Int + // --------------------------- + rule memory.grow => grow N ... + < i32 > N : VALSTACK => VALSTACK + + rule grow N => < i32 > SIZE ... + CUR + + CUR + 0 |-> ADDR + ... + + + ADDR + MAX + SIZE => SIZE +Int N + ... + + requires #growthAllowed(SIZE +Int N, MAX) + + rule grow N => < i32 > #unsigned(i32, -1) ... + DET:Bool + CUR + + CUR + 0 |-> ADDR + ... + + + ADDR + MAX + SIZE + ... + + requires notBool DET + orBool notBool #growthAllowed(SIZE +Int N, MAX) + + syntax Bool ::= #growthAllowed(Int, OptionalInt) [function] + // ----------------------------------------------------------- + rule #growthAllowed(SIZE, .Int ) => SIZE <=Int #maxMemorySize() + rule #growthAllowed(SIZE, I:Int) => #growthAllowed(SIZE, .Int) andBool SIZE <=Int I +``` + +However, the absolute max allowed size if 2^16 pages. +Incidentally, the page size is 2^16 bytes. +The maximum of table size is 2^32 bytes. + +```k + syntax Int ::= #pageSize() [function] + syntax Int ::= #maxMemorySize() [function] + syntax Int ::= #maxTableSize() [function] + // ------------------------------------------ + rule #pageSize() => 65536 + rule #maxMemorySize() => 65536 + rule #maxTableSize() => 4294967296 +``` + +Initializers +------------ + +### Table initialization + +Tables can be initialized with element and the element type is always `funcref`. +The initialization of a table needs an offset and a list of functions, given as `Index`s. +A table index is optional and will be default to zero. + +```k + + syntax ElemDefn ::= #elem(index : Int, offset : Instrs, elemSegment : Ints) [klabel(aElemDefn), symbol] + | "elem" "{" Int Ints "}" + syntax Stmt ::= #initElements ( Int, Int, Map, Ints ) + // ----------------------------------------------------- + rule #elem(TABIDX, IS, ELEMSEGMENT ) => sequenceInstrs(IS) ~> elem { TABIDX ELEMSEGMENT } ... + + rule elem { TABIDX ELEMSEGMENT } => #initElements ( ADDR, OFFSET, FADDRS, ELEMSEGMENT ) ... + CUR + < i32 > OFFSET : STACK => STACK + + CUR + FADDRS + TABIDX |-> ADDR + ... + + + rule #initElements ( _, _, _, .Ints ) => . ... + rule #initElements ( ADDR, OFFSET, FADDRS, E:Int ES ) => #initElements ( ADDR, OFFSET +Int 1, FADDRS, ES ) ... + + ADDR + DATA => DATA [ OFFSET <- FADDRS[E] ] + ... + +``` + +### Memory initialization + +Memories can be initialized with data, specified as a list of bytes together with an offset. +The `data` initializer simply puts these bytes into the specified memory, starting at the offset. + +```k + syntax DataDefn ::= #data(index : Int, offset : Instrs, data : Bytes) [klabel(aDataDefn), symbol] + | "data" "{" Int Bytes "}" + // -------------------------------------------- + // Default to memory 0. + rule #data(IDX, IS, DATA) => sequenceInstrs(IS) ~> data { IDX DATA } ... + + // For now, deal only with memory 0. + rule data { MEMIDX DSBYTES } => . ... + < i32 > OFFSET : STACK => STACK + CUR + + CUR + MEMIDX |-> ADDR + ... + + + ADDR + DATA => #setRange(DATA, OFFSET, Bytes2Int(DSBYTES, LE, Unsigned), lengthBytes(DSBYTES)) + ... + + + syntax Int ::= Int "up/Int" Int [function] + // ------------------------------------------ + rule I1 up/Int I2 => (I1 +Int (I2 -Int 1)) /Int I2 requires I2 >Int 0 +``` + +Start Function +-------------- + +The `start` component of a module declares the function index of a `start function` that is automatically invoked when the module is instantiated, after `tables` and `memories` have been initialized. + +```k + syntax StartDefn ::= #start(Int) [klabel(aStartDefn), symbol] + // ------------------------------------------------------------- + rule #start(IDX) => ( invoke FADDR ) ... + CUR + + CUR + ... IDX |-> FADDR ... + ... + +``` + +Export +------ + +Exports make functions, tables, memories and globals available for importing into other modules. + +```k + syntax ExportDefn ::= #export(name : WasmString, index : Int) [klabel(aExportDefn), symbol] + syntax Alloc ::= ExportDefn + // --------------------------- + rule #export(ENAME, IDX) => . ... + CUR + + CUR + EXPORTS => EXPORTS [ ENAME <- IDX ] + ... + +``` + +Imports +------- + +Imports need to describe the type of what is imported. +That an import is really a subtype of the declared import needs to be checked at instantiation time. +The value of a global gets copied when it is imported. + +```k + syntax ImportDefn ::= #import(mod : WasmString, name : WasmString, ImportDesc) [klabel(aImportDefn), symbol] + syntax ImportDesc ::= #funcDesc (id: OptionalId, type: Int) [klabel(aFuncDesc), symbol] + | #globalDesc (id: OptionalId, type: GlobalType) [klabel(aGlobalDesc), symbol] + | #tableDesc (id: OptionalId, type: Limits) [klabel(aTableDesc), symbol] + | #memoryDesc (id: OptionalId, type: Limits) [klabel(aMemoryDesc), symbol] + syntax Alloc ::= ImportDefn + // -------------------------------- + rule #import(MOD, NAME, #funcDesc(... type: TIDX) ) => . ... + CUR + + CUR + TYPES + FS => FS [NEXT <- ADDR] + NEXT => NEXT +Int 1 + ... + + ... MOD |-> MODIDX ... + + MODIDX + ... IDX |-> ADDR ... + ... NAME |-> IDX ... + ... + + + ADDR + FTYPE + ... + + requires FTYPE ==K TYPES[TIDX] + + rule #import(MOD, NAME, #tableDesc(... id: OID, type: LIM) ) => . ... + CUR + + CUR + IDS => #saveId(IDS, OID, 0) + .Map => 0 |-> ADDR + ... + + ... MOD |-> MODIDX ... + + MODIDX + IDS' + ... #ContextLookup(IDS' , TFIDX) |-> ADDR ... + ... NAME |-> TFIDX ... + ... + + + ADDR + MAX + SIZE + ... + + requires #limitsMatchImport(SIZE, MAX, LIM) + + rule #import(MOD, NAME, #memoryDesc(... id: OID, type: LIM) ) => . ... + CUR + + CUR + IDS => #saveId(IDS, OID, 0) + .Map => 0 |-> ADDR + ... + + ... MOD |-> MODIDX ... + + MODIDX + IDS' + ... #ContextLookup(IDS' , TFIDX) |-> ADDR ... + ... NAME |-> TFIDX ... + ... + + + ADDR + MAX + SIZE + ... + + requires #limitsMatchImport(SIZE, MAX, LIM) + + rule #import(MOD, NAME, #globalDesc(... id: OID, type: MUT TYP) ) => . ... + CUR + + CUR + IDS => #saveId(IDS, OID, NEXT) + GS => GS [NEXT <- ADDR] + NEXT => NEXT +Int 1 + ... + + ... MOD |-> MODIDX ... + + MODIDX + IDS' + ... #ContextLookup(IDS' , TFIDX) |-> ADDR ... + ... NAME |-> TFIDX ... + ... + + + ADDR + _ + MUT + +``` + +Tables and memories have proper subtyping, unlike globals and functions where a type is only a subtype of itself. +Subtyping is determined by whether the limits of one table/memory fit in the limits of another. +The following function checks if the limits in the first parameter *match*, i.e. is a subtype of, the limits in the second. + +```k + syntax Bool ::= #limitsMatchImport(Int, OptionalInt, Limits) [function] + // ----------------------------------------------------------------------- + rule #limitsMatchImport(L1, _, #limitsMin(L2:Int )) => L1 >=Int L2 + rule #limitsMatchImport( _, .Int, #limits( _:Int, _)) => false + rule #limitsMatchImport(L1, U1:Int, #limits(L2:Int, U2)) => L1 >=Int L2 andBool U1 <=Int U2 +``` + +Module Instantiation +-------------------- + +There is some dependencies among definitions that require that we do them in a certain order, even though they may appear in many valid orders. +First, functions, tables, memories and globals get *allocated*. +Then, tables, memories and globals get *instantiated* with elements, data and initialization vectors. +However, since (currently) globals can only make use of imported globals to be instantiated, we can initialize at allocation time. +Finally, the start function is invoked. +Exports may appear anywhere in a module, but can only be performed after what they refer to has been allocated. +Exports that are inlined in a definition, e.g., `func (export "foo") ...`, are safe to extract as they appear. +Imports must appear before any allocations in a module, due to validation. + +A subtle point is related to tables with inline `elem` definitions: since these may refer to functions by identifier, we need to make sure that tables definitions come after function definitions. + +`sortModule` takes a list of definitions and returns a map with different groups of definitions, preserving the order within each group. +The groups are chosen to represent different stages of allocation and instantiation. + +```k + syntax ModuleDecl ::= #module ( types: Defns, funcs: Defns, tables: Defns, mems: Defns, globals: Defns, elem: Defns, data: Defns, start: Defns, importDefns: Defns, exports: Defns, metadata: ModuleMetadata) [klabel(aModuleDecl), symbol] + // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + syntax ModuleDecl ::= #emptyModule(OptionalId) [function, total] + // --------------------------------------------------------------------- + rule #emptyModule(OID) => #module (... types: .Defns, funcs: .Defns, tables: .Defns, mems: .Defns, globals: .Defns, elem: .Defns, data: .Defns, start: .Defns, importDefns: .Defns, exports: .Defns, metadata: #meta(... id: OID, funcIds: .Map, filename: .String)) + + syntax ModuleMetadata ::= #meta(id: OptionalId, funcIds: Map, filename : OptionalString) [klabel(moduleMeta), symbol] + syntax OptionalString ::= ".String" [klabel(.String), symbol] | String + // ---------------------------------------------------------------------- +``` + +A new module instance gets allocated. +Then, the surrounding `module` tag is discarded, and the definitions are executed, putting them into the module currently being defined. + +```k + rule #module(... types: TS, funcs: FS, tables: TABS, mems: MS, globals: GS, elem: EL, data: DAT, start: S, importDefns: IS, exports: ES, + metadata: #meta(... id: OID, funcIds: FIDS, filename: FILE)) + => sequenceDefns(TS) + ~> sequenceDefns(IS) + ~> sequenceDefns(FS) + ~> sequenceDefns(GS) + ~> sequenceDefns(MS) + ~> sequenceDefns(TABS) + ~> sequenceDefns(ES) + ~> sequenceDefns(EL) + ~> sequenceDefns(DAT) + ~> sequenceDefns(S) + ... + + _ => NEXT + NEXT => NEXT +Int 1 + IDS => #saveId(IDS, OID, NEXT) + + ( .Bag + => + NEXT + + FILE + OID + FIDS + ... + + ... + + ) + ... + +``` + +After a module is instantiated, it should be saved somewhere. +How this is done is up to the embedder. + +```k +endmodule +``` diff --git a/docs/runtimeverification-wasm-semantics/wrc20.md b/docs/runtimeverification-wasm-semantics/wrc20.md new file mode 100644 index 000000000..b06c03c7f --- /dev/null +++ b/docs/runtimeverification-wasm-semantics/wrc20.md @@ -0,0 +1,309 @@ +WRC20 +===== + +```k +requires "kwasm-lemmas.md" +``` + +Lemmas +------ + +```k +module WRC20-LEMMAS [symbolic] + imports WRC20 + imports KWASM-LEMMAS +``` + +These conversions turns out to be helpful in this particular proof, but we don't want to apply it on all KWasm proofs. + +```k + rule X /Int N => X >>Int 8 requires N ==Int 256 [simplification] +``` + +TODO: The following theorems should be generalized and proven, and moved to the set of general lemmas. +Perhaps using `requires N ==Int 2 ^Int log2Int(N)`? +Also, some of these have concrete integers on the LHS. +It may be better to use a symbolic value as a side condition, e.g. `rule N => foo requires N ==Int 8`, because simplifications rely on exact matching of the LHS. + +```k + rule X *Int 256 >>Int N => (X >>Int (N -Int 8)) requires N >=Int 8 [simplification] + + rule (Y +Int X *Int 256) >>Int N => (Y >>Int N) +Int (X >>Int (N -Int 8)) requires N >=Int 8 [simplification] + + rule (X < (X +Int (Y < (X +Int (Y < ((X <Int M + [simplification] + + rule (X +Int (Y < X +Int ((Y <=Int 256 + andBool 0 <=Int X + andBool X =Int 8 + [simplification] + + rule #wrap(N, (X +Int (Y < X +Int (#wrap(N, Y <=Int 1 + andBool 0 <=Int X + andBool X =Int 8 + [simplification] +``` + +```k +endmodule +``` + +Macros +------ + +The following module gives macros for the wrc20 code so that its parts can be expressed succinctly in proofs. + +```k +module WRC20 + imports WASM-TEXT +``` + +A module of shorthand commands for the WRC20 module. + +```k + syntax WasmStringToken ::= "#ethereumModule" [macro] + // ---------------------------------------------------- + rule #ethereumModule => #token("\"ethereum\"", "WasmStringToken") + + syntax ModuleDecl ::= "#wrc20" [macro] + syntax Defns ::= "#wrc20Body" [macro] + syntax Defns ::= "#wrc20Imports" [macro] + syntax Defns ::= "#wrc20Functions_fastBalance" [macro] + syntax Defns ::= "#wrc20ReverseBytes" [macro] + syntax Int ::= "#wrc20ReverseBytesTypeIdx" [macro] + syntax FuncType ::= "#wrc20ReverseBytesType" [macro] + // ----------------------------------------------------------- + rule #wrc20 => ( module #wrc20Body ) + + rule #wrc20Body => #wrc20Imports ++Defns #wrc20Functions_fastBalance + + rule #wrc20Imports => + (func String2Identifier("$revert") ( import #ethereumModule #token("\"revert\"" , "WasmStringToken") ) param i32 i32 .ValTypes .TypeDecls ) + (func String2Identifier("$finish") ( import #ethereumModule #token("\"finish\"" , "WasmStringToken") ) param i32 i32 .ValTypes .TypeDecls ) + (func String2Identifier("$getCallDataSize") ( import #ethereumModule #token("\"getCallDataSize\"", "WasmStringToken") ) result i32 .ValTypes .TypeDecls ) + (func String2Identifier("$callDataCopy") ( import #ethereumModule #token("\"callDataCopy\"" , "WasmStringToken") ) param i32 i32 i32 .ValTypes .TypeDecls ) + (func String2Identifier("$storageLoad") ( import #ethereumModule #token("\"storageLoad\"" , "WasmStringToken") ) param i32 i32 .ValTypes .TypeDecls ) + (func String2Identifier("$storageStore") ( import #ethereumModule #token("\"storageStore\"" , "WasmStringToken") ) param i32 i32 .ValTypes .TypeDecls ) + (func String2Identifier("$getCaller") ( import #ethereumModule #token("\"getCaller\"" , "WasmStringToken") ) param i32 .ValTypes .TypeDecls ) + ( memory ( export #token("\"memory\"", "WasmStringToken") ) 1 ) + .Defns + + rule #wrc20Functions_fastBalance => + (func ( export #token("\"main\"", "WasmStringToken") ) .TypeDecls .LocalDecls + block .TypeDecls + block .TypeDecls + call String2Identifier("$getCallDataSize") + i32.const 4 + i32.ge_u + br_if 0 + i32.const 0 + i32.const 0 + call String2Identifier("$revert") + br 1 + .EmptyStmts + end + i32.const 0 + i32.const 0 + i32.const 4 + call String2Identifier("$callDataCopy") + block .TypeDecls + i32.const 0 + i32.load + i32.const 436376473:Int + i32.eq + i32.eqz + br_if 0 + call String2Identifier("$do_balance") + br 1 + .EmptyStmts + end + block .TypeDecls + i32.const 0 i32.load + i32.const 3181327709:Int + i32.eq + i32.eqz + br_if 0 + call String2Identifier("$do_transfer") + br 1 + .EmptyStmts + end + i32.const 0 + i32.const 0 + call String2Identifier("$revert") + .EmptyStmts + end + .EmptyStmts + ) + + (func String2Identifier("$do_balance") .TypeDecls .LocalDecls + block .TypeDecls + block .TypeDecls + call String2Identifier("$getCallDataSize") + i32.const 24 + i32.eq + br_if 0 + i32.const 0 + i32.const 0 + call String2Identifier("$revert") + br 1 + .EmptyStmts + end + i32.const 0 + i32.const 4 + i32.const 20 + call String2Identifier("$callDataCopy") + i32.const 0 + i32.const 32 + call String2Identifier("$storageLoad") + i32.const 32 + i32.const 8 + call String2Identifier("$finish") + .EmptyStmts + end + .EmptyStmts ) + + (func String2Identifier("$do_transfer") .TypeDecls local i64 i64 i64 .ValTypes .LocalDecls + block .TypeDecls + block .TypeDecls + call String2Identifier("$getCallDataSize") + i32.const 32 + i32.eq + br_if 0 + i32.const 0 + i32.const 0 + call String2Identifier("$revert") + br 1 + .EmptyStmts + end + i32.const 0 + call String2Identifier("$getCaller") + i32.const 32 + i32.const 4 + i32.const 20 + call String2Identifier("$callDataCopy") + i32.const 64 + i32.const 24 + i32.const 8 + call String2Identifier("$callDataCopy") + i32.const 64 + i64.load + call String2Identifier("$i64.reverse_bytes") + local.set 0 + i32.const 0 + i32.const 64 + call String2Identifier("$storageLoad") + i32.const 64 + i64.load + call String2Identifier("$i64.reverse_bytes") + local.set 1 + i32.const 32 + i32.const 64 + call String2Identifier("$storageLoad") + i32.const 64 + i64.load + call String2Identifier("$i64.reverse_bytes") + local.set 2 + block .TypeDecls + local.get 0 + local.get 1 + i64.le_u + br_if 0 + i32.const 0 + i32.const 0 + call String2Identifier("$revert") + br 1 + .EmptyStmts + end + local.get 1 + local.get 0 + i64.sub + local.set 1 + local.get 2 + local.get 0 + i64.add + local.set 2 + i32.const 64 + local.get 1 + call String2Identifier("$i64.reverse_bytes") + i64.store + i32.const 0 + i32.const 64 + call String2Identifier("$storageStore") + i32.const 64 + local.get 2 + call String2Identifier("$i64.reverse_bytes") + i64.store + i32.const 32 + i32.const 64 + call String2Identifier("$storageStore") + .EmptyStmts + end + .EmptyStmts + ) + + #wrc20ReverseBytes + + rule #wrc20ReverseBytesTypeIdx => 1 + rule #wrc20ReverseBytesType => [ i64 ] -> [ i64 ] [ignoreThisAttribute] + + rule #wrc20ReverseBytes => + (func String2Identifier("$i64.reverse_bytes") (type #wrc20ReverseBytesTypeIdx) local i64 i64 .ValTypes .LocalDecls + block .TypeDecls + loop .TypeDecls + local.get 1 + i64.const 8 + i64.ge_u + br_if 1 + local.get 0 + i64.const 56 + local.get 1 + i64.const 8 + i64.mul + i64.sub + i64.shl + i64.const 56 + i64.shr_u + i64.const 56 + i64.const 8 + local.get 1 + i64.mul + i64.sub + i64.shl + local.get 2 + i64.add + local.set 2 + local.get 1 + i64.const 1 + i64.add + local.set 1 + br 0 + .EmptyStmts + end + .EmptyStmts + end + local.get 2 + .EmptyStmts + ) + .Defns + + syntax Defns ::= Defns "++Defns" Defns [function, total] + // ------------------------------------------------------------- + rule .Defns ++Defns DS' => DS' + rule (D DS) ++Defns DS' => D (DS ++Defns DS') +``` + +```k +endmodule +```