commit d4e56f2ffcd2b964118739b88a34e32bb23f7d13 Author: Bruno Verachten Date: Wed Apr 1 20:28:12 2026 +0200 ci: add riscv64-linux build using native RISE runner Add riscv64gc-unknown-linux-musl to the build matrix using a native ubuntu-24.04-riscv runner (RISE Project), mirroring the arm64-linux pattern which uses ubuntu-22.04-arm. Changes: - Use ubuntu-24.04-riscv runner instead of cross-compilation on x86_64 - Dockerfile.riscv64-linux: Ubuntu 24.04 base (AlmaLinux 8 has no riscv64 image); native build-essential, no cross-compiler env vars - docker-build.sh: skip wasmtime volume mount when WASI_SDK_CI_SKIP_SYSROOT is set (wasmtime is not available on riscv64 runners yet) - WASI_SDK_CI_SKIP_SYSROOT: 1 and WASI_SDK_LLDB=OFF kept as before; sysroot tests require running wasm binaries which needs wasmtime RISE runners are GitHub Actions runners for riscv64, free for open source projects: https://github.com/apps/rise-risc-v-runners Closes #607 Signed-off-by: Bruno Verachten diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a110ba1 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +# Our docker builds do not require the submodule sources so exclude them as +# they can be very big. +/src +.git diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8237fdf --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Bourne-style shell scripts: These are Unix-style scripts. Don't add CR's. +*.sh text eol=lf diff --git a/.github/actions/checkout/action.yml b/.github/actions/checkout/action.yml new file mode 100644 index 0000000..aca1b72 --- /dev/null +++ b/.github/actions/checkout/action.yml @@ -0,0 +1,16 @@ +name: 'Prepare wasi-sdk git directory' +description: 'Prepare wasi-sdk git directory' + +runs: + using: composite + steps: + - run: git fetch --tags --force + name: Force-fetch tags to work around actions/checkout#290 + shell: bash + # We can't use `--depth 1` here sadly because the GNU config + # submodule is not pinned to a particular tag/branch. Please + # bump depth (or even better, the submodule), in case of "error: + # Server does not allow request for unadvertised object" in the + # future. + - run: git submodule update --init --depth 64 --jobs 3 + shell: bash diff --git a/.github/actions/install-deps/action.yml b/.github/actions/install-deps/action.yml new file mode 100644 index 0000000..e3d00f8 --- /dev/null +++ b/.github/actions/install-deps/action.yml @@ -0,0 +1,28 @@ +name: 'Install wasi-sdk dependencies' +description: 'Install wasi-sdk dependencies' + +runs: + using: composite + steps: + - name: Setup `wasmtime` for tests + uses: bytecodealliance/actions/wasmtime/setup@v1 + with: + version: "41.0.3" + - name: Install ccache, ninja (macOS) + run: brew install ccache ninja + if: runner.os == 'macOS' + shell: bash + - name: Install ccache, ninja (Windows) + run: choco install ccache ninja + if: startsWith(matrix.os, 'windows') + shell: bash + # Windows arm runners don't come with rust by default (see https://github.com/actions/partner-runner-images/blob/main/images/arm-windows-11-image.md) + # but the x86 ones do (see https://github.com/actions/runner-images/blob/main/images/windows/Windows2025-Readme.md) + - name: Install cargo (Windows-arm) + run: choco install rust + if: matrix.os == 'windows-11-arm' + shell: bash + - name: Install ccache, ninja (Linux) + run: sudo apt-get install -y ccache ninja-build + if: runner.os == 'Linux' + shell: bash diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..16401db --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,281 @@ +name: CI + +on: + push: + tags: + - 'wasi-sdk-*' + branches: + - main + + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build ${{ matrix.artifact }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - artifact: x86_64-linux + os: ubuntu-24.04 + + - artifact: arm64-linux + os: ubuntu-22.04-arm + + - artifact: riscv64-linux + os: ubuntu-24.04-riscv + rust_target: riscv64gc-unknown-linux-gnu + cross_cmake_args: -DWASI_SDK_LLDB=OFF + env: + WASI_SDK_CI_SKIP_SYSROOT: 1 + + - artifact: arm64-macos + os: macos-14 + rust_target: aarch64-apple-darwin + env: + WASI_SDK_CI_TOOLCHAIN_LLVM_CMAKE_ARGS: >- + -DCMAKE_OSX_DEPLOYMENT_TARGET=10.12 + -DCMAKE_OSX_ARCHITECTURES=arm64 + + - artifact: x86_64-macos + os: macos-14 + rust_target: x86_64-apple-darwin + env: + WASI_SDK_CI_SKIP_SYSROOT: 1 + WASI_SDK_CI_TOOLCHAIN_LLVM_CMAKE_ARGS: >- + -DCMAKE_OSX_DEPLOYMENT_TARGET=10.12 + -DCMAKE_OSX_ARCHITECTURES=x86_64 + + - artifact: x86_64-windows + os: windows-2022 + + - artifact: arm64-windows + os: windows-11-arm + + env: ${{ matrix.env || fromJSON('{}') }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - uses: ./.github/actions/checkout + - uses: ./.github/actions/install-deps + + # Persist ccache-based caches across builds. This directory is configured + # via the CCACHE_DIR env var below for ccache to use. + # + # Bump the prefix number to evict all previous caches and enforce a clean + # build, in the unlikely case that some weird build error occur and ccache + # becomes a potential suspect. + - uses: actions/cache@v5 + id: cache-restore + with: + path: ${{ runner.tool_cache }}/ccache + key: 0-cache-${{ matrix.artifact }}-${{ github.run_id }} + restore-keys: | + 0-cache-${{ matrix.artifact }}- + - run: | + mkdir -p '${{ runner.tool_cache }}/ccache' + echo 'CCACHE_DIR=${{ runner.tool_cache }}/ccache' >> $GITHUB_ENV + shell: bash + + # Configure CMake flags for `ci/build.sh` as necessary for each + # matrix entry. + - run: | + cmake_args=-DWASI_SDK_ARTIFACT=${{ matrix.artifact }} + if [ "${{ matrix.rust_target }}" != "" ]; then + rustup target add ${{ matrix.rust_target }} + cmake_args="$cmake_args -DRUST_TARGET=${{ matrix.rust_target }}" + fi + if [ "${{ matrix.cross_cmake_args }}" != "" ]; then + cmake_args="$cmake_args ${{ matrix.cross_cmake_args }}" + fi + echo WASI_SDK_CI_TOOLCHAIN_CMAKE_ARGS="$cmake_args" >> $GITHUB_ENV + shell: bash + + - name: Clear ccache statistics + run: ccache --zero-stats + + - name: Build and test (macOS) + run: ./ci/build.sh + if: runner.os == 'macOS' + + - name: Build and test (Linux) + run: ./ci/docker-build.sh ${{ matrix.artifact }} + if: runner.os == 'Linux' + + # Setup the VS Developoer Prompt environment variables to explicitly use + # MSVC to compile LLVM as that avoids extra runtime dependencies + # msys/mingw might bring. + # + # As of 2024-07-22 this sha is the "v1.13.0" tag. + - uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 + if: startsWith(matrix.os, 'windows') + - name: Build and test (Windows) + run: | + # Delete a troublesome binary as recommended here + # https://github.com/ilammy/msvc-dev-cmd?tab=readme-ov-file#name-conflicts-with-shell-bash + rm /usr/bin/link + # Use a shorter build directory than the default on Windows to avoid + # hitting path length and command line length limits. See + # WebAssembly/wasi-libc#514. Despite using a different build directory + # though still move the `dist` folder to `build/dist` so the upload + # step below doesn't need a windows-specific hook. + ./ci/build.sh C:/wasi-sdk + mkdir build + cp -r C:/wasi-sdk/dist build + shell: bash + if: startsWith(matrix.os, 'windows') + + # Upload the `dist` folder from the build as the artifacts for this + # runner. + - name: Upload artifacts + uses: actions/upload-artifact@v6 + with: + name: ${{ format( 'dist-{0}', matrix.artifact) }} + path: build/dist + + # Caches are persisted across runs by restoring the latest cache which + # means that quite a lot of cruft can accumulate. Prune older entries that + # haven't been used by this run to avoid the cache continuously getting + # larger. In theory this should use `--evict-older-than $dur` where `$dur` + # is the time since the start of the run, but I'm not sure how to easily + # calculate that so pick something loose like one day instead. + - name: Prune ccache objects + run: ccache --evict-older-than 1d + + # Help debug ccache issues by showing what happened. + - if: always() + name: Show ccache statistics + run: ccache --show-stats + + # Always save a cache, even if the build failed. This ensures that if + # live-debugging via CI the build gets to pick up where it left off last + # time instead of having to recreate everything each time a failure + # happens. + - if: always() && steps.cache-restore.outputs.cache-hit != 'true' + uses: actions/cache/save@v5 + with: + path: ${{ runner.tool_cache }}/ccache + key: 0-cache-${{ matrix.artifact }}-${{ github.run_id }} + + build-only-sysroot: + name: Build only sysroot - ${{ matrix.name }} + runs-on: ubuntu-24.04 + strategy: + matrix: + include: + - name: default + - name: exceptions + defines: -DWASI_SDK_EXCEPTIONS=ON + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - uses: ./.github/actions/checkout + - uses: ./.github/actions/install-deps + - run: cargo install wasm-component-ld@0.5.21 + - run: sudo apt-get update -y && sudo apt-get install -y clang-20 lld-20 + - run: | + cmake -G Ninja -B build -S . \ + -DCMAKE_C_COMPILER=/usr/lib/llvm-20/bin/clang \ + -DCMAKE_SYSTEM_NAME=WASI \ + -DWASI_SDK_INCLUDE_TESTS=ON \ + -DWASI_SDK_CPU_CFLAGS="" \ + -DCMAKE_C_LINKER_DEPFILE_SUPPORTED=OFF \ + -DCMAKE_CXX_LINKER_DEPFILE_SUPPORTED=OFF \ + ${{ matrix.defines }} + - run: ninja -C build + - run: ctest --output-on-failure --parallel 10 --test-dir build/tests + + # Once all of the above matrix entries have completed this job will run and + # assemble the final `wasi-sdk-*` artifacts by fusing the toolchain/sysroot + # artifacts. + finalize: + name: Finalize wasi-sdk artifacts + needs: build + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - uses: ./.github/actions/checkout + + # Download all artifacts from all platforms in `build`, merge them into + # final wasi-sdk-* artifacts, and then upload them. + - uses: actions/download-artifact@v8 + - run: ./ci/merge-artifacts.sh + - uses: actions/upload-artifact@v6 + with: + name: release-artifacts + path: dist + + # Use the `wasi-sdk-*` artifacts just created to create a docker image + # with a toolchain pre-installed. + - uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: docker/setup-qemu-action@v2 + - uses: docker/setup-buildx-action@v2 + - uses: docker/metadata-action@v4 + id: meta + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=schedule + type=ref,event=branch + type=ref,event=tag + type=ref,event=pr + type=sha + - name: Build and push wasi-sdk docker image + uses: docker/build-push-action@v3 + with: + context: . + file: docker/Dockerfile + push: ${{ github.event_name != 'pull_request' && github.event_name != 'workflow_dispatch' }} + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Publish a draft release + if: startsWith(github.ref, 'refs/tags') + run: gh release create --draft --prerelease --generate-notes ${{ github.ref_name }} ./dist/* + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Test the final artifacts as-is without passing `--sysroot` or + # `-resource-dir` or any extra flags. This exercises running the compiler + # as-is from the distribution tarballs and ensuring that it can build and pass + # all tests. + test-standalone: + name: Test standalone toolchain + needs: build + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - uses: ./.github/actions/checkout + - uses: ./.github/actions/install-deps + - uses: actions/download-artifact@v8 + with: + name: dist-x86_64-linux + path: dist-x86_64-linux + - run: ./ci/merge-artifacts.sh + - run: tar xf dist/wasi-sdk-*.tar.gz + - run: | + cmake -G Ninja -B build -S . \ + -DWASI_SDK_INCLUDE_TESTS=ON \ + -DWASI_SDK_TEST_HOST_TOOLCHAIN=ON \ + -DCMAKE_TOOLCHAIN_FILE=$(ls ./wasi-sdk-*/share/cmake/wasi-sdk.cmake) + - run: ninja -C build build-tests + - run: ctest --output-on-failure --parallel 10 --test-dir build/tests diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9d0b71a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +dist diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..0691635 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "src/llvm-project"] + path = src/llvm-project + url = https://github.com/llvm/llvm-project +[submodule "src/wasi-libc"] + path = src/wasi-libc + url = https://github.com/WebAssembly/wasi-libc +[submodule "src/config"] + path = src/config + url = https://git.savannah.gnu.org/git/config.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a0d3fca --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,40 @@ +# Build logic for building both a toolchain and a sysroot for WASI. +# +# This top level `CMakeLists.txt` file can be used either to build a clang +# toolchain or a WASI sysroot. Note that this can't be done at the same time. +# A toolchain build requires a compiler for the target architecture. A +# WASI sysroot build requires this previous compiler and must be runnable on +# the host. + +cmake_minimum_required(VERSION 3.26) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +project(wasi-sdk) +include(ExternalProject) + +set(WASI_SDK_TARGETS "wasm32-wasi;wasm32-wasip1;wasm32-wasip2;wasm32-wasip1-threads;wasm32-wasi-threads" + CACHE STRING "List of WASI targets to build") +option(WASI_SDK_BUILD_TOOLCHAIN "Build a toolchain instead of the sysroot" OFF) + +set(llvm_proj_dir ${CMAKE_CURRENT_SOURCE_DIR}/src/llvm-project) +set(wasi_libc ${CMAKE_CURRENT_SOURCE_DIR}/src/wasi-libc) + +include(wasi-sdk-enable-ccache) + +find_program(PYTHON python3 python REQUIRED) + +# Set some variables based on the `version.py` script +set(version_script ${CMAKE_CURRENT_SOURCE_DIR}/version.py) +execute_process( + COMMAND ${PYTHON} ${version_script} + OUTPUT_VARIABLE wasi_sdk_version + OUTPUT_STRIP_TRAILING_WHITESPACE) + +message(STATUS "wasi-sdk version is ${wasi_sdk_version}") + +# Only include one version of the build logic as pulling in both isn't +# supported at this time. +if(WASI_SDK_BUILD_TOOLCHAIN) +include(wasi-sdk-toolchain) +else() +include(wasi-sdk-sysroot) +endif() diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..38ef2ef --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at either sunfish@mozilla.com or tyler@fastly.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CppExceptions.md b/CppExceptions.md new file mode 100644 index 0000000..7ea1e2d --- /dev/null +++ b/CppExceptions.md @@ -0,0 +1,60 @@ +# Support for C++ Exceptions + +The released artifacts for wasi-sdk at this time do not support C++ exceptions. +LLVM and Clang, however, have support for C++ exceptions in WebAssembly and this +is intended to serve as documentation of the current state of affairs of using +C++ exceptions. It should be noted though that the current status of C++ +exceptions support is not intended to be the final state of support, and this is +all continuing to be iterated on over time. + +## Building wasi-sdk with exceptions + +When building the sysroot with wasi-sdk you can pass `-DWASI_SDK_EXCEPTIONS=ON` +to enable support for C++ exceptions. For example: + +```shell script +$ cmake -G Ninja -B build/sysroot -S . \ + -DCMAKE_TOOLCHAIN_FILE=$path/to/wasi-sdk-p1.cmake \ + -DWASI_SDK_EXCEPTIONS=ON +``` + +The C++ standard library will be compiled with support for exceptions for the +desired targets and the resulting sysroot supports using exceptions. + +## Compiling code with C++ exceptions + +Currently extra compilation flags are required to fully support C++ exceptions. +Without these flags programs using C++ exceptions will not work correctly: + +* `-fwasm-exceptions` - needed to enable the WebAssembly exception-handling + proposal. +* `-mllvm -wasm-use-legacy-eh=false` - indicates that the standard WebAssembly + exception-handling instructions should be used. +* `-lunwind` - links in support for unwinding which C++ exceptions requires. + +This can be specified for example with: + +```shell script +$ export CFLAGS="-fwasm-exceptions -mllvm -wasm-use-legacy-eh=false" +$ export LDFLAGS="-lunwind" +``` + +## Limitations + +Currently C++ exceptions support in wasi-sdk does not support shared libraries. +Fixing this will require resolving some miscellaneous build issues in this +repository itself. + +## Future Plans + +There are a few tracking issues with historical discussion about C++ exceptions +support in wasi-sdk such as [#334](https://github.com/WebAssembly/wasi-sdk/issues/334) +and [#565](https://github.com/WebAssembly/wasi-sdk/issues/565). The major +remaining items are: + +* Figure out support for shared libraries. +* Determine how to ship a sysroot that supports both with-and-without + exceptions. +* Figure out how to avoid the need for extra compiler flags when using + exceptions. +* Figure out if a new wasm target is warranted. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f9d8195 --- /dev/null +++ b/LICENSE @@ -0,0 +1,220 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +--- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..7d28f69 --- /dev/null +++ b/README.md @@ -0,0 +1,238 @@ +# WASI SDK + +## Quick Start + +[Download SDK packages here.][releases] + +[releases]: https://github.com/WebAssembly/wasi-sdk/releases + +## About this repository + +This repository contains no compiler or library code itself; it uses +git submodules to pull in the upstream Clang and LLVM tree, as well as the +wasi-libc tree. + +The libc portion of this SDK is maintained in [wasi-libc]. + +[wasi-libc]: https://github.com/WebAssembly/wasi-libc + +Upstream Clang and LLVM (from 9.0 onwards) can compile for WASI out of the box, +and WebAssembly support is included in them by default. So, all that's done here +is to provide builds configured to set the default target and sysroot for +convenience. + +One could also use a standard Clang installation, build a sysroot from the +sources mentioned above, and compile with `--target=wasm32-wasi +--sysroot=/path/to/sysroot`. In this scenario, one would also need the +`libclang_rt.*.a` objects available separately in the [release +downloads][releases] which must be extracted into +`$CLANG_INSTALL_DIR/$CLANG_VERSION/lib/`. + +## Clone + +This repository uses git submodule, to clone it you need use the command below : + +```shell script +git clone --recursive https://github.com/WebAssembly/wasi-sdk.git +``` + +## Requirements + +The Wasm-sdk's build process needs some packages : + +* `cmake` +* `clang` +* `ninja` +* `python3` +* `cargo` + +Please refer to your OS documentation to install those packages. + +## Build + +Building `wasi-sdk` uses CMake and is split into two halves. First you can build +the toolchain itself: + +```shell script +cmake -G Ninja -B build/toolchain -S . -DWASI_SDK_BUILD_TOOLCHAIN=ON -DCMAKE_INSTALL_PREFIX=build/install +cmake --build build/toolchain --target install +``` + +When you're developing locally you may also wish to pass +`-DCMAKE_CXX_COMPILER_LAUNCHER=ccache` to assist with rebuilds. Other supported +CMake flags are: + +* `-DLLVM_CMAKE_FLAGS` - extra flags to pass to `cmake` when building + LLVM/Clang. +* `-DRUST_TARGET` - the specific Rust target triple to build `wasm-component-ld` + for, useful for cross-compiles. + +The `clang` compiler should now be located at `build/install/bin/clang` but it's +just a compiler, the sysroot isn't built yet. Next the second step of the build +is to build the sysroot: + +```shell script +cmake -G Ninja -B build/sysroot -S . \ + -DCMAKE_INSTALL_PREFIX=build/install \ + -DCMAKE_TOOLCHAIN_FILE=build/install/share/cmake/wasi-sdk.cmake \ + -DCMAKE_C_COMPILER_WORKS=ON \ + -DCMAKE_CXX_COMPILER_WORKS=ON +cmake --build build/sysroot --target install +``` + +A full toolchain should now be present at `build/install` and is ready for use +in compiling WebAssembly code. Supported CMake flags are: + +* `-DWASI_SDK_DEBUG_PREFIX_MAKE=OFF` - disable `-fdebug-prefix-map` when + building C/C++ code to use full host paths instead. +* `-DWASI_SDK_INCLUDE_TESTS=ON` - used for building tests. +* `-DWASI_SDK_CPU_CFLAGS=..` - used to specify CFLAGS to tweak wasm features + to enable/disable. The default is `-mcpu=lime1`. +* `-DWASI_SDK_LTO=ON` - whether to enable/disable builds of LTO-capable + libraries as part of the build. +* `-DWASI_SDK_EXCEPTIONS=ON` - whether to enable/disable support for C++ + exceptions, see [CppExceptions.md](./CppExceptions.md) for more information. +* `-DWASI_SDK_TEST_HOST_TOOLCHAIN=ON` - test the host toolchain's wasi-libc and + sysroot libraries, don't build or use fresh libraries for tests. +* `-DWASI_SDK_TARGETS=..` - a list of targets to build, by default all WASI + targets are compiled. +* `-DWASI_SDK_INSTALL_TO_CLANG_RESOURCE_DIR=ON` - install compiler-rt + to the compiler's resource directory. might be convenient if you want to + use the toolchain (eg. `./build/install/bin/clang`) in-place. + +If you'd like to build distribution artifacts you can use the `dist` target like +so: + +```shell script +cmake --build build/toolchain --target dist +cmake --build build/sysroot --target dist +``` + +Tarballs will be created under `build/toolchain/dist` and `build/sysroot/dist`. +Note that these are separate tarballs for the toolchain and sysroot. To create a +single tarball for the entire SDK you'll first want to copy all tarballs into a +new folder and then run the `./ci/merge-artifacts.sh` script: + +```shell script +mkdir dist-my-platform +cp build/toolchain/dist/* build/sysroot/dist/* dist-my-platform +./ci/merge-artifacts.sh +``` + +This will produce `dist/wasi-sdk-*.tar.gz` which is the same as the release +artifacts for this repository. + +Finally you can additionally bundle many of the above steps, minus +`merge-artifact.sh` by using the CI script to perform both the toolchain and +sysroot build: + +```shell script +./ci/build.sh +``` + +The built package can be found into `build/dist` directory. +For releasing a new version of the package on GitHub, +see [RELEASING.md](RELEASING.md). + +## Install + +A typical installation from the release binaries might look like the following: + +```shell script +WASI_OS=linux +WASI_ARCH=x86_64 # or 'arm64' if running on arm64 host +WASI_VERSION=27 +WASI_VERSION_FULL=${WASI_VERSION}.0 +wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_VERSION}/wasi-sdk-${WASI_VERSION_FULL}-${WASI_ARCH}-${WASI_OS}.tar.gz +tar xvf wasi-sdk-${WASI_VERSION_FULL}-${WASI_ARCH}-${WASI_OS}.tar.gz +``` + +## Use + +Use the clang installed in the `wasi-sdk` directory: + +```shell script +WASI_SDK_PATH=`pwd`/wasi-sdk-${WASI_VERSION_FULL}-${WASI_ARCH}-${WASI_OS} +CC="${WASI_SDK_PATH}/bin/clang --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot" +$CC foo.c -o foo.wasm +``` + +Note: `${WASI_SDK_PATH}/share/wasi-sysroot` contains the WASI-specific +includes/libraries/etc. The `--sysroot=...` option is not necessary if +`WASI_SDK_PATH` is `/opt/wasi-sdk`. For troubleshooting, one can replace the +`--sysroot` path with a manual build of [wasi-libc]. + +### Integrating with a CMake build system + +Use a toolchain file to setup the *wasi-sdk* platform. + +``` +$ cmake -DCMAKE_TOOLCHAIN_FILE=${WASI_SDK_PATH}/share/cmake/wasi-sdk.cmake ... +``` + +or the *wasi-sdk-thread* platform + +``` +$ cmake -DCMAKE_TOOLCHAIN_FILE=${WASI_SDK_PATH}/share/cmake/wasi-sdk-pthread.cmake ... +``` + +## Notes for Autoconf + +[Autoconf] 2.70 now [recognizes WASI]. + +[Autoconf]: https://www.gnu.org/software/autoconf/autoconf.html +[recognizes WASI]: https://git.savannah.gnu.org/gitweb/?p=autoconf.git;a=blob;f=build-aux/config.sub;h=19c9553b1825cafb182115513bc628e0ee801bd0;hb=97fbc5c184acc6fa591ad094eae86917f03459fa#l1723 + +For convenience when building packages that aren't yet updated, updated +config.sub and config.guess files are installed at `share/misc/config.*` +in the install directory. + +## Docker Image + +We provide a [docker image] including WASI SDK that can be used for building +projects without a separate installation of the SDK. Autotools, CMake, and Ninja +are included in this image, and standard environment variables are set to use +WASI SDK for building. + +[docker image]: https://github.com/WebAssembly/wasi-sdk/pkgs/container/wasi-sdk + +For example, this command can build a make-based project with the Docker +image. + +``` +docker run -v `pwd`:/src -w /src ghcr.io/webassembly/wasi-sdk make +``` + +Take note of the [notable limitations](#notable-limitations) below when +building projects, for example many projects will need threads support +disabled in a configure step before building with WASI SDK. + +## Notable Limitations + +* C++ exceptions are disabled by default. For more information see + [CppExceptions.md]. +* C `setjmp`/`longjmp` require some extra configuration to get working, see + [SetjmpLongjmp.md]. +* Most targets do not support spawning a thread. Experimental support for + spawning threads is available with the `wasm32-wasip1-threads` target which + uses [wasi-threads]. Note that the `pthread_*` family of functions, as well as + C++ threading primitives such as ``, ``, and `` are + available on all targets. Defining a macro `_WASI_STRICT_PTHREAD` will make + `pthread_create`, `pthread_detach`, `pthread_join`, `pthread_tryjoin_np`, and + `pthread_timedjoin_np` fail with a compile time error when building for + single-threaded targets. +* Dynamic linking [is supported][dylink] but not as fully baked as static + linking. There might be obscure bugs in some situations related to dynamic + linking. +* The WASIp1 targets do not support networking, but WASIp2/WASIp3 support + networking. +* 64-bit linear memories (a "wasm64" target) are not supported at this time. + Supporting this will require resolving [WebAssembly/component-model#22] first + at which point it will be possible to add a `wasm64-wasip2` target. There are + no plans to add support for `wasm64-wasi{,-threads,p1,p1-threads}` at this + time. + +[threads]: https://github.com/WebAssembly/threads +[wasi-threads]: https://github.com/WebAssembly/wasi-threads +[dylink]: https://github.com/WebAssembly/tool-conventions/blob/master/DynamicLinking.md +[WebAssembly/component-model#22]: https://github.com/WebAssembly/component-model/issues/22 diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..72989dd --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,43 @@ +# Release Process + +We (maintainers) plan to release a new version of wasi-sdk every three months, +coinciding with our monthly meeting to discuss latest issues and pull requests. +This provides regularity to the release cadence, though we also reserve the +right to publish at any intervening time if there is a pressing need (i.e., open +an issue to discuss). + +To publish a new version of `wasi-sdk` as a GitHub release: + +1. Tag a commit with an annotated tag. Note that this must be an annotated tag, + not a lightweight tag, so that `version.py` can use it for calculating the + package version (use `git show wasi-sdk-...` to show other tag messages). + Note that you may need to clear the repository cache to avoid problems with + cached artifacts [^cache]. + + ```shell script + TAG=wasi-sdk-1 + git tag -a $TAG + git push origin $TAG + ``` + +2. Wait for the CI build of the tag to finish. This will automatically publish + a draft pre-release to [GitHub Releases](https://github.com/WebAssembly/wasi-sdk/releases). + Release notes are auto-generated and should be reviewed for accuracy. Once + everything looks good manually publish the release through the GitHub UI. + +3. Remember to tag the wasi-libc repository with the new `$TAG` version. + + ```shell script + git submodule status -- src/wasi-libc # grab $WASI_LIBC_COMMIT from the output + cd $WASI_LIBC_REPO_DIR + git tag $TAG $WASI_LIBC_COMMIT + git push origin $TAG + ``` + +[^cache]: Here is an example of how to clear a cache with the GitHub CLI: + + ```shell script + URL=/repos/WebAssembly/wasi-sdk/actions/caches + gh api $URL -q '.actions_caches[].id' \ + | xargs -I {} gh api --method DELETE $URL/{} + ``` diff --git a/SetjmpLongjmp.md b/SetjmpLongjmp.md new file mode 100644 index 0000000..364a061 --- /dev/null +++ b/SetjmpLongjmp.md @@ -0,0 +1,117 @@ +# C setjmp/longjmp support + +WASI-SDK provides basic setjmp/longjmp support. + +Note that it's still under active development and may change in +future versions. The tl;dr; version of this document is to pass these flags to +the C compiler: + +``` +-mllvm -wasm-enable-sjlj -lsetjmp -mllvm -wasm-use-legacy-eh=false +``` + +## Implementation Primitives + +Support for `setjmp` and `longjmp` is built on top of the +[exception-handling](https://github.com/WebAssembly/exception-handling) +WebAssembly proposal. This proposal is now [phase +5](https://github.com/WebAssembly/proposals) and becoming part of the official +specification. Note, however, that the exception-handling proposal has a long +history and has a "legacy" version which shipped in browsers as well. This means +that there are two different, but similar, sets of instructions that can be +emitted to support `setjmp` and `longjmp`. Clang 20 and later (wasi-sdk-26 and +later) is capable of emitting both at this time via `-mllvm +-wasm-use-legacy-eh={false,true}` compiler flags. + +Another important point is that exception-handling only provides structured +control flow primitives for exceptions. This means it is not possible to purely +define `setjmp` in C as otherwise it must be a function that returns twice. This +means that support for `setjmp` and `longjmp` in WebAssembly relies on a +compiler pass to transform invocations of `setjmp` at a compiler IR level. This +means that the `setjmp` symbol is not defined in wasi-libc, for example, but +instead primitives used to implement `setjmp`, in conjunction with LLVM, are +found in wasi-libc. + +## Build an application + +To build an application using setjmp/longjmp, you need three sets of compiler +flags: + +1. `-mllvm -wasm-enable-sjlj`: Enable LLVM compiler pass which replaces calls to + `setjmp` and `longjmp` with a different implementation that wasi-libc + implements and hooks into. +2. `-lsetjmp`: Link the setjmp library that wasi-libc provides which contains + these hooks that LLVM uses. +2. `-mllvm -wasm-use-legacy-eh=false`: Specify which version of the + exception-handling instructions will be emitted. Note that if this is omitted + it currently defaults to `true` meaning that the legacy instructions are + emitted, not the standard instructions. + +In short, these flags are required to use `setjmp`/`longjmp` + +``` +-mllvm -wasm-enable-sjlj -lsetjmp -mllvm -wasm-use-legacy-eh=false +``` + +### Examples + +This source code: + +```c +#include +#include +#include +#include + +static jmp_buf env; + +static bool test_if_longjmp(void(*f)(void)) { + if (setjmp(env)) + return true; + f(); + return false; +} + +static void do_not_longjmp() { +} + +static void do_longjmp() { + longjmp(env, 1); +} + +int main() { + bool longjmped = test_if_longjmp(do_not_longjmp); + assert(!longjmped); + longjmped = test_if_longjmp(do_longjmp); + assert(longjmped); + return 0; +} +``` + +can be compiled using the standard set of instructions as: + +```shell +clang -Os -o test.wasm test.c \ + -mllvm -wasm-enable-sjlj -lsetjmp -mllvm -wasm-use-legacy-eh=false +``` + +and then `test.wasm` can be executed in a WebAssembly runtime supporting WASI. + +You can also compile for the legacy exceptions proposal with: + +```shell +clang -Os -o test.wasm test.c \ + -mllvm -wasm-enable-sjlj -lsetjmp -mllvm -wasm-use-legacy-eh=true +``` + +and then `test.wasm` can be executed in a WebAssembly runtime supporting the +legacy WebAssembly instructions. + +Note that when compiling with LTO you'll need to pass `-mllvm` flags to the +linker in addition to Clang itself, such as: + +```shell +clang -Os -flto=full -o test.wasm test.c \ + -mllvm -wasm-enable-sjlj -lsetjmp -mllvm -wasm-use-legacy-eh=false \ + -Wl,-mllvm,-wasm-enable-sjlj,-mllvm,-wasm-use-legacy-eh=false +``` diff --git a/ci/build.sh b/ci/build.sh new file mode 100755 index 0000000..7525aa4 --- /dev/null +++ b/ci/build.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +# Build logic executed in CI. This is intentionally kept relatively minimal to +# one day not live in bash to have a bash-less build on Windows. For now though +# this will unconditionally build a toolchain and then optionally build a +# sysroot. Builders which can't actually execute the toolchain they produce +# skip the sysroot step below. + +set -ex + +# Optionally allow the first argument to this script to be the install +# location. +if [ "$1" = "" ]; then + build_dir=`pwd`/build +else + build_dir="$1" +fi + +cmake -G Ninja -B $build_dir/toolchain -S . \ + -DWASI_SDK_BUILD_TOOLCHAIN=ON \ + -DCMAKE_BUILD_TYPE=MinSizeRel \ + "-DCMAKE_INSTALL_PREFIX=$build_dir/install" \ + $WASI_SDK_CI_TOOLCHAIN_CMAKE_ARGS \ + "-DLLVM_CMAKE_FLAGS=$WASI_SDK_CI_TOOLCHAIN_LLVM_CMAKE_ARGS" +ninja -C $build_dir/toolchain install dist -v + +mv $build_dir/toolchain/dist $build_dir/dist + +if [ "$WASI_SDK_CI_SKIP_SYSROOT" = "1" ]; then + exit 0 +fi + +# Use the just-built toolchain and its `CMAKE_TOOLCHAIN_FILE` to build a +# sysroot. +cmake -G Ninja -B $build_dir/sysroot -S . \ + "-DCMAKE_TOOLCHAIN_FILE=$build_dir/install/share/cmake/wasi-sdk.cmake" \ + -DCMAKE_C_COMPILER_WORKS=ON \ + -DCMAKE_CXX_COMPILER_WORKS=ON \ + -DWASI_SDK_INCLUDE_TESTS=ON \ + "-DCMAKE_INSTALL_PREFIX=$build_dir/install" +ninja -C $build_dir/sysroot install dist -v + +mv $build_dir/sysroot/dist/* $build_dir/dist + +if [ "$WASI_SDK_CI_SKIP_TESTS" = "1" ]; then + exit 0 +fi + +# Run tests to ensure that the sysroot works. +ctest --output-on-failure --parallel 10 --test-dir $build_dir/sysroot/tests \ + --timeout 60 diff --git a/ci/docker-build.sh b/ci/docker-build.sh new file mode 100755 index 0000000..e04fd51 --- /dev/null +++ b/ci/docker-build.sh @@ -0,0 +1,63 @@ +#!/bin/sh + +# This is a helper script invoked from CI which will execute the `ci/build.sh` +# script within a docker container. This contain is built using the Dockerfile located at `ci/docker/Dockerfile` +# This container is then used to execute `ci/build.sh`. + +set -e + +if [ "$1" = "" ]; then + echo "Usage: $0 " + echo "" + echo "example: $0 x86_64-linux" + exit 1 +fi + +set -x + +# Build the Docker image. Use an artifact-specific Dockerfile if one exists +# (e.g. ci/docker/Dockerfile.riscv64-linux), otherwise use the default. +dockerfile=ci/docker/Dockerfile +if [ -f "ci/docker/Dockerfile.$1" ]; then + dockerfile="ci/docker/Dockerfile.$1" +fi +docker build --tag wasi-sdk-builder --file $dockerfile ci/docker + +# Perform the build in `/src`. The current directory is mounted read-write at +# this location as well. To ensure that container-created files are reasonable +# on the host as well the `--user` is passed to configure various permissions. +args="--workdir /src --volume `pwd`:/src:Z" +args="$args --user $(id -u):$(id -g)" + +# Persist the ccache directory on the host to ensure repeated runs/debugging +# of this container don't take forever. Also enables caching in CI. +ccache_dir=$CCACHE_DIR +if [ "$ccache_dir" = "" ]; then + ccache_dir=$HOME/.ccache +fi +args="$args --volume $ccache_dir:/ccache:Z --env CCACHE_DIR=/ccache" + +# Inherit some tools from the host into this container. This ensures that the +# decision made on CI of what versions to use is the canonical source of truth +# for these tools. +args="$args --volume `rustc --print sysroot`:/rustc:ro" +# Only mount wasmtime when the sysroot build (and its tests) will run. +# When WASI_SDK_CI_SKIP_SYSROOT is set wasmtime is not needed. +if [ -z "$WASI_SDK_CI_SKIP_SYSROOT" ]; then + args="$args --volume $(dirname $(which wasmtime)):/wasmtime:ro" +fi + +# Pass through some env vars that `build.sh` reads +args="$args --env WASI_SDK_CI_TOOLCHAIN_CMAKE_ARGS" +args="$args --env WASI_SDK_CI_TOOLCHAIN_LLVM_CMAKE_ARGS" +args="$args --env WASI_SDK_CI_SKIP_SYSROOT" +args="$args --env WASI_SDK_CI_SKIP_TESTS" + +# Before running `ci/build.sh` set up some rust/PATH related info to use what +# was just mounted above, and then execute the build. +docker run \ + $args \ + --tty \ + --init \ + wasi-sdk-builder \ + bash -c 'CARGO_HOME=/tmp/cargo-home PATH=$PATH:/rustc/bin:/wasmtime exec ci/build.sh' diff --git a/ci/docker/Dockerfile b/ci/docker/Dockerfile new file mode 100644 index 0000000..71f4a1d --- /dev/null +++ b/ci/docker/Dockerfile @@ -0,0 +1,33 @@ +# Use a relatively old/stable distro here to maximize the supported platforms +# and avoid depending on more recent version of, say, libc. +# Here we choose AlmaLinux 8 + +FROM almalinux:8 + +# Various build tooling and such necessary to build LLVM and a wasi-sysroot +RUN dnf install -y \ + curl \ + ca-certificates \ + clang \ + python3 \ + git \ + unzip \ + cmake \ + ncurses-devel + +COPY ./install-ccache.sh . +RUN ./install-ccache.sh + +ENV PATH /opt/ccache/bin:$PATH + +# AlmaLinux 8 doesn't seem to have ninja, so install it manually. +RUN ARCH=$(uname -m) \ + && if [ "$ARCH" = "aarch64" ]; then SUFFIX=-aarch64; fi \ + && curl -sSL -o ninja.zip https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-linux${SUFFIX}.zip \ + && unzip ninja.zip \ + && rm *.zip \ + && mv ninja /opt/ccache/bin + +# Tell programs to cache in a location that both isn't a `--volume` mounted root +# and isn't `/root` in the container as that won't be writable during the build. +ENV XDG_CACHE_HOME /tmp/cache diff --git a/ci/docker/Dockerfile.riscv64-linux b/ci/docker/Dockerfile.riscv64-linux new file mode 100644 index 0000000..18acaa0 --- /dev/null +++ b/ci/docker/Dockerfile.riscv64-linux @@ -0,0 +1,20 @@ +# Ubuntu 24.04 is used here (rather than AlmaLinux 8) because AlmaLinux 8 +# does not publish riscv64 images. This runs natively on a riscv64 runner. +FROM ubuntu:24.04 + +RUN apt-get update && apt-get install -y \ + curl \ + ca-certificates \ + build-essential \ + clang \ + lld \ + python3 \ + git \ + unzip \ + cmake \ + ninja-build \ + ccache + +# Tell programs to cache in a location that both isn't a `--volume` mounted root +# and isn't `/root` in the container as that won't be writable during the build. +ENV XDG_CACHE_HOME /tmp/cache diff --git a/ci/docker/README.md b/ci/docker/README.md new file mode 100644 index 0000000..09ed7bc --- /dev/null +++ b/ci/docker/README.md @@ -0,0 +1,10 @@ +# About + +This folder contains the docker images that are used in CI to build the wasi-sdk +release toolchains. Docker is used to intentionally use older Linux +distributions to build the toolchain to have a more maximal set of glibc +compatibility. + +These images are intended to be used on an x86\_64 host. Images start from the +`Dockerfile.common` file and then layer on target-specific +toolchains/options/etc as necessary. diff --git a/ci/docker/install-ccache.sh b/ci/docker/install-ccache.sh new file mode 100755 index 0000000..e70fb64 --- /dev/null +++ b/ci/docker/install-ccache.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# AlmaLinux 8, the container this script runs in, does not have ccache in its +# package repositories. The ccache project publishes both x86_64 and aarch64 +# binaries, however. The x86_64 binaries for ccache are themselves built in +# AlmaLinux 8 so they're compatible, but the aarch64 binaries are built in a +# newer container and don't run on AlmaLinux 8. +# +# Thus this script downloads precompiled binaries for x86_64 but builds from +# source on aarch64. + +ARCH=$(uname -m) +ver=4.12.1 + +if [ "x$ARCH" = "x86_64" ]; then + curl -sSLO https://github.com/ccache/ccache/releases/download/v${ver}/ccache-${ver}-linux-${ARCH}.tar.xz + tar -xf ccache-${ver}-linux-${ARCH}.tar.xz + rm ccache-${ver}-linux-${ARCH}.tar.xz + mv ccache-${ver}-linux-${ARCH} /opt/ccache/bin +else + curl -sSLO https://github.com/ccache/ccache/releases/download/v${ver}/ccache-${ver}.tar.xz + tar -xf ccache-${ver}.tar.xz + + cd ccache-${ver} + mkdir build + cd build + cmake .. \ + -DCMAKE_INSTALL_PREFIX=/opt/ccache \ + -DCMAKE_BUILD_TYPE=Release \ + -DHTTP_STORAGE_BACKEND=OFF \ + -DENABLE_TESTING=OFF \ + -DREDIS_STORAGE_BACKEND=OFF + make -j$(nproc) + make install +fi diff --git a/ci/merge-artifacts.sh b/ci/merge-artifacts.sh new file mode 100755 index 0000000..e64b68e --- /dev/null +++ b/ci/merge-artifacts.sh @@ -0,0 +1,83 @@ +#!/bin/sh + +# Helper script executed on CI once all builds have completed. This takes +# `wasi-toolchain-*` artifacts and `wasi-sysroot-*` artifacts and merges +# them together into a single `wasi-sdk-*` artifact. Toolchains which don't +# have a sysroot that they themselves built use a sysroot from the x86_64-linux +# toolchain. + +set -ex + +rm -rf dist +mkdir dist +version=$(./version.py) + +make_deb() { + build=$1 + dir=$2 + + if ! command -v dpkg-deb >/dev/null; then + return + fi + + case $build in + dist-x86_64-linux) deb_arch=amd64 ;; + dist-arm64-linux) deb_arch=arm64 ;; + *) + echo "unknown build $build" + exit 1 + esac + + mkdir dist/pkg + mkdir dist/pkg/opt + mkdir dist/pkg/DEBIAN + sed s/VERSION/$version/ wasi-sdk.control | \ + sed s/ARCH/$deb_arch/ > dist/pkg/DEBIAN/control + cp -R $dir dist/pkg/opt/wasi-sdk + deb_name=$(echo $(basename $dir) | sed 's/.tar.gz//') + (cd dist && dpkg-deb -b pkg $deb_name.deb) + rm -rf dist/pkg +} + +for build in dist-*; do + toolchain=`ls $build/wasi-toolchain-*` + if [ -f $build/wasi-sysroot-* ]; then + sysroot=`ls $build/wasi-sysroot-*` + else + sysroot=`ls dist-x86_64-linux/wasi-sysroot-*` + fi + if [ -f $build/libclang_rt* ]; then + compiler_rt=`ls $build/libclang_rt*` + else + compiler_rt=`ls dist-x86_64-linux/libclang_rt*` + fi + + sdk_dir=`basename $toolchain | sed 's/.tar.gz//' | sed s/toolchain/sdk/` + mkdir dist/$sdk_dir + + # Start with the toolchain and then overlay the sysroot into + # `share/wasi-sysroot`, the default sysroot. + tar xf $toolchain -C dist/$sdk_dir --strip-components 1 + mkdir -p dist/$sdk_dir/share/wasi-sysroot + tar xf $sysroot -C dist/$sdk_dir/share/wasi-sysroot --strip-components 1 + mv dist/$sdk_dir/share/wasi-sysroot/VERSION dist/$sdk_dir + + # Setup the compiler-rt library for all targets. + rtlibdir=$(dirname $(find dist/$sdk_dir/lib -name include))/lib + mkdir -p $rtlibdir + tar xf $compiler_rt -C $rtlibdir --strip-components 1 + + tar czf dist/$sdk_dir.tar.gz -C dist $sdk_dir + + if echo $build | grep -q linux; then + make_deb $build dist/$sdk_dir + fi + rm -rf dist/$sdk_dir +done + +# In addition to `wasi-sdk-*` also preserve artifacts for just the sysroot +# and just compiler-rt. +if [ -d dist-x86_64-linux ]; then + cp dist-x86_64-linux/wasi-sysroot-* dist + cp dist-x86_64-linux/libclang_rt* dist +fi diff --git a/clang.cfg b/clang.cfg new file mode 100644 index 0000000..38ddf54 --- /dev/null +++ b/clang.cfg @@ -0,0 +1 @@ +--sysroot=/../share/wasi-sysroot diff --git a/cmake/Platform/WASI.cmake b/cmake/Platform/WASI.cmake new file mode 100644 index 0000000..b49713f --- /dev/null +++ b/cmake/Platform/WASI.cmake @@ -0,0 +1 @@ +set(WASI 1) diff --git a/cmake/wasi-sdk-dist.cmake b/cmake/wasi-sdk-dist.cmake new file mode 100644 index 0000000..8a9b2b3 --- /dev/null +++ b/cmake/wasi-sdk-dist.cmake @@ -0,0 +1,37 @@ +# Helper function to create tarballs for wasi-sdk. +# +# The `target` is the name of the CMake target to create for the creation of +# this tarball. The `tarball` argument is where the final tarball will be +# located. The name of the tarball is also used for the name of the root folder +# in the tarball. The `dir` argument is is the directory that will get packaged +# up within the tarball. +function(wasi_sdk_add_tarball target tarball dir) + cmake_path(GET tarball PARENT_PATH tarball_dir) + + # Run STEM twice to chop of both `.gz` and `.tar` in `.tar.gz` + cmake_path(GET tarball STEM LAST_ONLY tarball_stem) + cmake_path(GET tarball_stem STEM LAST_ONLY tarball_stem) + + if(CMAKE_SYSTEM_NAME MATCHES Windows) + # Copy the contents of symlinks on Windows to avoid dealing with symlink + set(copy_dir ${CMAKE_COMMAND} -E copy_directory ${dir} ${tarball_stem}) + else() + # ... but on non-Windows copy symlinks themselves to cut down on + # distribution size. + set(copy_dir cp -R ${dir} ${tarball_stem}) + endif() + + add_custom_command( + OUTPUT ${tarball} + # First copy the directory under a different name, the filestem of the + # tarball. + COMMAND ${copy_dir} + # Next use CMake to create the tarball itself + COMMAND ${CMAKE_COMMAND} -E tar cfz ${tarball} ${tarball_stem} + # Finally delete the temporary directory created above. + COMMAND ${CMAKE_COMMAND} -E rm -rf ${tarball_stem} + WORKING_DIRECTORY ${tarball_dir} + COMMENT "Creating ${tarball}..." + ) + add_custom_target(${target} DEPENDS ${tarball}) +endfunction() diff --git a/cmake/wasi-sdk-enable-ccache.cmake b/cmake/wasi-sdk-enable-ccache.cmake new file mode 100644 index 0000000..f5e1f30 --- /dev/null +++ b/cmake/wasi-sdk-enable-ccache.cmake @@ -0,0 +1,17 @@ +# Helper module to auto-enable ccache if detected. + +find_program(CCACHE ccache) + +option(WASI_SDK_DISABLE_CCACHE "Force disable ccache even if it's found" OFF) + +if(NOT CMAKE_C_COMPILER_LAUNCHER) + if(NOT WASI_SDK_DISABLE_CCACHE) + if(CCACHE) + set(CMAKE_C_COMPILER_LAUNCHER ccache) + set(CMAKE_CXX_COMPILER_LAUNCHER ccache) + message(STATUS "Auto-enabling ccache") + else() + message(STATUS "Failed to auto-enable ccache, not found on system") + endif() + endif() +endif() diff --git a/cmake/wasi-sdk-sysroot.cmake b/cmake/wasi-sdk-sysroot.cmake new file mode 100644 index 0000000..38ed426 --- /dev/null +++ b/cmake/wasi-sdk-sysroot.cmake @@ -0,0 +1,416 @@ +# Build logic for building a sysroot for wasi-sdk which includes compiler-rt, +# wasi-libc, libcxx, and libcxxabi. + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE RelWithDebInfo) +endif() + +if(NOT CMAKE_C_COMPILER_ID MATCHES Clang) + message(FATAL_ERROR "C compiler ${CMAKE_C_COMPILER} is not `Clang`, it is ${CMAKE_C_COMPILER_ID}") +endif() + +set(minimum_clang_required 18.0.0) + +if(CMAKE_C_COMPILER_VERSION VERSION_LESS ${minimum_clang_required}) + message(FATAL_ERROR "compiler version ${CMAKE_C_COMPILER_VERSION} is less than the required version ${minimum_clang_required}") +endif() + +message(STATUS "Found executable for `nm`: ${CMAKE_NM}") +message(STATUS "Found executable for `ar`: ${CMAKE_AR}") + +find_program(MAKE make REQUIRED) + +option(WASI_SDK_DEBUG_PREFIX_MAP "Pass `-fdebug-prefix-map` for built artifacts" ON) +option(WASI_SDK_INCLUDE_TESTS "Whether or not to build tests by default" OFF) +option(WASI_SDK_INSTALL_TO_CLANG_RESOURCE_DIR "Whether or not to modify the compiler's resource directory" OFF) +option(WASI_SDK_LTO "Whether or not to build LTO assets" ON) +option(WASI_SDK_EXCEPTIONS "Whether or not C++ exceptions are enabled" OFF) +set(WASI_SDK_CPU_CFLAGS "-mcpu=lime1" CACHE STRING "CFLAGS to specify wasm features to enable") + +set(wasi_tmp_install ${CMAKE_CURRENT_BINARY_DIR}/install) +set(wasi_sysroot ${wasi_tmp_install}/share/wasi-sysroot) +set(wasi_resource_dir ${wasi_tmp_install}/wasi-resource-dir) + +if(WASI_SDK_DEBUG_PREFIX_MAP) + add_compile_options( + -fdebug-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=wasisdk://v${wasi_sdk_version}) +endif() + +# Default arguments for builds of cmake projects (mostly LLVM-based) to forward +# along much of our own configuration into these projects. +set(default_cmake_args + -DCMAKE_SYSTEM_NAME=WASI + -DCMAKE_SYSTEM_VERSION=1 + -DCMAKE_SYSTEM_PROCESSOR=wasm32 + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_AR=${CMAKE_AR} + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} + -DCMAKE_C_COMPILER_WORKS=ON + -DCMAKE_CXX_COMPILER_WORKS=ON + -DCMAKE_SYSROOT=${wasi_sysroot} + -DCMAKE_MODULE_PATH=${CMAKE_CURRENT_SOURCE_DIR}/cmake + # CMake detects this based on `CMAKE_C_COMPILER` alone and when that compiler + # is just a bare "clang" installation then it can mistakenly deduce that this + # feature is supported when it's not actually supported for WASI targets. + # Currently `wasm-ld` does not support the linker flag for this. + -DCMAKE_C_LINKER_DEPFILE_SUPPORTED=OFF + -DCMAKE_CXX_LINKER_DEPFILE_SUPPORTED=OFF) + +if(CMAKE_C_COMPILER_LAUNCHER) + list(APPEND default_cmake_args -DCMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER}) +endif() +if(CMAKE_CXX_COMPILER_LAUNCHER) + list(APPEND default_cmake_args -DCMAKE_CXX_COMPILER_LAUNCHER=${CMAKE_CXX_COMPILER_LAUNCHER}) +endif() + +# ============================================================================= +# compiler-rt build logic +# ============================================================================= + +add_custom_target(compiler-rt-build) +function(define_compiler_rt target) + ExternalProject_Add(compiler-rt-build-${target} + SOURCE_DIR "${llvm_proj_dir}/compiler-rt" + CMAKE_ARGS + ${default_cmake_args} + -DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=ON + -DCOMPILER_RT_BAREMETAL_BUILD=ON + -DCOMPILER_RT_BUILD_XRAY=OFF + -DCOMPILER_RT_INCLUDE_TESTS=OFF + -DCOMPILER_RT_HAS_FPIC_FLAG=OFF + -DCOMPILER_RT_DEFAULT_TARGET_ONLY=ON + -DCOMPILER_RT_BUILD_SANITIZERS=OFF + -DCOMPILER_RT_BUILD_XRAY=OFF + -DCOMPILER_RT_BUILD_LIBFUZZER=OFF + -DCOMPILER_RT_BUILD_PROFILE=OFF + -DCOMPILER_RT_BUILD_CTX_PROFILE=OFF + -DCOMPILER_RT_BUILD_MEMPROF=OFF + -DCOMPILER_RT_BUILD_ORC=OFF + -DCOMPILER_RT_BUILD_GWP_ASAN=OFF + -DCMAKE_C_COMPILER_TARGET=${target} + -DCMAKE_C_FLAGS=${WASI_SDK_CPU_CFLAGS} + -DCMAKE_CXX_FLAGS=${WASI_SDK_CPU_CFLAGS} + -DCMAKE_ASM_FLAGS=${WASI_SDK_CPU_CFLAGS} + -DCMAKE_INSTALL_PREFIX=${wasi_resource_dir} + EXCLUDE_FROM_ALL ON + USES_TERMINAL_CONFIGURE ON + USES_TERMINAL_BUILD ON + USES_TERMINAL_INSTALL ON + ) + add_dependencies(compiler-rt-build compiler-rt-build-${target}) +endfunction() + +# The `compiler-rt` for `wasm32-wasip1` will be reused for `wasm32-wasip2` and +# `wasm32-wasi`. The version for `wasm32-wasip1-threads` will be reused for +# `wasm32-wasi-threads`. Different builds are needed for different codegen flags +# and such across the threaded/not target. +define_compiler_rt(wasm32-wasip1) +define_compiler_rt(wasm32-wasip1-threads) + +# If a p3 target is requested, also build compiler-rt for that target. WASIp3 +# will eventually have a different ABI than wasm32-wasip2, so this separate +# build is needed. +if(WASI_SDK_TARGETS MATCHES p3) + define_compiler_rt(wasm32-wasip3) +endif() + +# In addition to the default installation of `compiler-rt` itself also copy +# around some headers and make copies of the `wasi` directory as `wasip1` and +# `wasip2` and `wasip3` +execute_process( + COMMAND ${CMAKE_C_COMPILER} -print-resource-dir + OUTPUT_VARIABLE clang_resource_dir + OUTPUT_STRIP_TRAILING_WHITESPACE) +add_custom_target(compiler-rt-post-build + # The `${wasi_resource_dir}` folder is going to get used as `-resource-dir` + # for future compiles. Copy the host compiler's own headers into this + # directory to ensure that all host-defined headers all work as well. + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${clang_resource_dir}/include ${wasi_resource_dir}/include + + # Copy the `lib/wasm32-unknown-wasip1` folder to `lib/wasm32-unknown-wasi{,p2}` to ensure that those + # OS-strings also work for looking up the compiler-rt.a file. + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${wasi_resource_dir}/lib/wasm32-unknown-wasip1 ${wasi_resource_dir}/lib/wasm32-unknown-wasi + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${wasi_resource_dir}/lib/wasm32-unknown-wasip1 ${wasi_resource_dir}/lib/wasm32-unknown-wasip2 + # Copy the `lib/wasm32-unknown-wasip1-threads` folder to `lib/wasm32-unknown-wasi-threads` + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${wasi_resource_dir}/lib/wasm32-unknown-wasip1-threads ${wasi_resource_dir}/lib/wasm32-unknown-wasi-threads + + COMMENT "finalizing compiler-rt installation" +) +add_dependencies(compiler-rt-post-build compiler-rt-build) + +add_custom_target(compiler-rt DEPENDS compiler-rt-build compiler-rt-post-build) + +# ============================================================================= +# wasi-libc build logic +# ============================================================================= + +function(define_wasi_libc_sub target target_suffix lto) + string(TOUPPER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_UPPER) + get_property(directory_cflags DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY COMPILE_OPTIONS) + set(extra_cflags_list "${WASI_SDK_CPU_CFLAGS} ${CMAKE_C_FLAGS} ${directory_cflags}") + + if(${target} MATCHES "p[23]") + # Always enable `-fPIC` for the `wasm32-wasip2` and `wasm32-wasip3` targets. + # This makes `libc.a` more flexible and usable in dynamic linking situations. + list(APPEND extra_cflags_list -fPIC) + endif() + + # The `wasm32-wasi` target is deprecated in clang, so ignore the deprecation + # warnings for now. + if(${target} STREQUAL wasm32-wasi OR ${target} STREQUAL wasm32-wasi-threads) + list(APPEND extra_cflags_list -Wno-deprecated) + endif() + + list(JOIN extra_cflags_list " " extra_cflags) + + if(${target} MATCHES threads) + set(libcompiler_rt_a ${wasi_resource_dir}/lib/wasm32-unknown-wasip1-threads/libclang_rt.builtins.a) + else() + set(libcompiler_rt_a ${wasi_resource_dir}/lib/wasm32-unknown-wasip1/libclang_rt.builtins.a) + endif() + + set(extra_cmake_args) + + # Configure LTO in wasi libc if it's enabled. Be sure to disable shared + # libraries as well since that's not currently supported. + if (lto) + list(APPEND extra_cmake_args -DLTO=full -DBUILD_SHARED=OFF) + endif() + + ExternalProject_Add(wasi-libc-${target}${target_suffix}-build + SOURCE_DIR ${wasi_libc} + CMAKE_ARGS + ${default_cmake_args} + ${extra_cmake_args} + -DTARGET_TRIPLE=${target} + -DCMAKE_INSTALL_PREFIX=${wasi_sysroot} + -DCMAKE_C_FLAGS=${extra_cflags} + -DCMAKE_ASM_FLAGS=${extra_cflags} + -DBUILTINS_LIB=${libcompiler_rt_a} + -DUSE_WASM_COMPONENT_LD=OFF + -DWASI_SDK_VERSION=${wasi_sdk_version} + DEPENDS compiler-rt + EXCLUDE_FROM_ALL ON + USES_TERMINAL_CONFIGURE ON + USES_TERMINAL_BUILD ON + USES_TERMINAL_INSTALL ON + ) +endfunction() + +function(define_wasi_libc target) + define_wasi_libc_sub (${target} "" OFF) + if(WASI_SDK_LTO) + define_wasi_libc_sub (${target} "-lto" ON) + endif() + + add_custom_target(wasi-libc-${target} + DEPENDS wasi-libc-${target}-build $<$:wasi-libc-${target}-lto-build>) +endfunction() + +foreach(target IN LISTS WASI_SDK_TARGETS) + define_wasi_libc(${target}) +endforeach() + +# ============================================================================= +# libcxx build logic +# ============================================================================= + +execute_process( + COMMAND ${CMAKE_C_COMPILER} -dumpversion + OUTPUT_VARIABLE llvm_version + OUTPUT_STRIP_TRAILING_WHITESPACE) + +function(define_libcxx_sub target target_suffix extra_target_flags extra_libdir_suffix) + if(${target} MATCHES threads) + set(pic OFF) + set(target_flags -pthread) + else() + set(pic ON) + set(target_flags "") + endif() + if(${target_suffix} MATCHES lto) + set(pic OFF) + endif() + list(APPEND target_flags ${extra_target_flags}) + + set(runtimes "libcxx;libcxxabi") + + get_property(dir_compile_opts DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY COMPILE_OPTIONS) + get_property(dir_link_opts DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY LINK_OPTIONS) + set(extra_flags + ${WASI_SDK_CPU_CFLAGS} + ${target_flags} + --target=${target} + ${dir_compile_opts} + ${dir_link_opts} + --sysroot ${wasi_sysroot} + -resource-dir ${wasi_resource_dir}) + + if (WASI_SDK_EXCEPTIONS) + # TODO: lots of builds fail with shared libraries and `-fPIC`. Looks like + # things are maybe changing in llvm/llvm-project#159143 but otherwise I'm at + # least not really sure what the state of shared libraries and exceptions + # are. For now shared libraries are disabled and supporting them is left for + # a future endeavor. + set(pic OFF) + set(runtimes "libunwind;${runtimes}") + list(APPEND extra_flags -fwasm-exceptions -mllvm -wasm-use-legacy-eh=false) + endif() + + # The `wasm32-wasi` target is deprecated in clang, so ignore the deprecation + # warnings for now. + if(${target} STREQUAL wasm32-wasi OR ${target} STREQUAL wasm32-wasi-threads) + list(APPEND extra_flags -Wno-deprecated) + endif() + + set(extra_cflags_list ${CMAKE_C_FLAGS} ${extra_flags}) + list(JOIN extra_cflags_list " " extra_cflags) + set(extra_cxxflags_list ${CMAKE_CXX_FLAGS} ${extra_flags}) + list(JOIN extra_cxxflags_list " " extra_cxxflags) + + ExternalProject_Add(libcxx-${target}${target_suffix}-build + SOURCE_DIR ${llvm_proj_dir}/runtimes + CMAKE_ARGS + ${default_cmake_args} + # Ensure headers are installed in a target-specific path instead of a + # target-generic path. + -DCMAKE_INSTALL_INCLUDEDIR=${wasi_sysroot}/include/${target} + -DCMAKE_STAGING_PREFIX=${wasi_sysroot} + -DCMAKE_POSITION_INDEPENDENT_CODE=${pic} + -DLIBCXX_ENABLE_THREADS:BOOL=ON + -DLIBCXX_HAS_PTHREAD_API:BOOL=ON + -DLIBCXX_HAS_EXTERNAL_THREAD_API:BOOL=OFF + -DLIBCXX_HAS_WIN32_THREAD_API:BOOL=OFF + -DLLVM_COMPILER_CHECKED=ON + -DLIBCXX_ENABLE_SHARED:BOOL=${pic} + -DLIBCXX_ENABLE_EXCEPTIONS:BOOL=${WASI_SDK_EXCEPTIONS} + -DLIBCXX_ENABLE_FILESYSTEM:BOOL=ON + -DLIBCXX_ENABLE_ABI_LINKER_SCRIPT:BOOL=OFF + -DLIBCXX_CXX_ABI=libcxxabi + -DLIBCXX_HAS_MUSL_LIBC:BOOL=OFF + -DLIBCXX_ABI_VERSION=2 + -DLIBCXXABI_ENABLE_EXCEPTIONS:BOOL=${WASI_SDK_EXCEPTIONS} + -DLIBCXXABI_ENABLE_SHARED:BOOL=${pic} + -DLIBCXXABI_SILENT_TERMINATE:BOOL=ON + -DLIBCXXABI_ENABLE_THREADS:BOOL=ON + -DLIBCXXABI_HAS_PTHREAD_API:BOOL=ON + -DLIBCXXABI_HAS_EXTERNAL_THREAD_API:BOOL=OFF + -DLIBCXXABI_HAS_WIN32_THREAD_API:BOOL=OFF + -DLIBCXXABI_USE_LLVM_UNWINDER:BOOL=${WASI_SDK_EXCEPTIONS} + -DLIBUNWIND_ENABLE_SHARED:BOOL=${pic} + -DLIBUNWIND_ENABLE_THREADS:BOOL=ON + -DLIBUNWIND_USE_COMPILER_RT:BOOL=ON + -DLIBUNWIND_INCLUDE_TESTS:BOOL=OFF + -DUNIX:BOOL=ON + -DCMAKE_C_FLAGS=${extra_cflags} + -DCMAKE_ASM_FLAGS=${extra_cflags} + -DCMAKE_CXX_FLAGS=${extra_cxxflags} + -DLIBCXX_LIBDIR_SUFFIX=/${target}${extra_libdir_suffix} + -DLIBCXXABI_LIBDIR_SUFFIX=/${target}${extra_libdir_suffix} + -DLIBUNWIND_LIBDIR_SUFFIX=/${target}${extra_libdir_suffix} + -DLIBCXX_INCLUDE_TESTS=OFF + -DLIBCXX_INCLUDE_BENCHMARKS=OFF + + # See https://www.scivision.dev/cmake-externalproject-list-arguments/ for + # why this is in `CMAKE_CACHE_ARGS` instead of above + CMAKE_CACHE_ARGS + -DLLVM_ENABLE_RUNTIMES:STRING=${runtimes} + DEPENDS + wasi-libc-${target} + compiler-rt + EXCLUDE_FROM_ALL ON + USES_TERMINAL_CONFIGURE ON + USES_TERMINAL_BUILD ON + USES_TERMINAL_INSTALL ON + PATCH_COMMAND + ${CMAKE_COMMAND} -E chdir .. bash -c + "git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-168449.patch || git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-168449.patch -R --check" + COMMAND + ${CMAKE_COMMAND} -E chdir .. bash -c + "git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-186054.patch || git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-186054.patch -R --check" + ) +endfunction() + +function(define_libcxx target) + define_libcxx_sub(${target} "" "" "") + if(WASI_SDK_LTO) + # Note: clang knows this /llvm-lto/${llvm_version} convention. + # https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/clang/lib/Driver/ToolChains/WebAssembly.cpp#L204-L210 + define_libcxx_sub(${target} "-lto" "-flto=full" "/llvm-lto/${llvm_version}") + endif() + + # As of this writing, `clang++` will ignore the target-specific include dirs + # unless this one also exists: + add_custom_target(libcxx-${target}-extra-dir + COMMAND ${CMAKE_COMMAND} -E make_directory ${wasi_sysroot}/include/c++/v1 + COMMENT "creating libcxx-specific header file folder") + add_custom_target(libcxx-${target} + DEPENDS libcxx-${target}-build $<$:libcxx-${target}-lto-build> libcxx-${target}-extra-dir) +endfunction() + +foreach(target IN LISTS WASI_SDK_TARGETS) + define_libcxx(${target}) +endforeach() + +# ============================================================================= +# misc build logic +# ============================================================================= + +install(DIRECTORY ${wasi_tmp_install}/share + USE_SOURCE_PERMISSIONS + DESTINATION ${CMAKE_INSTALL_PREFIX}) +if(WASI_SDK_INSTALL_TO_CLANG_RESOURCE_DIR) + install(DIRECTORY ${wasi_resource_dir}/lib + USE_SOURCE_PERMISSIONS + DESTINATION ${clang_resource_dir}) +else() + install(DIRECTORY ${wasi_resource_dir}/lib + USE_SOURCE_PERMISSIONS + DESTINATION ${CMAKE_INSTALL_PREFIX}/clang-resource-dir) +endif() + +# Add a top-level `build` target as well as `build-$target` targets. +add_custom_target(build ALL) +foreach(target IN LISTS WASI_SDK_TARGETS) + add_custom_target(build-${target}) + add_dependencies(build-${target} libcxx-${target} wasi-libc-${target} compiler-rt) + add_dependencies(build build-${target}) +endforeach() + +# Install a `VERSION` file in the output prefix with a dump of version +# information. +execute_process( + COMMAND ${PYTHON} ${version_script} dump + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE version_dump) +set(version_file_tmp ${wasi_sysroot}/VERSION) +file(GENERATE OUTPUT ${version_file_tmp} CONTENT ${version_dump}) +add_custom_target(version-file DEPENDS ${version_file_tmp}) +add_dependencies(build version-file) + +if(WASI_SDK_INCLUDE_TESTS) + add_subdirectory(tests) +endif() + +include(wasi-sdk-dist) + +set(dist_dir ${CMAKE_CURRENT_BINARY_DIR}/dist) + +# Tarball with just `compiler-rt` libraries within it +wasi_sdk_add_tarball(dist-compiler-rt + ${dist_dir}/libclang_rt-${wasi_sdk_version}.tar.gz + ${wasi_resource_dir}/lib) +add_dependencies(dist-compiler-rt compiler-rt) + +# Tarball with the whole sysroot +wasi_sdk_add_tarball(dist-sysroot + ${dist_dir}/wasi-sysroot-${wasi_sdk_version}.tar.gz + ${wasi_sysroot}) +add_dependencies(dist-sysroot build) + +add_custom_target(dist DEPENDS dist-compiler-rt dist-sysroot) diff --git a/cmake/wasi-sdk-toolchain.cmake b/cmake/wasi-sdk-toolchain.cmake new file mode 100644 index 0000000..5468d93 --- /dev/null +++ b/cmake/wasi-sdk-toolchain.cmake @@ -0,0 +1,337 @@ +# Build logic and support for building a Clang toolchain that can target +# WebAssembly and build a WASI sysroot. + +set(LLVM_CMAKE_FLAGS "" CACHE STRING "Extra cmake flags to pass to LLVM's build") +set(RUST_TARGET "" CACHE STRING "Target to build Rust code for, if not the host") +set(WASI_SDK_ARTIFACT "" CACHE STRING "Name of the wasi-sdk artifact being produced") + +option(WASI_SDK_LLDB "Include a build of LLDB" ON) + +set(LIBEDIT_DEFAULT ON) +# I don't want to deal with running a `./configure` script on Windows, disable +# it by default. +if(WIN32) + set(LIBEDIT_DEFAULT OFF) +endif() +# I don't know how to resolve build failures when building libedit for x86_64 +# from arm64 on macos, so disable it for now. +if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64" AND LLVM_CMAKE_FLAGS MATCHES "x86_64") + set(LIBEDIT_DEFAULT OFF) +endif() +option(WASI_SDK_LIBEDIT "Whether or not to build libedit for LLDB" ${LIBEDIT_DEFAULT}) +option(WASI_SDK_LIBXML2 "Whether or not to build libxml2 for LLDB" ON) + +string(REGEX REPLACE "[ ]+" ";" llvm_cmake_flags_list "${LLVM_CMAKE_FLAGS}") + +set(wasi_tmp_install ${CMAKE_CURRENT_BINARY_DIR}/install) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE MinSizeRel) +endif() + +set(default_cmake_args + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_AR=${CMAKE_AR} + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} + -DCMAKE_INSTALL_PREFIX=${wasi_tmp_install}) + +if(CMAKE_C_COMPILER_LAUNCHER) + list(APPEND default_cmake_args -DCMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER}) +endif() +if(CMAKE_CXX_COMPILER_LAUNCHER) + list(APPEND default_cmake_args -DCMAKE_CXX_COMPILER_LAUNCHER=${CMAKE_CXX_COMPILER_LAUNCHER}) +endif() + +set(links_to_create clang-cl clang-cpp clang++) +foreach(target IN LISTS WASI_SDK_TARGETS) + list(APPEND links_to_create ${target}-clang) + list(APPEND links_to_create ${target}-clang++) +endforeach() + +set(projects "lld;clang;clang-tools-extra") + +set(tools + clang + clang-format + clang-tidy + clang-apply-replacements + lld + llvm-addr2line + llvm-mc + llvm-ranlib + llvm-strip + llvm-dwarfdump + llvm-dwp + clang-resource-headers + ar + ranlib + strip + nm + size + strings + objdump + objcopy + c++filt + llvm-config + libclang) + +# By default link LLVM dynamically to all the various tools. This greatly +# reduces the binary size of all the tools through a shared library rather than +# statically linking LLVM to each individual tool. This requires a few other +# install targets as well to ensure the appropriate libraries are all installed. +# +# Also note that the `-wasi-sdk` version suffix is intended to help prevent +# these dynamic libraries from clashing with other system libraries in case the +# `lib` dir gets put on `LD_LIBRARY_PATH` or similar. +if(NOT WIN32) + list(APPEND default_cmake_args -DLLVM_LINK_LLVM_DYLIB=ON -DLLVM_VERSION_SUFFIX=-wasi-sdk) + list(APPEND tools LLVM clang-cpp) +endif() + +# Configure/add LLDB if requested. +# +# Note that LLDB depends on `libedit` which is more-or-less required to get a +# reasonable command-line experience, so this is built custom here to ensure +# that it's available for LLDB. +if(WASI_SDK_LLDB) + list(APPEND projects lldb) + list(APPEND tools lldb liblldb) + list(APPEND default_cmake_args + -DLLDB_INCLUDE_TESTS=OFF + -DLLDB_INCLUDE_UNITTESTS=OFF + -DLLDB_ENABLE_SWIG=OFF + -DLLDB_ENABLE_CURSES=OFF + -DLLDB_ENABLE_LZMA=OFF + -DLLDB_ENABLE_LUA=OFF + -DLLDB_ENABLE_PYTHON=OFF + -DLLDB_ENABLE_FBSDVMCORE=OFF + -DLLDB_ENABLE_LINUXPTY=OFF + ) + + set(extra_configure_commands) + if(CMAKE_SYSTEM_NAME STREQUAL Linux) + set(extra_configure_commands + # By default it looks like `libedit` tries to link to `libncurses.so` and + # such on Linux. This is problematic as systems may not have that + # installed. Turns out though at least for AlmaLinux [1] they just edit + # makefile and pkg-config info and it works out. Who knew! I thought + # one of the millions of lines in `./configure` would take care of this + # but apparently we're still resorting to editing things raw... + # + # [1]: https://git.almalinux.org/rpms/libedit/src/commit/3f0893c4cd8e0cbb2f556d2fad48326c9c037a6c/SPECS/libedit.spec#L44-L48 + COMMAND sed -i "s/lncurses/ltinfo/" src/Makefile + COMMAND sed -i "s/ -lncurses//" libedit.pc + ) + endif() + + if (WASI_SDK_LIBEDIT) + include(ProcessorCount) + ProcessorCount(nproc) + find_program(MAKE_EXECUTABLE make REQUIRED) + if(CMAKE_SYSTEM_NAME STREQUAL Darwin) + set(libedit_ldflags -Wl,-install_name,@rpath/libedit.0.dylib) + endif() + ExternalProject_Add(libedit + URL https://thrysoee.dk/editline/libedit-20251016-3.1.tar.gz + URL_HASH SHA256=21362b00653bbfc1c71f71a7578da66b5b5203559d43134d2dd7719e313ce041 + + # Without this the build system tries to find and use `aclocal-1.18` where + # with this it doesn't so turn this on. + DOWNLOAD_EXTRACT_TIMESTAMP ON + + CONFIGURE_COMMAND + /configure + --prefix=${wasi_tmp_install} + --enable-pic + --disable-examples + --disable-static + --disable-silent-rules + CC=${CMAKE_C_COMPILER} + LDFLAGS=${libedit_ldflags} + ${extra_configure_commands} + BUILD_COMMAND + ${MAKE_EXECUTABLE} -j${nproc} + + USES_TERMINAL_CONFIGURE ON + USES_TERMINAL_BUILD ON + USES_TERMINAL_INSTALL ON + ) + list(APPEND default_cmake_args + -DLLDB_ENABLE_LIBEDIT=ON + -DLibEdit_ROOT=${wasi_tmp_install} + ) + else() + list(APPEND default_cmake_args -DLLDB_ENABLE_LIBEDIT=OFF) + add_custom_target(libedit) + endif() + + set(libxml_cmake_args) + + # Windows doesn't have iconv by default, so disable it for now. + if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") + list(APPEND libxml_cmake_args -DLIBXML2_WITH_ICONV=OFF) + endif() + + # Our AlmaLinux:8 container ends up using `lib64` instead of `lib` by default + # which doesn't match LLVM, so specifically use the same dir as LLVM. + if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") + list(APPEND libxml_cmake_args -DCMAKE_INSTALL_LIBDIR=lib) + endif() + + if (WASI_SDK_LIBXML2) + ExternalProject_Add(libxml2 + URL https://download.gnome.org/sources/libxml2/2.15/libxml2-2.15.2.tar.xz + URL_HASH SHA256=c8b9bc81f8b590c33af8cc6c336dbff2f53409973588a351c95f1c621b13d09d + + CMAKE_ARGS + ${default_cmake_args} + -DLIBXML2_WITH_PROGRAMS=OFF + -DLIBXML2_WITH_DEBUG=OFF + -DLIBXML2_WITH_DOCS=OFF + -DLIBXML2_WITH_TESTS=OFF + ${libxml_cmake_args} + ${llvm_cmake_flags_list} + + USES_TERMINAL_CONFIGURE ON + USES_TERMINAL_BUILD ON + USES_TERMINAL_INSTALL ON + ) + list(APPEND default_cmake_args + -DLLDB_ENABLE_LIBXML2=ON + -DLibXml2_ROOT=${wasi_tmp_install} + ) + else() + list(APPEND default_cmake_args -DLLDB_ENABLE_LIBXML2=OFF) + add_custom_target(libxml2) + endif() +else() + add_custom_target(libedit) + add_custom_target(libxml2) +endif() + +list(TRANSFORM tools PREPEND --target= OUTPUT_VARIABLE build_targets) +list(TRANSFORM tools PREPEND --target=install- OUTPUT_VARIABLE install_targets) + +ExternalProject_Add(llvm-build + SOURCE_DIR "${llvm_proj_dir}/llvm" + CMAKE_ARGS + ${default_cmake_args} + -DLLVM_ENABLE_ZLIB=OFF + -DLLVM_ENABLE_ZSTD=OFF + -DLLVM_STATIC_LINK_CXX_STDLIB=ON + -DLLVM_INCLUDE_TESTS=OFF + -DLLVM_INCLUDE_UTILS=OFF + -DLLVM_INCLUDE_BENCHMARKS=OFF + -DLLVM_INCLUDE_EXAMPLES=OFF + -DLLVM_TARGETS_TO_BUILD=WebAssembly + -DLLVM_DEFAULT_TARGET_TRIPLE=wasm32-wasip1 + -DLLVM_INSTALL_BINUTILS_SYMLINKS=TRUE + -DLLVM_ENABLE_LIBXML2=OFF + # Pass `-s` to strip symbols by default and shrink the size of the + # distribution + -DCMAKE_EXE_LINKER_FLAGS=-s + # Looks to be required on macOS for, at build time, the dynamic linker to + # find `libedit.dylib` when that's enabled. + -DCMAKE_BUILD_RPATH=${wasi_tmp_install}/lib + ${llvm_cmake_flags_list} + # See https://www.scivision.dev/cmake-externalproject-list-arguments/ for + # why this is in `CMAKE_CACHE_ARGS` instead of above + CMAKE_CACHE_ARGS + -DLLVM_ENABLE_PROJECTS:STRING=${projects} + -DCLANG_LINKS_TO_CREATE:STRING=${links_to_create} + BUILD_COMMAND + cmake --build . ${build_targets} + INSTALL_COMMAND + cmake --build . ${install_targets} + USES_TERMINAL_CONFIGURE ON + USES_TERMINAL_BUILD ON + USES_TERMINAL_INSTALL ON +) + +add_custom_target(build ALL DEPENDS llvm-build) +ExternalProject_Add_StepDependencies(llvm-build configure libedit libxml2) + +# Installation target for this outer project for installing the toolchain to the +# system. +install(DIRECTORY ${wasi_tmp_install}/bin ${wasi_tmp_install}/lib ${wasi_tmp_install}/share + USE_SOURCE_PERMISSIONS + DESTINATION ${CMAKE_INSTALL_PREFIX}) + +# Build logic for `wasm-component-ld` installed from Rust code. +set(wasm_component_ld_root ${CMAKE_CURRENT_BINARY_DIR}/wasm-component-ld) +set(wasm_component_ld ${wasm_component_ld_root}/bin/wasm-component-ld${CMAKE_EXECUTABLE_SUFFIX}) +set(wasm_component_ld_version 0.5.21) +if(RUST_TARGET) + set(rust_target_flag --target=${RUST_TARGET}) +endif() +add_custom_command( + OUTPUT ${wasm_component_ld} + COMMAND + cargo install --root ${wasm_component_ld_root} ${rust_target_flag} + wasm-component-ld@${wasm_component_ld_version} + COMMAND + cmake -E make_directory ${wasi_tmp_install}/bin + COMMAND + cmake -E copy ${wasm_component_ld} ${wasi_tmp_install}/bin + COMMENT "Building `wasm-component-ld` ...") +add_custom_target(wasm-component-ld DEPENDS ${wasm_component_ld}) +add_dependencies(build wasm-component-ld) + +# Setup installation logic for CMake support files. +add_custom_target(misc-files) +add_dependencies(build misc-files) + +function(copy_misc_file src dst_folder) + cmake_path(GET src FILENAME src_filename) + set(dst ${wasi_tmp_install}/share/${dst_folder}/${src_filename}) + add_custom_command( + OUTPUT ${dst} + COMMAND cmake -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${src} ${dst}) + add_custom_target(copy-${src_filename} DEPENDS ${dst}) + add_dependencies(misc-files copy-${src_filename}) +endfunction() + +copy_misc_file(src/config/config.sub misc) +copy_misc_file(src/config/config.guess misc) +copy_misc_file(wasi-sdk.cmake cmake) +copy_misc_file(wasi-sdk-pthread.cmake cmake) +copy_misc_file(wasi-sdk-p1.cmake cmake) +copy_misc_file(wasi-sdk-p2.cmake cmake) +copy_misc_file(wasi-sdk-p3.cmake cmake) +copy_misc_file(cmake/Platform/WASI.cmake cmake/Platform) + +function(copy_cfg_file compiler) + set(dst ${wasi_tmp_install}/bin/${compiler}.cfg) + add_custom_command( + OUTPUT ${dst} + COMMAND cmake -E copy ${CMAKE_CURRENT_SOURCE_DIR}/clang.cfg ${dst}) + add_custom_target(copy-${compiler} DEPENDS ${dst}) + add_dependencies(misc-files copy-${compiler}) +endfunction() + +copy_cfg_file(clang) +copy_cfg_file(clang++) + +include(wasi-sdk-dist) + +# Figure out the name of the artifact which is either explicitly specified or +# inferred from CMake default variables. +if(WASI_SDK_ARTIFACT) + set(wasi_sdk_artifact ${WASI_SDK_ARTIFACT}) +else() + if(APPLE) + set(wasi_sdk_os macos) + else() + string(TOLOWER ${CMAKE_SYSTEM_NAME} wasi_sdk_os) + endif() + set(wasi_sdk_arch ${CMAKE_SYSTEM_PROCESSOR}) + set(wasi_sdk_artifact ${wasi_sdk_arch}-${wasi_sdk_os}) +endif() + +set(dist_dir ${CMAKE_CURRENT_BINARY_DIR}/dist) +wasi_sdk_add_tarball(dist-toolchain + ${dist_dir}/wasi-toolchain-${wasi_sdk_version}-${wasi_sdk_artifact}.tar.gz + ${wasi_tmp_install}) +add_dependencies(dist-toolchain build) +add_custom_target(dist DEPENDS dist-toolchain) diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..ba1c6f9 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,27 @@ +# A container which has a number of build tools pre-installed plus a build of +# `wasi-sdk` installed at `/opt/wasi-sdk`. This also has environment variables +# pre-configued to use the installed toolchain. +# +# This container is built as the last step on CI for this repository and +# pre-built versions of this container are pushed as a package to the repository +# as well. + +FROM ubuntu:24.04 + +RUN apt-get update && \ + apt-get install -y cmake ninja-build make autoconf autogen automake libtool && \ + rm -rf /var/lib/apt/lists/* + +ADD dist/wasi-sdk-*.deb . +RUN case `dpkg --print-architecture` in \ + amd64) dpkg -i wasi-sdk-*-x86_64-linux.deb ;; \ + arm64) dpkg -i wasi-sdk-*-arm64-linux.deb ;; \ + *) exit 1 ;; \ + esac && \ + rm wasi-sdk-*.deb + +ENV CC="/opt/wasi-sdk/bin/clang" +ENV CXX="/opt/wasi-sdk/bin/clang++" +ENV LD="/opt/wasi-sdk/bin/wasm-ld" +ENV AR="/opt/wasi-sdk/bin/llvm-ar" +ENV RANLIB="/opt/wasi-sdk/bin/llvm-ranlib" diff --git a/src/config b/src/config new file mode 160000 index 0000000..f992bcc --- /dev/null +++ b/src/config @@ -0,0 +1 @@ +Subproject commit f992bcc08219edb283d2ab31dd3871a4a0e8220e diff --git a/src/llvm-pr-168449.patch b/src/llvm-pr-168449.patch new file mode 100644 index 0000000..a0da1d8 --- /dev/null +++ b/src/llvm-pr-168449.patch @@ -0,0 +1,28 @@ +diff --git a/libunwind/src/assembly.h b/libunwind/src/assembly.h +index f8e83e138eff..c5097d25b0c6 100644 +--- a/libunwind/src/assembly.h ++++ b/libunwind/src/assembly.h +@@ -249,6 +249,9 @@ aliasname: \ + #define WEAK_ALIAS(name, aliasname) + #define NO_EXEC_STACK_DIRECTIVE + ++#elif defined(__wasm__) ++#define NO_EXEC_STACK_DIRECTIVE ++ + // clang-format on + #else + +diff --git a/libunwind/src/config.h b/libunwind/src/config.h +index deb5a4d4d73d..23c9f012cbcf 100644 +--- a/libunwind/src/config.h ++++ b/libunwind/src/config.h +@@ -66,7 +66,8 @@ + #define _LIBUNWIND_EXPORT + #define _LIBUNWIND_HIDDEN + #else +- #if !defined(__ELF__) && !defined(__MACH__) && !defined(_AIX) ++ #if !defined(__ELF__) && !defined(__MACH__) && !defined(_AIX) && \ ++ !defined(__wasm__) + #define _LIBUNWIND_EXPORT __declspec(dllexport) + #define _LIBUNWIND_HIDDEN + #else diff --git a/src/llvm-pr-186054.patch b/src/llvm-pr-186054.patch new file mode 100644 index 0000000..c7756d7 --- /dev/null +++ b/src/llvm-pr-186054.patch @@ -0,0 +1,48 @@ +From f71fdfcbd6fcc7b521c74b5856ebeacdd6cf55d9 Mon Sep 17 00:00:00 2001 +From: Catherine +Date: Thu, 12 Mar 2026 08:19:49 +0000 +Subject: [PATCH] [libc++abi] Revert gating of `__cxa_thread_atexit` on + Linux||Fuchsia + +This was done in the commit 3c100d5d548d with the description +"Enable -Wmissing-prototypes" which seems incongruent to me. + +Since then it's made its way into a release and broke the use of +`thread_local` variables with destructors on Wasm/WASI: + +```cc +// repro.cc +struct c { ~c() {} }; +thread_local c v; +int main() { (void)v; } +``` + +```console +$ ./wasi-sdk-31.0-x86_64-linux/bin/clang++ repro.cc +wasm-ld: error: /tmp/repro-dd1ad7.o: undefined symbol: __cxa_thread_atexit +clang++: error: linker command failed with exit code 1 (use -v to see invocation) +``` +--- + libcxxabi/src/cxa_thread_atexit.cpp | 4 +--- + 1 file changed, 1 insertion(+), 3 deletions(-) + +diff --git a/libcxxabi/src/cxa_thread_atexit.cpp b/libcxxabi/src/cxa_thread_atexit.cpp +index 402a52c741012..1bdcb4ef192b4 100644 +--- a/libcxxabi/src/cxa_thread_atexit.cpp ++++ b/libcxxabi/src/cxa_thread_atexit.cpp +@@ -106,7 +106,6 @@ namespace { + + #endif // HAVE___CXA_THREAD_ATEXIT_IMPL + +-#if defined(__linux__) || defined(__Fuchsia__) + extern "C" { + + _LIBCXXABI_FUNC_VIS int __cxa_thread_atexit(Dtor dtor, void* obj, void* dso_symbol) throw() { +@@ -141,6 +140,5 @@ extern "C" { + } + #endif // HAVE___CXA_THREAD_ATEXIT_IMPL + } +-} // extern "C" +-#endif // defined(__linux__) || defined(__Fuchsia__) ++ } // extern "C" + } // namespace __cxxabiv1 diff --git a/src/llvm-project b/src/llvm-project new file mode 160000 index 0000000..4434dab --- /dev/null +++ b/src/llvm-project @@ -0,0 +1 @@ +Subproject commit 4434dabb69916856b824f68a64b029c67175e532 diff --git a/src/wasi-libc b/src/wasi-libc new file mode 160000 index 0000000..2fc32bc --- /dev/null +++ b/src/wasi-libc @@ -0,0 +1 @@ +Subproject commit 2fc32bc81b9f07f8d9525edea59bfbaf760c06d6 diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..16e760f --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,3 @@ +*.observed +*.observed.filtered +*.wasm diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..c573aea --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,123 @@ +# Support for running tests in the `tests/{compile-only,general}` folders +cmake_minimum_required(VERSION 3.22) +project(wasi-sdk-test) +include(CTest) +enable_testing() +set(CMAKE_EXECUTABLE_SUFFIX ".wasm") + +option(WASI_SDK_TEST_HOST_TOOLCHAIN "Test against the host toolchain, not a fresh sysroot" OFF) + +if(NOT WASI_SDK_TEST_HOST_TOOLCHAIN) + add_compile_options(--sysroot=${wasi_sysroot} -resource-dir ${wasi_resource_dir}) + add_link_options(--sysroot=${wasi_sysroot} -resource-dir ${wasi_resource_dir}) +endif() + +# Sanity check setup +if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL WASI) + message(FATAL_ERROR "Wrong system name (${CMAKE_SYSTEM_NAME}), wrong toolchain file in use?") +endif() + +if(NOT DEFINED WASI) + message(FATAL_ERROR "WASI is not set, platform file likely not loaded") +endif() + +set(WASI_SDK_RUNWASI "wasmtime" CACHE STRING "Runner for tests") + +# Test everything at O0, O2, and O2+LTO +set(opt_flags -O0 -O2 "-O2 -flto") + +add_custom_target(build-tests) + +# Executes a single `test` specified. +# +# This will compile `test` for all the various targets and with various +# compiler options. If `runwasi` is non-empty then the test will be executed +# in that runner as well. +function(add_testcase runwasi test) + foreach(target IN LISTS WASI_SDK_TARGETS) + foreach(compile_flags IN LISTS opt_flags) + # Mangle the options into something appropriate for a CMake rule name + string(REGEX REPLACE " " "." target_name "${target}.${compile_flags}.${test}") + + # Add a new test executable based on `test` + add_executable(${target_name} ${test}) + add_dependencies(build-tests ${target_name}) + + # Configure all the compile options necessary. For example `--target` here + # if the target doesn't look like it's already in the name of the compiler + # as well. + if(NOT(CMAKE_C_COMPILER MATCHES ${target})) + target_compile_options(${target_name} PRIVATE --target=${target}) + target_link_options(${target_name} PRIVATE --target=${target}) + endif() + + # Apply test-specific compile options and link flags. + if(test MATCHES "clocks.c$") + target_compile_options(${target_name} PRIVATE -D_WASI_EMULATED_PROCESS_CLOCKS) + target_link_options(${target_name} PRIVATE -lwasi-emulated-process-clocks) + elseif(test MATCHES "mmap.c$") + target_compile_options(${target_name} PRIVATE -D_WASI_EMULATED_MMAN) + target_link_options(${target_name} PRIVATE -lwasi-emulated-mman) + elseif(test MATCHES "(sigabrt|signals).c$") + target_compile_options(${target_name} PRIVATE -D_WASI_EMULATED_SIGNAL) + target_link_options(${target_name} PRIVATE -lwasi-emulated-signal) + elseif(test MATCHES "printf-long-double-enabled.c$") + target_link_options(${target_name} PRIVATE -lc-printscan-long-double) + endif() + + # Apply language-specific options and dependencies. + if(test MATCHES "cc$") + if(WASI_SDK_EXCEPTIONS) + target_compile_options(${target_name} PRIVATE -fwasm-exceptions -mllvm -wasm-use-legacy-eh=false) + target_link_options(${target_name} PRIVATE -lunwind) + else() + target_compile_options(${target_name} PRIVATE -fno-exceptions) + endif() + if(NOT WASI_SDK_TEST_HOST_TOOLCHAIN) + add_dependencies(${target_name} libcxx-${target}) + endif() + else() + if(NOT WASI_SDK_TEST_HOST_TOOLCHAIN) + add_dependencies(${target_name} wasi-libc-${target}) + endif() + endif() + + # Apply target-specific options. + if(target MATCHES threads) + target_compile_options(${target_name} PRIVATE -pthread) + target_link_options(${target_name} PRIVATE -pthread) + endif() + + if(target STREQUAL wasm32-wasi OR target STREQUAL wasm32-wasi-threads) + target_compile_options(${target_name} PRIVATE -Wno-deprecated) + target_link_options(${target_name} PRIVATE -Wno-deprecated) + endif() + + if(runwasi) + set(runner ${runwasi}) + if(${runner} MATCHES wasmtime) + if(target MATCHES threads) + set(runner "${runner} -Wshared-memory") + endif() + if(WASI_SDK_EXCEPTIONS) + set(runner "${runner} -Wexceptions") + endif() + if(target MATCHES "wasip3") + set(runner "${runner} -Wcomponent-model-async -Sp3") + endif() + endif() + add_test( + NAME test-${target_name} + COMMAND + bash ../testcase.sh + ${runner} + ${test} + $ + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + endif() + endforeach() + endforeach() +endfunction() + +add_subdirectory(compile-only) +add_subdirectory(general) diff --git a/tests/compile-only/CMakeLists.txt b/tests/compile-only/CMakeLists.txt new file mode 100644 index 0000000..484a9cd --- /dev/null +++ b/tests/compile-only/CMakeLists.txt @@ -0,0 +1,9 @@ +file(GLOB c_compile_tests RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*.c") +file(GLOB cxx_compile_tests RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*.cc") + +set(compile_tests ${c_compile_tests} ${cxx_compile_tests}) + +foreach(test IN LISTS compile_tests) + add_testcase("" ${test}) +endforeach() + diff --git a/tests/compile-only/addresses.c b/tests/compile-only/addresses.c new file mode 100644 index 0000000..6511ccd --- /dev/null +++ b/tests/compile-only/addresses.c @@ -0,0 +1,27 @@ +#include +#include +#include +extern char **environ; + +extern void __dso_handle; +#if !defined(__clang_major__) || __clang_major__ >= 10 +extern void __data_end; +extern void __global_base; +extern void __heap_base; +#endif + +int main(int argc, char *argv[]) { + printf("NULL=%p\n", NULL); + printf("__dso_handle=%p\n", &__dso_handle); +#if !defined(__clang_major__) || __clang_major__ >= 10 + printf("__data_end=%p\n", &__data_end); + printf("__global_base=%p\n", &__global_base); + printf("__heap_base=%p\n", &__heap_base); +#endif + printf("__builtin_frame_address(0)=%p\n", __builtin_frame_address(0)); + printf("__builtin_alloca(0)=%p\n", __builtin_alloca(0)); + printf("__builtin_wasm_memory_size(0)=%p\n", (void *)(__builtin_wasm_memory_size(0) * PAGE_SIZE)); + printf("&errno=%p\n", (void *)&errno); + printf("&environ=%p\n", (void *)&environ); + return 0; +} diff --git a/tests/compile-only/printf-long-double.c b/tests/compile-only/printf-long-double.c new file mode 100644 index 0000000..96fc26a --- /dev/null +++ b/tests/compile-only/printf-long-double.c @@ -0,0 +1,8 @@ +#include + +volatile long double x = 42.0L; + +int main(void) { + printf("the answer is %Lf\n", x); + return 0; +} diff --git a/tests/compile-only/test.cc b/tests/compile-only/test.cc new file mode 100644 index 0000000..979a1cd --- /dev/null +++ b/tests/compile-only/test.cc @@ -0,0 +1 @@ +int main(){} diff --git a/tests/exit_status_zero b/tests/exit_status_zero new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/exit_status_zero @@ -0,0 +1 @@ +0 diff --git a/tests/general/CMakeLists.txt b/tests/general/CMakeLists.txt new file mode 100644 index 0000000..2954fd1 --- /dev/null +++ b/tests/general/CMakeLists.txt @@ -0,0 +1,8 @@ +file(GLOB c_general_tests RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS "*.c") +file(GLOB cxx_general_tests RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS "*.cc") + +set(general_tests ${c_general_tests} ${cxx_general_tests}) + +foreach(test IN LISTS general_tests) + add_testcase(${WASI_SDK_RUNWASI} ${test}) +endforeach() diff --git a/tests/general/abort.c b/tests/general/abort.c new file mode 100644 index 0000000..ddac207 --- /dev/null +++ b/tests/general/abort.c @@ -0,0 +1,6 @@ +#include + +int main(void) { + abort(); + return 0; +} diff --git a/tests/general/abort.c.exit_status.expected b/tests/general/abort.c.exit_status.expected new file mode 100644 index 0000000..405e2af --- /dev/null +++ b/tests/general/abort.c.exit_status.expected @@ -0,0 +1 @@ +134 diff --git a/tests/general/abort.c.stderr.expected b/tests/general/abort.c.stderr.expected new file mode 100644 index 0000000..d533e58 --- /dev/null +++ b/tests/general/abort.c.stderr.expected @@ -0,0 +1,7 @@ +Error: failed to run main module `abort.c.---.wasm` + +Caused by: + 0: failed to invoke --- + 1: error while executing at wasm backtrace: + note: using the `WASMTIME_BACKTRACE_DETAILS=1` environment variable may show more debugging information + 2: wasm trap: wasm `unreachable` instruction executed diff --git a/tests/general/abort.c.stderr.expected.filter b/tests/general/abort.c.stderr.expected.filter new file mode 100755 index 0000000..98233a1 --- /dev/null +++ b/tests/general/abort.c.stderr.expected.filter @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +cat \ + | sed -e 's/main module `.*abort\.c\.wasm`/main module `abort.c.---.wasm`/' \ + | sed -e 's/failed to invoke.*/failed to invoke ---/' \ + | sed -E '/0x[[:xdigit:]]+/d' diff --git a/tests/general/argc_argv_main.c b/tests/general/argc_argv_main.c new file mode 100644 index 0000000..114d1c6 --- /dev/null +++ b/tests/general/argc_argv_main.c @@ -0,0 +1,6 @@ +#include + +int main(int argc, char *argv[]) { + puts("hello from argc argv main!"); + return 0; +} diff --git a/tests/general/argc_argv_main.c.stdout.expected b/tests/general/argc_argv_main.c.stdout.expected new file mode 100644 index 0000000..851e349 --- /dev/null +++ b/tests/general/argc_argv_main.c.stdout.expected @@ -0,0 +1 @@ +hello from argc argv main! diff --git a/tests/general/argc_argv_main.cc b/tests/general/argc_argv_main.cc new file mode 100644 index 0000000..e4f3566 --- /dev/null +++ b/tests/general/argc_argv_main.cc @@ -0,0 +1,6 @@ +#include + +int main(int argc, char *argv[]) { + puts("hello from C++ argc argv main!"); + return 0; +} diff --git a/tests/general/argc_argv_main.cc.stdout.expected b/tests/general/argc_argv_main.cc.stdout.expected new file mode 100644 index 0000000..8d6f69a --- /dev/null +++ b/tests/general/argc_argv_main.cc.stdout.expected @@ -0,0 +1 @@ +hello from C++ argc argv main! diff --git a/tests/general/assert-fail.c b/tests/general/assert-fail.c new file mode 100644 index 0000000..54479f9 --- /dev/null +++ b/tests/general/assert-fail.c @@ -0,0 +1,11 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include + +int main(void) { + assert(false); + return 0; +} diff --git a/tests/general/assert-fail.c.exit_status.expected b/tests/general/assert-fail.c.exit_status.expected new file mode 100644 index 0000000..405e2af --- /dev/null +++ b/tests/general/assert-fail.c.exit_status.expected @@ -0,0 +1 @@ +134 diff --git a/tests/general/assert-fail.c.stderr.expected b/tests/general/assert-fail.c.stderr.expected new file mode 100644 index 0000000..fcf1f75 --- /dev/null +++ b/tests/general/assert-fail.c.stderr.expected @@ -0,0 +1,8 @@ +Assertion failed: false (assert-fail.c: main: 9) +Error: failed to run main module `assert-fail.c.---.wasm` + +Caused by: + 0: failed to invoke --- + 1: error while executing at wasm backtrace: + note: using the `WASMTIME_BACKTRACE_DETAILS=1` environment variable may show more debugging information + 2: wasm trap: wasm `unreachable` instruction executed diff --git a/tests/general/assert-fail.c.stderr.expected.filter b/tests/general/assert-fail.c.stderr.expected.filter new file mode 100755 index 0000000..77849a1 --- /dev/null +++ b/tests/general/assert-fail.c.stderr.expected.filter @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +cat \ + | sed -e 's/main module `.*assert-fail\.c\.wasm`/main module `assert-fail.c.---.wasm`/' \ + | sed -e 's/failed to invoke.*/failed to invoke ---/' \ + | sed -e 's/Assertion failed: false (.*assert-fail.c/Assertion failed: false (assert-fail.c/' \ + | sed -E '/0x[[:xdigit:]]+/d' diff --git a/tests/general/assert-pass.c b/tests/general/assert-pass.c new file mode 100644 index 0000000..e57329f --- /dev/null +++ b/tests/general/assert-pass.c @@ -0,0 +1,7 @@ +#include +#include + +int main(void) { + assert(true); + return 0; +} diff --git a/tests/general/clocks.c b/tests/general/clocks.c new file mode 100644 index 0000000..36908e5 --- /dev/null +++ b/tests/general/clocks.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include + +static void test_clock(void) { + clock_t a = clock(); + clock_t b = clock(); + assert(a != -1); + assert(b != -1); + assert(a > 0); + assert(b >= a); +} + +static void test_times(void) { + struct tms before; + struct tms after; + clock_t a = times(&before); + clock_t b = times(&after); + assert(a != -1); + assert(b != -1); + assert(b >= a); + assert(after.tms_utime >= before.tms_utime); +} + +static void test_getrusage(void) { + struct rusage before; + struct rusage after; + int a = getrusage(RUSAGE_SELF, &before); + int b = getrusage(RUSAGE_SELF, &after); + assert(a != -1); + assert(b != -1); + assert(after.ru_utime.tv_sec >= before.ru_utime.tv_sec); + assert(after.ru_utime.tv_sec != before.ru_utime.tv_sec || + after.ru_utime.tv_usec >= before.ru_utime.tv_usec); +} + +int main(void) { + test_clock(); + test_times(); + test_getrusage(); + return 0; +} diff --git a/tests/general/cpp_thread_local.cc b/tests/general/cpp_thread_local.cc new file mode 100644 index 0000000..30a1518 --- /dev/null +++ b/tests/general/cpp_thread_local.cc @@ -0,0 +1,3 @@ +struct c { ~c() {} }; +thread_local c v; +int main() { (void)v; } diff --git a/tests/general/ctors_dtors.c b/tests/general/ctors_dtors.c new file mode 100644 index 0000000..01c75fb --- /dev/null +++ b/tests/general/ctors_dtors.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include +extern char **environ; + +static void from_atexit(void) { + printf("hello from_atexit\n"); +} + +static void another_from_atexit(void) { + printf("hello another_from_atexit\n"); +} + +__attribute__((constructor)) static void from_constructor(void) { + printf("hello from_constructor\n"); +} + +__attribute__((constructor(101))) static void from_constructor_101(void) { + assert(errno == 0); + printf("hello from_constructor101\n"); + + assert(environ && "environment should be initialized by this point"); +} + +__attribute__((constructor(65535))) static void from_constructor_65535(void) { + printf("hello from_constructor65535\n"); +} + +__attribute__((destructor)) static void from_destructor(void) { + printf("hello from_destructor\n"); +} + +__attribute__((destructor(101))) static void from_destructor101(void) { + printf("hello from_destructor101\n"); +} + +__attribute__((destructor(65535))) static void from_destructor65535(void) { + printf("hello from_destructor65535\n"); +} + +int main(int argc, char *argv[]) { + printf("hello main\n"); + assert(argc != 0); + assert(argv != NULL); + assert(argv[argc] == NULL); + + atexit(from_atexit); + atexit(another_from_atexit); + printf("goodbye main\n"); + return 0; +} diff --git a/tests/general/ctors_dtors.c.stdout.expected b/tests/general/ctors_dtors.c.stdout.expected new file mode 100644 index 0000000..dfe8292 --- /dev/null +++ b/tests/general/ctors_dtors.c.stdout.expected @@ -0,0 +1,10 @@ +hello from_constructor101 +hello from_constructor +hello from_constructor65535 +hello main +goodbye main +hello another_from_atexit +hello from_atexit +hello from_destructor65535 +hello from_destructor +hello from_destructor101 diff --git a/tests/general/ctors_dtors.cc b/tests/general/ctors_dtors.cc new file mode 100644 index 0000000..f9e011b --- /dev/null +++ b/tests/general/ctors_dtors.cc @@ -0,0 +1,16 @@ +#include "ctors_dtors.c" + +struct StaticObject { + StaticObject(); + ~StaticObject(); +}; + +StaticObject::StaticObject() { + printf("hello StaticObject::StaticObject\n"); +} + +StaticObject::~StaticObject() { + printf("hello StaticObject::~StaticObject\n"); +} + +static StaticObject static_object; diff --git a/tests/general/ctors_dtors.cc.stdout.expected b/tests/general/ctors_dtors.cc.stdout.expected new file mode 100644 index 0000000..675900b --- /dev/null +++ b/tests/general/ctors_dtors.cc.stdout.expected @@ -0,0 +1,12 @@ +hello from_constructor101 +hello from_constructor +hello from_constructor65535 +hello StaticObject::StaticObject +hello main +goodbye main +hello another_from_atexit +hello from_atexit +hello from_destructor65535 +hello from_destructor +hello StaticObject::~StaticObject +hello from_destructor101 diff --git a/tests/general/empty.c b/tests/general/empty.c new file mode 100644 index 0000000..78f2de1 --- /dev/null +++ b/tests/general/empty.c @@ -0,0 +1 @@ +int main(void) { return 0; } diff --git a/tests/general/env-absent.c b/tests/general/env-absent.c new file mode 100644 index 0000000..b5f6284 --- /dev/null +++ b/tests/general/env-absent.c @@ -0,0 +1,7 @@ +#include +#include + +int main(void) { + printf("HELLO = %s\n", getenv("HELLO")); + return 0; +} diff --git a/tests/general/env-absent.c.stdout.expected b/tests/general/env-absent.c.stdout.expected new file mode 100644 index 0000000..f5614c3 --- /dev/null +++ b/tests/general/env-absent.c.stdout.expected @@ -0,0 +1 @@ +HELLO = (null) diff --git a/tests/general/env.c b/tests/general/env.c new file mode 100644 index 0000000..b5f6284 --- /dev/null +++ b/tests/general/env.c @@ -0,0 +1,7 @@ +#include +#include + +int main(void) { + printf("HELLO = %s\n", getenv("HELLO")); + return 0; +} diff --git a/tests/general/env.c.env b/tests/general/env.c.env new file mode 100644 index 0000000..9be21df --- /dev/null +++ b/tests/general/env.c.env @@ -0,0 +1 @@ +HELLO=hello diff --git a/tests/general/env.c.stdout.expected b/tests/general/env.c.stdout.expected new file mode 100644 index 0000000..b9a2a66 --- /dev/null +++ b/tests/general/env.c.stdout.expected @@ -0,0 +1 @@ +HELLO = hello diff --git a/tests/general/environ.c b/tests/general/environ.c new file mode 100644 index 0000000..14dc955 --- /dev/null +++ b/tests/general/environ.c @@ -0,0 +1,18 @@ +#include +#include +#include +#include +extern char **environ; + +int main(void) { + assert(environ != NULL); + for (char **p = environ; *p; ++p) { + assert(p != NULL); + } + for (char **p = environ; *p; ++p) { + if (strncmp(*p, "HELLO=", 5) == 0) { + printf("HELLO = %s\n", *p + 6); + } + } + return 0; +} diff --git a/tests/general/environ.c.env b/tests/general/environ.c.env new file mode 100644 index 0000000..9be21df --- /dev/null +++ b/tests/general/environ.c.env @@ -0,0 +1 @@ +HELLO=hello diff --git a/tests/general/environ.c.stdout.expected b/tests/general/environ.c.stdout.expected new file mode 100644 index 0000000..b9a2a66 --- /dev/null +++ b/tests/general/environ.c.stdout.expected @@ -0,0 +1 @@ +HELLO = hello diff --git a/tests/general/exceptions.cc b/tests/general/exceptions.cc new file mode 100644 index 0000000..3daed98 --- /dev/null +++ b/tests/general/exceptions.cc @@ -0,0 +1,17 @@ +#include +#include + +int main() { +#ifdef __wasm_exception_handling__ + try { + throw std::runtime_error("An error occurred"); + abort(); + } catch (const std::runtime_error& e) { + // .. + return 0; + } + abort(); +#else + return 0; +#endif +} diff --git a/tests/general/getentropy.c b/tests/general/getentropy.c new file mode 100644 index 0000000..d52bfbc --- /dev/null +++ b/tests/general/getentropy.c @@ -0,0 +1,19 @@ +#include +#include +#include + +int main() { + char buf[256] = {0}; + int ret = getentropy(buf, 256); + assert(ret == 0); + + bool something_nonzero = false; + for (int i = 0; i < 256; i++) { + if (buf[i] != 0) + something_nonzero = true; + } + + assert(something_nonzero); + + return 0; +} diff --git a/tests/general/iostream_main.cc b/tests/general/iostream_main.cc new file mode 100644 index 0000000..789c143 --- /dev/null +++ b/tests/general/iostream_main.cc @@ -0,0 +1,6 @@ +#include + +int main() { + std::cout << "hello from C++ main with cout!" << std::endl; + return 0; +} diff --git a/tests/general/iostream_main.cc.stdout.expected b/tests/general/iostream_main.cc.stdout.expected new file mode 100644 index 0000000..bbbdf77 --- /dev/null +++ b/tests/general/iostream_main.cc.stdout.expected @@ -0,0 +1 @@ +hello from C++ main with cout! diff --git a/tests/general/main_errno.c b/tests/general/main_errno.c new file mode 100644 index 0000000..6a51552 --- /dev/null +++ b/tests/general/main_errno.c @@ -0,0 +1,12 @@ +#include +#include +#include + +// It isn't required that errno be zero on entry to main, but +// for tidiness' sake, if we ever do things during startup that +// do set errno, we should reset it for tidiness' sake. +int main(void) { + int n = errno; + printf("initial errno is %d: %s\n", n, strerror(n)); + return 0; +} diff --git a/tests/general/main_errno.c.stdout.expected b/tests/general/main_errno.c.stdout.expected new file mode 100644 index 0000000..c537e5f --- /dev/null +++ b/tests/general/main_errno.c.stdout.expected @@ -0,0 +1 @@ +initial errno is 0: Success diff --git a/tests/general/mmap.c b/tests/general/mmap.c new file mode 100644 index 0000000..322c929 --- /dev/null +++ b/tests/general/mmap.c @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include +#include +#ifdef __GLIBC__ +#include +#endif +#include + +#define perror_and_exit(message) \ + do { \ + perror(message); \ + return EXIT_FAILURE; \ + } while (0) + +#define OFFSET 10726 +#define LENGTH 143 + +int main(int argc, char *argv[]) { + if (argc != 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + return EXIT_FAILURE; + } + char *filename; + if (asprintf(&filename, "%s/input.txt", argv[1]) == -1) { + fprintf(stderr, "can't allocate filename"); + return EXIT_FAILURE; + } + + int fd = open(filename, O_RDONLY); + if (fd < 0) + perror_and_exit("open"); + + struct stat stat_buf; + if (fstat(fd, &stat_buf) != 0) + perror_and_exit("fstat"); + + off_t offset = OFFSET; + if (offset < 0) { + fprintf(stderr, "negative offset\n"); + return EXIT_FAILURE; + } + if (offset > (off_t)SIZE_MAX) { + fprintf(stderr, "offset overflow\n"); + return EXIT_FAILURE; + } + + off_t aligned_offset = offset & -(off_t)PAGE_SIZE; + + if (offset >= stat_buf.st_size) { + fprintf(stderr, "offset is past end of file\n"); + return EXIT_FAILURE; + } + + size_t length = LENGTH; + if ((off_t)length < 0) { + fprintf(stderr, "length overflow\n"); + return EXIT_FAILURE; + } + if ((off_t)length > stat_buf.st_size - offset) + length = (size_t)(stat_buf.st_size - offset); + + size_t mmap_length = length + (size_t)(offset - aligned_offset); + char *addr = mmap(NULL, mmap_length, PROT_READ, MAP_PRIVATE, fd, aligned_offset); + if (addr == MAP_FAILED) + perror_and_exit("mmap"); + + ssize_t nwritten = write(STDOUT_FILENO, addr + (offset - aligned_offset), length); + if (nwritten < 0) + perror_and_exit("write"); + + if ((size_t)nwritten != length) { + fprintf(stderr, "partial write"); + return EXIT_FAILURE; + } + + if (munmap(addr, mmap_length) != 0) + perror_and_exit("munmap"); + + if (close(fd) != 0) + perror_and_exit("close"); + + return EXIT_SUCCESS; +} diff --git a/tests/general/mmap.c.dir/.gitattributes b/tests/general/mmap.c.dir/.gitattributes new file mode 100644 index 0000000..94a9658 --- /dev/null +++ b/tests/general/mmap.c.dir/.gitattributes @@ -0,0 +1,3 @@ +# This input is read at runtime during testing so ensure that the same input is +# read on unix and windows by forcing just-a-newline for line endings. +*.txt text eol=lf diff --git a/tests/general/mmap.c.dir/input.txt b/tests/general/mmap.c.dir/input.txt new file mode 100644 index 0000000..57e1c7b --- /dev/null +++ b/tests/general/mmap.c.dir/input.txt @@ -0,0 +1,172 @@ +Alice’s Adventures in Wonderland +by Lewis Carroll + +CHAPTER VI. +Pig and Pepper + +For a minute or two she stood looking at the house, and wondering what to do next, when suddenly a footman in livery came running out of the wood—(she considered him to be a footman because he was in livery: otherwise, judging by his face only, she would have called him a fish)—and rapped loudly at the door with his knuckles. It was opened by another footman in livery, with a round face, and large eyes like a frog; and both footmen, Alice noticed, had powdered hair that curled all over their heads. She felt very curious to know what it was all about, and crept a little way out of the wood to listen. + +The Fish-Footman began by producing from under his arm a great letter, nearly as large as himself, and this he handed over to the other, saying, in a solemn tone, “For the Duchess. An invitation from the Queen to play croquet.” The Frog-Footman repeated, in the same solemn tone, only changing the order of the words a little, “From the Queen. An invitation for the Duchess to play croquet.” + +Then they both bowed low, and their curls got entangled together. + +Alice laughed so much at this, that she had to run back into the wood for fear of their hearing her; and when she next peeped out the Fish-Footman was gone, and the other was sitting on the ground near the door, staring stupidly up into the sky. + +Alice went timidly up to the door, and knocked. + +“There’s no sort of use in knocking,” said the Footman, “and that for two reasons. First, because I’m on the same side of the door as you are; secondly, because they’re making such a noise inside, no one could possibly hear you.” And certainly there was a most extraordinary noise going on within—a constant howling and sneezing, and every now and then a great crash, as if a dish or kettle had been broken to pieces. + +“Please, then,” said Alice, “how am I to get in?” + +“There might be some sense in your knocking,” the Footman went on without attending to her, “if we had the door between us. For instance, if you were inside, you might knock, and I could let you out, you know.” He was looking up into the sky all the time he was speaking, and this Alice thought decidedly uncivil. “But perhaps he can’t help it,” she said to herself; “his eyes are so very nearly at the top of his head. But at any rate he might answer questions.—How am I to get in?” she repeated, aloud. + +“I shall sit here,” the Footman remarked, “till tomorrow—” + +At this moment the door of the house opened, and a large plate came skimming out, straight at the Footman’s head: it just grazed his nose, and broke to pieces against one of the trees behind him. + +“—or next day, maybe,” the Footman continued in the same tone, exactly as if nothing had happened. + +“How am I to get in?” asked Alice again, in a louder tone. + +“Are you to get in at all?” said the Footman. “That’s the first question, you know.” + +It was, no doubt: only Alice did not like to be told so. “It’s really dreadful,” she muttered to herself, “the way all the creatures argue. It’s enough to drive one crazy!” + +The Footman seemed to think this a good opportunity for repeating his remark, with variations. “I shall sit here,” he said, “on and off, for days and days.” + +“But what am I to do?” said Alice. + +“Anything you like,” said the Footman, and began whistling. + +“Oh, there’s no use in talking to him,” said Alice desperately: “he’s perfectly idiotic!” And she opened the door and went in. + +The door led right into a large kitchen, which was full of smoke from one end to the other: the Duchess was sitting on a three-legged stool in the middle, nursing a baby; the cook was leaning over the fire, stirring a large cauldron which seemed to be full of soup. + +“There’s certainly too much pepper in that soup!” Alice said to herself, as well as she could for sneezing. + +There was certainly too much of it in the air. Even the Duchess sneezed occasionally; and as for the baby, it was sneezing and howling alternately without a moment’s pause. The only things in the kitchen that did not sneeze, were the cook, and a large cat which was sitting on the hearth and grinning from ear to ear. + +“Please would you tell me,” said Alice, a little timidly, for she was not quite sure whether it was good manners for her to speak first, “why your cat grins like that?” + +“It’s a Cheshire cat,” said the Duchess, “and that’s why. Pig!” + +She said the last word with such sudden violence that Alice quite jumped; but she saw in another moment that it was addressed to the baby, and not to her, so she took courage, and went on again:— + +“I didn’t know that Cheshire cats always grinned; in fact, I didn’t know that cats could grin.” + +“They all can,” said the Duchess; “and most of ’em do.” + +“I don’t know of any that do,” Alice said very politely, feeling quite pleased to have got into a conversation. + +“You don’t know much,” said the Duchess; “and that’s a fact.” + +Alice did not at all like the tone of this remark, and thought it would be as well to introduce some other subject of conversation. While she was trying to fix on one, the cook took the cauldron of soup off the fire, and at once set to work throwing everything within her reach at the Duchess and the baby—the fire-irons came first; then followed a shower of saucepans, plates, and dishes. The Duchess took no notice of them even when they hit her; and the baby was howling so much already, that it was quite impossible to say whether the blows hurt it or not. + +“Oh, please mind what you’re doing!” cried Alice, jumping up and down in an agony of terror. “Oh, there goes his precious nose!” as an unusually large saucepan flew close by it, and very nearly carried it off. + +“If everybody minded their own business,” the Duchess said in a hoarse growl, “the world would go round a deal faster than it does.” + +“Which would not be an advantage,” said Alice, who felt very glad to get an opportunity of showing off a little of her knowledge. “Just think of what work it would make with the day and night! You see the earth takes twenty-four hours to turn round on its axis—” + +“Talking of axes,” said the Duchess, “chop off her head!” + +Alice glanced rather anxiously at the cook, to see if she meant to take the hint; but the cook was busily stirring the soup, and seemed not to be listening, so she went on again: “Twenty-four hours, I think; or is it twelve? I—” + +“Oh, don’t bother me,” said the Duchess; “I never could abide figures!” And with that she began nursing her child again, singing a sort of lullaby to it as she did so, and giving it a violent shake at the end of every line: + +“Speak roughly to your little boy, + And beat him when he sneezes: +He only does it to annoy, + Because he knows it teases.” + +CHORUS. +(In which the cook and the baby joined): + +“Wow! wow! wow!” + +While the Duchess sang the second verse of the song, she kept tossing the baby violently up and down, and the poor little thing howled so, that Alice could hardly hear the words:— + +“I speak severely to my boy, + I beat him when he sneezes; +For he can thoroughly enjoy + The pepper when he pleases!” + +CHORUS. + +“Wow! wow! wow!” + +“Here! you may nurse it a bit, if you like!” the Duchess said to Alice, flinging the baby at her as she spoke. “I must go and get ready to play croquet with the Queen,” and she hurried out of the room. The cook threw a frying-pan after her as she went out, but it just missed her. + +Alice caught the baby with some difficulty, as it was a queer-shaped little creature, and held out its arms and legs in all directions, “just like a star-fish,” thought Alice. The poor little thing was snorting like a steam-engine when she caught it, and kept doubling itself up and straightening itself out again, so that altogether, for the first minute or two, it was as much as she could do to hold it. + +As soon as she had made out the proper way of nursing it, (which was to twist it up into a sort of knot, and then keep tight hold of its right ear and left foot, so as to prevent its undoing itself,) she carried it out into the open air. “If I don’t take this child away with me,” thought Alice, “they’re sure to kill it in a day or two: wouldn’t it be murder to leave it behind?” She said the last words out loud, and the little thing grunted in reply (it had left off sneezing by this time). “Don’t grunt,” said Alice; “that’s not at all a proper way of expressing yourself.” + +The baby grunted again, and Alice looked very anxiously into its face to see what was the matter with it. There could be no doubt that it had a very turn-up nose, much more like a snout than a real nose; also its eyes were getting extremely small for a baby: altogether Alice did not like the look of the thing at all. “But perhaps it was only sobbing,” she thought, and looked into its eyes again, to see if there were any tears. + +No, there were no tears. “If you’re going to turn into a pig, my dear,” said Alice, seriously, “I’ll have nothing more to do with you. Mind now!” The poor little thing sobbed again (or grunted, it was impossible to say which), and they went on for some while in silence. + +Alice was just beginning to think to herself, “Now, what am I to do with this creature when I get it home?” when it grunted again, so violently, that she looked down into its face in some alarm. This time there could be no mistake about it: it was neither more nor less than a pig, and she felt that it would be quite absurd for her to carry it further. + +So she set the little creature down, and felt quite relieved to see it trot away quietly into the wood. “If it had grown up,” she said to herself, “it would have made a dreadfully ugly child: but it makes rather a handsome pig, I think.” And she began thinking over other children she knew, who might do very well as pigs, and was just saying to herself, “if one only knew the right way to change them—” when she was a little startled by seeing the Cheshire Cat sitting on a bough of a tree a few yards off. + +The Cat only grinned when it saw Alice. It looked good-natured, she thought: still it had very long claws and a great many teeth, so she felt that it ought to be treated with respect. + +“Cheshire Puss,” she began, rather timidly, as she did not at all know whether it would like the name: however, it only grinned a little wider. “Come, it’s pleased so far,” thought Alice, and she went on. “Would you tell me, please, which way I ought to go from here?” + +“That depends a good deal on where you want to get to,” said the Cat. + +“I don’t much care where—” said Alice. + +“Then it doesn’t matter which way you go,” said the Cat. + +“—so long as I get somewhere,” Alice added as an explanation. + +“Oh, you’re sure to do that,” said the Cat, “if you only walk long enough.” + +Alice felt that this could not be denied, so she tried another question. “What sort of people live about here?” + +“In that direction,” the Cat said, waving its right paw round, “lives a Hatter: and in that direction,” waving the other paw, “lives a March Hare. Visit either you like: they’re both mad.” + +“But I don’t want to go among mad people,” Alice remarked. + +“Oh, you can’t help that,” said the Cat: “we’re all mad here. I’m mad. You’re mad.” + +“How do you know I’m mad?” said Alice. + +“You must be,” said the Cat, “or you wouldn’t have come here.” + +Alice didn’t think that proved it at all; however, she went on “And how do you know that you’re mad?” + +“To begin with,” said the Cat, “a dog’s not mad. You grant that?” + +“I suppose so,” said Alice. + +“Well, then,” the Cat went on, “you see, a dog growls when it’s angry, and wags its tail when it’s pleased. Now I growl when I’m pleased, and wag my tail when I’m angry. Therefore I’m mad.” + +“I call it purring, not growling,” said Alice. + +“Call it what you like,” said the Cat. “Do you play croquet with the Queen to-day?” + +“I should like it very much,” said Alice, “but I haven’t been invited yet.” + +“You’ll see me there,” said the Cat, and vanished. + +Alice was not much surprised at this, she was getting so used to queer things happening. While she was looking at the place where it had been, it suddenly appeared again. + +“By-the-bye, what became of the baby?” said the Cat. “I’d nearly forgotten to ask.” + +“It turned into a pig,” Alice quietly said, just as if it had come back in a natural way. + +“I thought it would,” said the Cat, and vanished again. + +Alice waited a little, half expecting to see it again, but it did not appear, and after a minute or two she walked on in the direction in which the March Hare was said to live. “I’ve seen hatters before,” she said to herself; “the March Hare will be much the most interesting, and perhaps as this is May it won’t be raving mad—at least not so mad as it was in March.” As she said this, she looked up, and there was the Cat again, sitting on a branch of a tree. + +“Did you say pig, or fig?” said the Cat. + +“I said pig,” replied Alice; “and I wish you wouldn’t keep appearing and vanishing so suddenly: you make one quite giddy.” + +“All right,” said the Cat; and this time it vanished quite slowly, beginning with the end of the tail, and ending with the grin, which remained some time after the rest of it had gone. + +“Well! I’ve often seen a cat without a grin,” thought Alice; “but a grin without a cat! It’s the most curious thing I ever saw in my life!” + +She had not gone much farther before she came in sight of the house of the March Hare: she thought it must be the right house, because the chimneys were shaped like ears and the roof was thatched with fur. It was so large a house, that she did not like to go nearer till she had nibbled some more of the lefthand bit of mushroom, and raised herself to about two feet high: even then she walked up towards it rather timidly, saying to herself “Suppose it should be raving mad after all! I almost wish I’d gone to see the Hatter instead!” diff --git a/tests/general/mmap.c.stdout.expected b/tests/general/mmap.c.stdout.expected new file mode 100644 index 0000000..5011009 --- /dev/null +++ b/tests/general/mmap.c.stdout.expected @@ -0,0 +1,3 @@ +“Would you tell me, please, which way I ought to go from here?” + +“That depends a good deal on where you want to get to,” said the Cat. diff --git a/tests/general/no_arg_main.c b/tests/general/no_arg_main.c new file mode 100644 index 0000000..56d7537 --- /dev/null +++ b/tests/general/no_arg_main.c @@ -0,0 +1,6 @@ +#include + +int main() { + puts("hello from no-arg main!"); + return 0; +} diff --git a/tests/general/no_arg_main.c.stdout.expected b/tests/general/no_arg_main.c.stdout.expected new file mode 100644 index 0000000..d5ae29d --- /dev/null +++ b/tests/general/no_arg_main.c.stdout.expected @@ -0,0 +1 @@ +hello from no-arg main! diff --git a/tests/general/no_arg_main.cc b/tests/general/no_arg_main.cc new file mode 100644 index 0000000..91cfd34 --- /dev/null +++ b/tests/general/no_arg_main.cc @@ -0,0 +1,6 @@ +#include + +int main() { + puts("hello from C++ no-arg main!"); + return 0; +} diff --git a/tests/general/no_arg_main.cc.stdout.expected b/tests/general/no_arg_main.cc.stdout.expected new file mode 100644 index 0000000..1aa719e --- /dev/null +++ b/tests/general/no_arg_main.cc.stdout.expected @@ -0,0 +1 @@ +hello from C++ no-arg main! diff --git a/tests/general/opendir.c b/tests/general/opendir.c new file mode 100644 index 0000000..941e7ce --- /dev/null +++ b/tests/general/opendir.c @@ -0,0 +1,72 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define perror_and_exit(message) \ + do { \ + perror(message); \ + return EXIT_FAILURE; \ + } while (0) + +#define OFFSET 10726 +#define LENGTH 143 + +int main(int argc, char *argv[]) { + if (argc != 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + return EXIT_FAILURE; + } + DIR *dir = opendir(argv[1]); + if (dir == NULL) { + perror_and_exit("opendir"); + } + + int count = 0; + int zeros = 0; + errno = 0; + for (;; count += 1) { + struct dirent *ent = readdir(dir); + if (ent == NULL) { + if (errno == 0) { + break; + } + perror_and_exit("readdir"); + } + + if (strcmp(ent->d_name, "file.md") == 0) { + assert(ent->d_type == DT_REG); + } else if (strcmp(ent->d_name, "dir") == 0) { + assert(ent->d_type == DT_DIR); + } else if (strcmp(ent->d_name, "file-symlink") == 0) { + assert(ent->d_type == DT_LNK); + } else if (strcmp(ent->d_name, "dir-symlink") == 0) { + assert(ent->d_type == DT_LNK); + } else if (strcmp(ent->d_name, ".") == 0) { + assert(ent->d_type == DT_DIR); + } else if (strcmp(ent->d_name, "..") == 0) { + assert(ent->d_type == DT_DIR); + } else { + assert(false); + } + if (ent->d_ino == 0) { + zeros += 1; + } + } + + assert(count == 6); + assert(zeros <= 1); + + if (closedir(dir) != 0) + perror_and_exit("closedir"); + + return EXIT_SUCCESS; +} diff --git a/tests/general/opendir.c.dir/dir-symlink b/tests/general/opendir.c.dir/dir-symlink new file mode 120000 index 0000000..8724519 --- /dev/null +++ b/tests/general/opendir.c.dir/dir-symlink @@ -0,0 +1 @@ +dir \ No newline at end of file diff --git a/tests/general/opendir.c.dir/dir/file.txt b/tests/general/opendir.c.dir/dir/file.txt new file mode 100644 index 0000000..27f8ec8 --- /dev/null +++ b/tests/general/opendir.c.dir/dir/file.txt @@ -0,0 +1 @@ +This is a file in a directory. diff --git a/tests/general/opendir.c.dir/file-symlink b/tests/general/opendir.c.dir/file-symlink new file mode 120000 index 0000000..4e87f29 --- /dev/null +++ b/tests/general/opendir.c.dir/file-symlink @@ -0,0 +1 @@ +file.md \ No newline at end of file diff --git a/tests/general/opendir.c.dir/file.md b/tests/general/opendir.c.dir/file.md new file mode 100644 index 0000000..654066f --- /dev/null +++ b/tests/general/opendir.c.dir/file.md @@ -0,0 +1 @@ +# This is a top-level file diff --git a/tests/general/printf-long-double-enabled.c b/tests/general/printf-long-double-enabled.c new file mode 100644 index 0000000..96fc26a --- /dev/null +++ b/tests/general/printf-long-double-enabled.c @@ -0,0 +1,8 @@ +#include + +volatile long double x = 42.0L; + +int main(void) { + printf("the answer is %Lf\n", x); + return 0; +} diff --git a/tests/general/printf-long-double-enabled.c.stdout.expected b/tests/general/printf-long-double-enabled.c.stdout.expected new file mode 100644 index 0000000..e23e024 --- /dev/null +++ b/tests/general/printf-long-double-enabled.c.stdout.expected @@ -0,0 +1 @@ +the answer is 42.000000 diff --git a/tests/general/printf-no-float.c b/tests/general/printf-no-float.c new file mode 100644 index 0000000..7e8339d --- /dev/null +++ b/tests/general/printf-no-float.c @@ -0,0 +1,8 @@ +#include + +volatile int x = 42; + +int main(void) { + printf("the answer is %d\n", x); + return 0; +} diff --git a/tests/general/printf-no-float.c.stdout.expected b/tests/general/printf-no-float.c.stdout.expected new file mode 100644 index 0000000..5bdef3d --- /dev/null +++ b/tests/general/printf-no-float.c.stdout.expected @@ -0,0 +1 @@ +the answer is 42 diff --git a/tests/general/printf-no-long-double.c b/tests/general/printf-no-long-double.c new file mode 100644 index 0000000..2364ef0 --- /dev/null +++ b/tests/general/printf-no-long-double.c @@ -0,0 +1,8 @@ +#include + +volatile double x = 42.0; + +int main(void) { + printf("the answer is %f\n", x); + return 0; +} diff --git a/tests/general/printf-no-long-double.c.stdout.expected b/tests/general/printf-no-long-double.c.stdout.expected new file mode 100644 index 0000000..e23e024 --- /dev/null +++ b/tests/general/printf-no-long-double.c.stdout.expected @@ -0,0 +1 @@ +the answer is 42.000000 diff --git a/tests/general/sigabrt.c b/tests/general/sigabrt.c new file mode 100644 index 0000000..22569be --- /dev/null +++ b/tests/general/sigabrt.c @@ -0,0 +1,10 @@ +#include +#include +#include + +int main(void) { + fprintf(stderr, "raising SIGABRT...\n"); + raise(SIGABRT); + fprintf(stderr, "oops!\n"); + return EXIT_FAILURE; +} diff --git a/tests/general/sigabrt.c.exit_status.expected b/tests/general/sigabrt.c.exit_status.expected new file mode 100644 index 0000000..405e2af --- /dev/null +++ b/tests/general/sigabrt.c.exit_status.expected @@ -0,0 +1 @@ +134 diff --git a/tests/general/sigabrt.c.stderr.expected b/tests/general/sigabrt.c.stderr.expected new file mode 100644 index 0000000..d702a88 --- /dev/null +++ b/tests/general/sigabrt.c.stderr.expected @@ -0,0 +1,6 @@ +raising SIGABRT... +Program received fatal signal: Aborted +Error: failed to run main module `sigabrt.c.---.wasm` + +Caused by: + 0: failed to invoke --- diff --git a/tests/general/sigabrt.c.stderr.expected.filter b/tests/general/sigabrt.c.stderr.expected.filter new file mode 100755 index 0000000..726f024 --- /dev/null +++ b/tests/general/sigabrt.c.stderr.expected.filter @@ -0,0 +1,8 @@ +#!/bin/bash +set -euo pipefail + +cat \ + | sed -e 's/main module `.*sigabrt\.c\.wasm`/main module `sigabrt.c.---.wasm`/' \ + | sed -e 's/source location: @[[:xdigit:]]*$/source location: @----/' \ + | sed -e 's/failed to invoke.*/failed to invoke ---/' \ + | head -n 6 diff --git a/tests/general/sigabrt.c.wasm32-wasi-preview2.stderr.expected b/tests/general/sigabrt.c.wasm32-wasi-preview2.stderr.expected new file mode 100644 index 0000000..f437a82 --- /dev/null +++ b/tests/general/sigabrt.c.wasm32-wasi-preview2.stderr.expected @@ -0,0 +1,6 @@ +raising SIGABRT... +Program received fatal signal: Aborted +Error: failed to run main module `sigabrt.c.---.wasm` + +Caused by: + 0: failed to invoke `run` function diff --git a/tests/general/sigabrt.c.wasm32-wasip2.stderr.expected b/tests/general/sigabrt.c.wasm32-wasip2.stderr.expected new file mode 100644 index 0000000..f437a82 --- /dev/null +++ b/tests/general/sigabrt.c.wasm32-wasip2.stderr.expected @@ -0,0 +1,6 @@ +raising SIGABRT... +Program received fatal signal: Aborted +Error: failed to run main module `sigabrt.c.---.wasm` + +Caused by: + 0: failed to invoke `run` function diff --git a/tests/general/signals.c b/tests/general/signals.c new file mode 100644 index 0000000..be4f54e --- /dev/null +++ b/tests/general/signals.c @@ -0,0 +1,86 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include + +// Make sure this exists. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-W#warnings" +#include +#pragma clang diagnostic pop + +volatile sig_atomic_t flag = 0; + +static void handler(int n) { + // This is undefined behavior by the spec, but this is just a testcase. + fflush(stdout); + printf("handler for signal %s\n", strsignal(n)); + fflush(stdout); + flag = 1; +} + +int main(void) { + // Test various raise cases that don't abort. + assert(raise(SIGCHLD) == 0); +#ifdef SIGCLD + assert(raise(SIGCLD) == 0); +#endif + assert(raise(SIGURG) == 0); + assert(raise(SIGWINCH) == 0); + + errno = 0; + assert(raise(_NSIG) == -1 && errno == EINVAL); + + // Test psignal. + psignal(SIGINT, "psignal message for SIGINT"); + + // Test strsignal. + printf("strsignal for SIGHUP: '%s'\n", strsignal(SIGHUP)); + + // Some signals can't be ignored. + errno = 0; + assert(signal(SIGKILL, SIG_IGN) == SIG_ERR && errno == EINVAL); + errno = 0; + assert(signal(SIGSTOP, SIG_IGN) == SIG_ERR && errno == EINVAL); + + // Test that all the C-standard-required signals can be + // ignored with `SIG_IGN`. + int some_fatal_sigs[] = { + SIGINT, SIGABRT, SIGFPE, SIGILL, SIGSEGV, SIGTERM + }; + for (size_t i = 0; + i < sizeof(some_fatal_sigs) / sizeof(some_fatal_sigs[0]); + ++i) + { + int sig = some_fatal_sigs[i]; + assert(signal(sig, SIG_IGN) == SIG_DFL); + raise(sig); + assert(signal(sig, SIG_DFL) == SIG_IGN); + assert(signal(sig, SIG_DFL) == SIG_DFL); + } + + // Install a handler and invoke it. + printf("beginning handler test:\n"); + assert(signal(SIGWINCH, handler) == SIG_DFL); + fflush(stdout); + assert(raise(SIGWINCH) == 0); + fflush(stdout); + assert(flag == 1); + printf("finished handler test\n"); + + // Check various API invariants. + assert(signal(SIGWINCH, SIG_IGN) == handler); + assert(raise(SIGWINCH) == 0); + assert(signal(SIGWINCH, SIG_DFL) == SIG_IGN); + assert(raise(SIGWINCH) == 0); + assert(signal(SIGWINCH, SIG_DFL) == SIG_DFL); + assert(raise(SIGWINCH) == 0); + + return EXIT_SUCCESS; +} diff --git a/tests/general/signals.c.stderr.expected b/tests/general/signals.c.stderr.expected new file mode 100644 index 0000000..f1b48ab --- /dev/null +++ b/tests/general/signals.c.stderr.expected @@ -0,0 +1 @@ +psignal message for SIGINT: Interrupt diff --git a/tests/general/signals.c.stdout.expected b/tests/general/signals.c.stdout.expected new file mode 100644 index 0000000..b35f256 --- /dev/null +++ b/tests/general/signals.c.stdout.expected @@ -0,0 +1,4 @@ +strsignal for SIGHUP: 'Hangup' +beginning handler test: +handler for signal Window changed +finished handler test diff --git a/tests/general/stat.c b/tests/general/stat.c new file mode 100644 index 0000000..393921f --- /dev/null +++ b/tests/general/stat.c @@ -0,0 +1,85 @@ +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) { + if (argc != 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + return EXIT_FAILURE; + } + + char *filename; + int n = asprintf(&filename, "%s/file", argv[1]); + assert(n > 0); + + n = creat(filename, S_IRUSR|S_IWUSR); + assert(n >= 0); + + char *linkname; + n = asprintf(&linkname, "%s/symlink", argv[1]); + assert(n > 0); + + n = symlink("file", linkname); + assert(n == 0); + + struct stat file_statbuf; + struct stat link_statbuf; + + // Test stat. + + n = stat(filename, &file_statbuf); + assert(n == 0); + assert(file_statbuf.st_size == 0); + assert(S_ISREG(file_statbuf.st_mode)); + + n = stat(linkname, &link_statbuf); + assert(n == 0); + assert(link_statbuf.st_size == 0); + assert(S_ISREG(link_statbuf.st_mode)); + + assert(file_statbuf.st_dev == link_statbuf.st_dev); + + assert(file_statbuf.st_dev == link_statbuf.st_dev); + assert(file_statbuf.st_ino == link_statbuf.st_ino); + assert(file_statbuf.st_mode == link_statbuf.st_mode); + assert(file_statbuf.st_uid == link_statbuf.st_uid); + assert(file_statbuf.st_gid == link_statbuf.st_gid); + assert(file_statbuf.st_rdev == link_statbuf.st_rdev); + assert(file_statbuf.st_size == link_statbuf.st_size); + assert(file_statbuf.st_blksize == link_statbuf.st_blksize); + assert(file_statbuf.st_blocks == link_statbuf.st_blocks); + // NB: `atim` is explicitly not compared here + assert(file_statbuf.st_mtim.tv_sec == link_statbuf.st_mtim.tv_sec); + assert(file_statbuf.st_mtim.tv_nsec == link_statbuf.st_mtim.tv_nsec); + assert(file_statbuf.st_ctim.tv_sec == link_statbuf.st_ctim.tv_sec); + assert(file_statbuf.st_ctim.tv_nsec == link_statbuf.st_ctim.tv_nsec); + + // Test lstat. + + n = lstat(filename, &file_statbuf); + assert(n == 0); + assert(file_statbuf.st_size == 0); + assert(S_ISREG(file_statbuf.st_mode)); + + n = lstat(linkname, &link_statbuf); + assert(n == 0); + assert(link_statbuf.st_size != 0); + assert(S_ISLNK(link_statbuf.st_mode)); + + assert(file_statbuf.st_dev == link_statbuf.st_dev); + assert(link_statbuf.st_ino != file_statbuf.st_ino); + + n = unlink(filename); + assert(n == 0); + n = unlink(linkname); + assert(n == 0); + + free(filename); + free(linkname); + + return 0; +} diff --git a/tests/general/stat.c.dir/empty b/tests/general/stat.c.dir/empty new file mode 100644 index 0000000..e69de29 diff --git a/tests/general/void_main.c b/tests/general/void_main.c new file mode 100644 index 0000000..54920fc --- /dev/null +++ b/tests/general/void_main.c @@ -0,0 +1,6 @@ +#include + +int main(void) { + puts("hello from void main!"); + return 0; +} diff --git a/tests/general/void_main.c.stdout.expected b/tests/general/void_main.c.stdout.expected new file mode 100644 index 0000000..533c359 --- /dev/null +++ b/tests/general/void_main.c.stdout.expected @@ -0,0 +1 @@ +hello from void main! diff --git a/tests/general/void_main.cc b/tests/general/void_main.cc new file mode 100644 index 0000000..25eb91c --- /dev/null +++ b/tests/general/void_main.cc @@ -0,0 +1,6 @@ +#include + +int main(void) { + puts("hello from C++ void main!"); + return 0; +} diff --git a/tests/general/void_main.cc.stdout.expected b/tests/general/void_main.cc.stdout.expected new file mode 100644 index 0000000..d1f259d --- /dev/null +++ b/tests/general/void_main.cc.stdout.expected @@ -0,0 +1 @@ +hello from C++ void main! diff --git a/tests/testcase.sh b/tests/testcase.sh new file mode 100755 index 0000000..d2c5a8c --- /dev/null +++ b/tests/testcase.sh @@ -0,0 +1,102 @@ +#!/bin/bash +set -ueo pipefail + +# A simple testcase runner that runs a command, captures all its command-line +# outputs, and compares them against expected outputs. + +# Command-line parsing; this script is meant to be run from a higher-level +# script, so don't do anything fancy. +runwasi="$1" +input="$2" +wasm="$3" + +# Compile names for generated files. +stdout_observed="$wasm.stdout.observed" +stderr_observed="$wasm.stderr.observed" +exit_status_observed="$wasm.exit_status.observed" + +# Double-check that a runwasi command was specified since otherwise this script +# was invoked with no arguments which isn't as intended. +if [ "$runwasi" == "" ]; then + exit 1 +fi + +# Determine the input file to write to stdin. +if [ -e "$input.stdin" ]; then + stdin="$input.stdin" +else + stdin="/dev/null" +fi + +# Determine any environment variables to set. +if [ -e "$input.env" ]; then + env=$(sed -e 's/^/--env /' < "$input.env") +else + env="" +fi + +# Determine a preopened directory to provide. +if [ -e "$input.dir" ]; then + dir="--dir $input.dir" + dirarg="$input.dir" +else + dir="" + dirarg="" +fi + +# Run the test, capturing stdout, stderr, and the exit status. +exit_status=0 +$runwasi $env $dir "$wasm" $dirarg \ + < "$stdin" \ + > "$stdout_observed" \ + 2> "$stderr_observed" \ + || exit_status=$? +echo $exit_status > "$exit_status_observed" + +# On Windows Wasmtime will exit with error code 3 for aborts. On Unix Wasmtime +# will exit with status 134. Paper over this difference by pretending to be Unix +# on Windows and converting exit code 3 into 134 for the purposes of asserting +# test output. +if [ "$OSTYPE" = "msys" ] && [ "$exit_status" = "3" ]; then + echo 134 > "$exit_status_observed" +fi + +# Determine the reference files to compare with. +if [ -e "$input.stdout.expected" ]; then + stdout_expected="$input.stdout.expected" + + # Apply output filters. + if [ -e "$input.stdout.expected.filter" ]; then + cat "$stdout_observed" \ + | "$input.stdout.expected.filter" \ + > "${stdout_observed}.filtered" + stdout_observed="${stdout_observed}.filtered" + fi +else + stdout_expected="/dev/null" +fi + +if [ -e "$input.stderr.expected" ]; then + stderr_expected="$input.stderr.expected" + + # Apply output filters. + if [ -e "$input.stderr.expected.filter" ]; then + cat "$stderr_observed" \ + | "./$input.stderr.expected.filter" \ + > "${stderr_observed}.filtered" + stderr_observed="${stderr_observed}.filtered" + fi +else + stderr_expected="/dev/null" +fi +if [ -e "$input.exit_status.expected" ]; then + exit_status_expected="$input.exit_status.expected" +else + exit_status_expected=../exit_status_zero +fi + +# If there are any differences, diff will return a non-zero exit status, and +# since this script uses "set -e", it will return a non-zero exit status too. +diff --ignore-space-change -u "$stderr_expected" "$stderr_observed" +diff --ignore-space-change -u "$stdout_expected" "$stdout_observed" +diff --ignore-space-change -u "$exit_status_expected" "$exit_status_observed" diff --git a/version.py b/version.py new file mode 100755 index 0000000..62f0ff9 --- /dev/null +++ b/version.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 + +# This script finds and prints the various versions in this project: wasi-sdk +# itself, LLVM, and the Git revisions of dependencies. +# +# Usage: version [wasi-sdk|llvm|llvm-major|dump] [--llvm-dir=] + +import argparse +import os +import subprocess +import sys + +# The number of characters to use for the abbreviated Git revision. +GIT_REF_LEN = 12 + + +def exec(command, cwd): + result = subprocess.run(command, stdout=subprocess.PIPE, + universal_newlines=True, check=True, cwd=cwd) + return result.stdout.strip() + + +def git_commit(dir): + return exec(['git', 'rev-parse', f'--short={GIT_REF_LEN}', 'HEAD'], dir) + + +def parse_git_version(version): + # Parse, e.g.: wasi-sdk-21-0-g317548590b40+m + parts = version.replace('+', '-').split('-') + assert parts.pop(0) == 'wasi' + assert parts.pop(0) == 'sdk' + + major, minor = parts.pop(0), parts.pop(0) + git = None + dirty = False + + if parts: + # Check: git|dirty. + next = parts.pop(0) + if next == 'm': + dirty = True + elif minor != '0': + git = next[1:] + + # Check: dirty. + if parts: + assert parts.pop(0) == 'm', f'expected dirty flag: +m' + dirty = True + + assert not parts, f'unexpected suffixes: {parts}' + return major, minor, git, dirty + + +# Some inline tests to check Git version parsing: +assert parse_git_version( + 'wasi-sdk-21-1-g317548590b40+m') == ('21', '1', '317548590b40', True) +assert parse_git_version('wasi-sdk-21-2+m') == ('21', '2', None, True) +assert parse_git_version( + 'wasi-sdk-23-0-g317548590b40') == ('23', '0', None, False) + + +def git_version(): + version = exec(['git', 'describe', '--long', '--candidates=999', + '--match=wasi-sdk-*', '--dirty=+m', f'--abbrev={GIT_REF_LEN}'], + os.path.dirname(sys.argv[0])) + major, minor, git, dirty = parse_git_version(version) + version = f'{major}.{minor}' + if git: + version += f'g{git}' + if dirty: + version += '+m' + return version + + +def parse_cmake_set(line): + return line.split(' ')[1].split(')')[0] + + +def llvm_cmake_version(llvm_dir): + path = f'{llvm_dir}/cmake/Modules/LLVMVersion.cmake' + if not os.path.exists(path): + # Handle older LLVM versions; see #399. + path = f'{llvm_dir}/llvm/CMakeLists.txt' + with open(path) as file: + for line in file: + line = line.strip() + if line.startswith('set(LLVM_VERSION_MAJOR'): + llvm_version_major = parse_cmake_set(line) + elif line.startswith('set(LLVM_VERSION_MINOR'): + llvm_version_minor = parse_cmake_set(line) + elif line.startswith('set(LLVM_VERSION_PATCH'): + llvm_version_patch = parse_cmake_set(line) + return llvm_version_major, llvm_version_minor, llvm_version_patch + + +def main(action, llvm_dir): + if action == 'wasi-sdk': + print(git_version()) + elif action == 'llvm': + major, minor, path = llvm_cmake_version(llvm_dir) + print(f'{major}.{minor}.{path}') + elif action == 'llvm-major': + major, _, _ = llvm_cmake_version(llvm_dir) + print(major) + elif action == 'dump': + print(git_version()) + print(f'wasi-libc: {git_commit("src/wasi-libc")}') + print(f'llvm: {git_commit(llvm_dir)}') + major, minor, path = llvm_cmake_version(llvm_dir) + print(f'llvm-version: {major}.{minor}.{path}') + print(f'config: {git_commit("src/config")}') + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description='Print the various kinds of versions in wasi-sdk') + parser.add_argument('action', + choices=['wasi-sdk', 'llvm', 'llvm-major', 'dump'], + nargs='?', + default='wasi-sdk', + help='Which kind of version to print (default: wasi-sdk).') + parser.add_argument('--llvm-dir', + nargs='?', + default='src/llvm-project', + help='Override the location of the LLVM source directory (default: src/llvm-project).') + args = parser.parse_args() + main(args.action, args.llvm_dir) + sys.exit(0) diff --git a/wasi-sdk-p1.cmake b/wasi-sdk-p1.cmake new file mode 100644 index 0000000..9038301 --- /dev/null +++ b/wasi-sdk-p1.cmake @@ -0,0 +1,38 @@ +# Cmake toolchain description file for the Makefile + +# Until Platform/WASI.cmake is upstream we need to inject the path to it +# into CMAKE_MODULE_PATH. +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") + +set(CMAKE_SYSTEM_NAME WASI) +set(CMAKE_SYSTEM_VERSION 1) +set(CMAKE_SYSTEM_PROCESSOR wasm32) +set(triple wasm32-wasip1) + +if(WIN32) + set(WASI_HOST_EXE_SUFFIX ".exe") +else() + set(WASI_HOST_EXE_SUFFIX "") +endif() + +# When building from source, WASI_SDK_PREFIX represents the generated directory +if(NOT WASI_SDK_PREFIX) + set(WASI_SDK_PREFIX ${CMAKE_CURRENT_LIST_DIR}/../../) +endif() + +set(CMAKE_C_COMPILER ${WASI_SDK_PREFIX}/bin/clang${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_CXX_COMPILER ${WASI_SDK_PREFIX}/bin/clang++${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_ASM_COMPILER ${WASI_SDK_PREFIX}/bin/clang${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_AR ${WASI_SDK_PREFIX}/bin/llvm-ar${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_RANLIB ${WASI_SDK_PREFIX}/bin/llvm-ranlib${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_C_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) +set(CMAKE_ASM_COMPILER_TARGET ${triple}) + +# Don't look in the sysroot for executables to run during the build +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + +# Only look in the sysroot (not in the host paths) for the rest +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/wasi-sdk-p2.cmake b/wasi-sdk-p2.cmake new file mode 100644 index 0000000..aeed293 --- /dev/null +++ b/wasi-sdk-p2.cmake @@ -0,0 +1,37 @@ +# Cmake toolchain description file for the Makefile + +# Until Platform/WASI.cmake is upstream we need to inject the path to it +# into CMAKE_MODULE_PATH. +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") + +set(CMAKE_SYSTEM_NAME WASI) +set(CMAKE_SYSTEM_VERSION 1) +set(CMAKE_SYSTEM_PROCESSOR wasm32) +set(triple wasm32-wasip2) + +if(WIN32) + set(WASI_HOST_EXE_SUFFIX ".exe") +else() + set(WASI_HOST_EXE_SUFFIX "") +endif() + +# When building from source, WASI_SDK_PREFIX represents the generated directory +if(NOT WASI_SDK_PREFIX) + set(WASI_SDK_PREFIX ${CMAKE_CURRENT_LIST_DIR}/../../) +endif() + +set(CMAKE_C_COMPILER ${WASI_SDK_PREFIX}/bin/clang${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_CXX_COMPILER ${WASI_SDK_PREFIX}/bin/clang++${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_ASM_COMPILER ${WASI_SDK_PREFIX}/bin/clang${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_AR ${WASI_SDK_PREFIX}/bin/llvm-ar${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_RANLIB ${WASI_SDK_PREFIX}/bin/llvm-ranlib${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_C_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) +set(CMAKE_ASM_COMPILER_TARGET ${triple}) + +# Don't look in the sysroot for executables to run during the build +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +# Only look in the sysroot (not in the host paths) for the rest +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/wasi-sdk-p3.cmake b/wasi-sdk-p3.cmake new file mode 100644 index 0000000..f737562 --- /dev/null +++ b/wasi-sdk-p3.cmake @@ -0,0 +1,37 @@ +# Cmake toolchain description file for the Makefile + +# Until Platform/WASI.cmake is upstream we need to inject the path to it +# into CMAKE_MODULE_PATH. +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") + +set(CMAKE_SYSTEM_NAME WASI) +set(CMAKE_SYSTEM_VERSION 1) +set(CMAKE_SYSTEM_PROCESSOR wasm32) +set(triple wasm32-wasip3) + +if(WIN32) + set(WASI_HOST_EXE_SUFFIX ".exe") +else() + set(WASI_HOST_EXE_SUFFIX "") +endif() + +# When building from source, WASI_SDK_PREFIX represents the generated directory +if(NOT WASI_SDK_PREFIX) + set(WASI_SDK_PREFIX ${CMAKE_CURRENT_LIST_DIR}/../../) +endif() + +set(CMAKE_C_COMPILER ${WASI_SDK_PREFIX}/bin/clang${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_CXX_COMPILER ${WASI_SDK_PREFIX}/bin/clang++${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_ASM_COMPILER ${WASI_SDK_PREFIX}/bin/clang${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_AR ${WASI_SDK_PREFIX}/bin/llvm-ar${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_RANLIB ${WASI_SDK_PREFIX}/bin/llvm-ranlib${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_C_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) +set(CMAKE_ASM_COMPILER_TARGET ${triple}) + +# Don't look in the sysroot for executables to run during the build +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +# Only look in the sysroot (not in the host paths) for the rest +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/wasi-sdk-pthread.cmake b/wasi-sdk-pthread.cmake new file mode 100644 index 0000000..c2cd83d --- /dev/null +++ b/wasi-sdk-pthread.cmake @@ -0,0 +1,40 @@ +# Cmake toolchain description file for the Makefile + +set(CMAKE_SYSTEM_NAME WASI) +set(CMAKE_SYSTEM_VERSION 1) +set(CMAKE_SYSTEM_PROCESSOR wasm32) +set(triple wasm32-wasi-threads) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") +# wasi-threads requires --import-memory. +# wasi requires --export-memory. +# (--export-memory is implicit unless --import-memory is given) +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--import-memory") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--export-memory") + +if(WIN32) + set(WASI_HOST_EXE_SUFFIX ".exe") +else() + set(WASI_HOST_EXE_SUFFIX "") +endif() + +# When building from source, WASI_SDK_PREFIX represents the generated directory +if(NOT WASI_SDK_PREFIX) + set(WASI_SDK_PREFIX ${CMAKE_CURRENT_LIST_DIR}/../../) +endif() + +set(CMAKE_C_COMPILER ${WASI_SDK_PREFIX}/bin/clang${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_CXX_COMPILER ${WASI_SDK_PREFIX}/bin/clang++${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_ASM_COMPILER ${WASI_SDK_PREFIX}/bin/clang${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_AR ${WASI_SDK_PREFIX}/bin/llvm-ar${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_RANLIB ${WASI_SDK_PREFIX}/bin/llvm-ranlib${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_C_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) +set(CMAKE_ASM_COMPILER_TARGET ${triple}) + +# Don't look in the sysroot for executables to run during the build +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +# Only look in the sysroot (not in the host paths) for the rest +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/wasi-sdk.cmake b/wasi-sdk.cmake new file mode 100644 index 0000000..2131cfc --- /dev/null +++ b/wasi-sdk.cmake @@ -0,0 +1,37 @@ +# Cmake toolchain description file for the Makefile + +# Until Platform/WASI.cmake is upstream we need to inject the path to it +# into CMAKE_MODULE_PATH. +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") + +set(CMAKE_SYSTEM_NAME WASI) +set(CMAKE_SYSTEM_VERSION 1) +set(CMAKE_SYSTEM_PROCESSOR wasm32) +set(triple wasm32-wasi) + +if(WIN32) + set(WASI_HOST_EXE_SUFFIX ".exe") +else() + set(WASI_HOST_EXE_SUFFIX "") +endif() + +# When building from source, WASI_SDK_PREFIX represents the generated directory +if(NOT WASI_SDK_PREFIX) + set(WASI_SDK_PREFIX ${CMAKE_CURRENT_LIST_DIR}/../../) +endif() + +set(CMAKE_C_COMPILER ${WASI_SDK_PREFIX}/bin/clang${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_CXX_COMPILER ${WASI_SDK_PREFIX}/bin/clang++${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_ASM_COMPILER ${WASI_SDK_PREFIX}/bin/clang${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_AR ${WASI_SDK_PREFIX}/bin/llvm-ar${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_RANLIB ${WASI_SDK_PREFIX}/bin/llvm-ranlib${WASI_HOST_EXE_SUFFIX}) +set(CMAKE_C_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) +set(CMAKE_ASM_COMPILER_TARGET ${triple}) + +# Don't look in the sysroot for executables to run during the build +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +# Only look in the sysroot (not in the host paths) for the rest +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/wasi-sdk.control b/wasi-sdk.control new file mode 100644 index 0000000..e3859e7 --- /dev/null +++ b/wasi-sdk.control @@ -0,0 +1,6 @@ +Package: wasi-sdk +Version: VERSION +Architecture: ARCH +Priority: optional +Description: Clang toolchain with wasm32-wasi default target, and the wasi sysroot +Maintainer: WASI SDK Maintainers