| # Copyright 2026 The IREE Authors |
| # |
| # Licensed under the Apache License v2.0 with LLVM Exceptions. |
| # See https://llvm.org/LICENSE.txt for license information. |
| # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| |
| cmake_minimum_required(VERSION 3.26) |
| |
| # CMake invokes the toolchain file twice during the first build, but only once |
| # during subsequent rebuilds. This was causing the various flags to be added |
| # twice on the first build, and on a rebuild ninja would see only one set of the |
| # flags and rebuild the world. |
| # https://github.com/android-ndk/ndk/issues/323 |
| if(IREE_WASM32_TOOLCHAIN_INCLUDED) |
| return() |
| endif() |
| set(IREE_WASM32_TOOLCHAIN_INCLUDED true) |
| |
| # Wasm32 is freestanding — no OS, no libc by default. |
| set(CMAKE_SYSTEM_NAME Generic) |
| set(CMAKE_SYSTEM_PROCESSOR wasm32) |
| |
| #------------------------------------------------------------------------------- |
| # wasi-sdk download |
| #------------------------------------------------------------------------------- |
| |
| # Parse version and SHA-256 from the shared Bazel/CMake config file. |
| # This is the single source of truth — do not duplicate version constants here. |
| set(_WASI_SDK_VERSION_FILE "${CMAKE_CURRENT_LIST_DIR}/../wasm/wasi_sdk_version.bzl") |
| file(READ "${_WASI_SDK_VERSION_FILE}" _WASI_SDK_BZL) |
| |
| string(REGEX MATCH "WASI_SDK_VERSION = \"([^\"]+)\"" _ "${_WASI_SDK_BZL}") |
| set(_WASI_SDK_VERSION "${CMAKE_MATCH_1}") |
| string(REGEX MATCH "WASI_SDK_TAG = \"([^\"]+)\"" _ "${_WASI_SDK_BZL}") |
| set(_WASI_SDK_TAG "${CMAKE_MATCH_1}") |
| |
| # Detect host platform. |
| if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") |
| set(_WASI_SDK_OS "linux") |
| elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") |
| set(_WASI_SDK_OS "macos") |
| else() |
| message(FATAL_ERROR "wasi-sdk: unsupported host OS: ${CMAKE_HOST_SYSTEM_NAME}") |
| endif() |
| |
| cmake_host_system_information(RESULT _HOST_ARCH QUERY OS_PLATFORM) |
| if(_HOST_ARCH MATCHES "x86_64|AMD64") |
| set(_WASI_SDK_ARCH "x86_64") |
| elseif(_HOST_ARCH MATCHES "aarch64|arm64") |
| set(_WASI_SDK_ARCH "arm64") |
| else() |
| message(FATAL_ERROR "wasi-sdk: unsupported host architecture: ${_HOST_ARCH}") |
| endif() |
| |
| # Extract the SHA-256 hash for this platform from the .bzl dict. |
| set(_WASI_SDK_PLATFORM_KEY "${_WASI_SDK_ARCH}-${_WASI_SDK_OS}") |
| string(REGEX MATCH "\"${_WASI_SDK_PLATFORM_KEY}\": \"([^\"]+)\"" _ "${_WASI_SDK_BZL}") |
| set(_WASI_SDK_SHA256 "${CMAKE_MATCH_1}") |
| if("${_WASI_SDK_SHA256}" STREQUAL "") |
| message(FATAL_ERROR "wasi-sdk: no SHA-256 hash for platform ${_WASI_SDK_PLATFORM_KEY}") |
| endif() |
| |
| #------------------------------------------------------------------------------- |
| # Compiler tools |
| #------------------------------------------------------------------------------- |
| |
| # Users can provide IREE_WASI_SDK_ROOT to skip the download. |
| # Otherwise we download wasi-sdk into the build directory. |
| if(NOT "${IREE_WASI_SDK_ROOT}" STREQUAL "") |
| set(_WASI_SDK_ROOT "${IREE_WASI_SDK_ROOT}") |
| else() |
| # Download wasi-sdk into the build tree (idempotent via stamp file). |
| set(_WASI_SDK_BASENAME "wasi-sdk-${_WASI_SDK_VERSION}-${_WASI_SDK_ARCH}-${_WASI_SDK_OS}") |
| set(_WASI_SDK_URL |
| "https://github.com/WebAssembly/wasi-sdk/releases/download/${_WASI_SDK_TAG}/${_WASI_SDK_BASENAME}.tar.gz") |
| |
| # Download location: inside the build directory so it persists across |
| # reconfigures without leaving generated files in the source tree. |
| get_filename_component(_WASI_SDK_DOWNLOAD_DIR "${CMAKE_BINARY_DIR}/wasi-sdk" ABSOLUTE) |
| set(_WASI_SDK_ROOT "${_WASI_SDK_DOWNLOAD_DIR}/${_WASI_SDK_BASENAME}") |
| set(_WASI_SDK_ARCHIVE "${_WASI_SDK_DOWNLOAD_DIR}/${_WASI_SDK_BASENAME}.tar.gz") |
| set(_WASI_SDK_STAMP "${_WASI_SDK_DOWNLOAD_DIR}/${_WASI_SDK_BASENAME}.stamp") |
| set(_WASI_SDK_STAMP_CONTENT "${_WASI_SDK_URL} : ${_WASI_SDK_SHA256}") |
| |
| set(_NEEDS_DOWNLOAD ON) |
| if(EXISTS "${_WASI_SDK_STAMP}" AND IS_DIRECTORY "${_WASI_SDK_ROOT}") |
| file(READ "${_WASI_SDK_STAMP}" _STAMP_CONTENTS) |
| if("${_STAMP_CONTENTS}" STREQUAL "${_WASI_SDK_STAMP_CONTENT}") |
| set(_NEEDS_DOWNLOAD OFF) |
| endif() |
| endif() |
| |
| if(_NEEDS_DOWNLOAD) |
| message(STATUS "Downloading wasi-sdk ${_WASI_SDK_VERSION} for ${_WASI_SDK_PLATFORM_KEY} from ${_WASI_SDK_URL}") |
| file(MAKE_DIRECTORY "${_WASI_SDK_DOWNLOAD_DIR}") |
| file(DOWNLOAD "${_WASI_SDK_URL}" "${_WASI_SDK_ARCHIVE}" |
| EXPECTED_HASH SHA256=${_WASI_SDK_SHA256} |
| SHOW_PROGRESS) |
| message(STATUS "Extracting wasi-sdk to ${_WASI_SDK_ROOT}") |
| file(ARCHIVE_EXTRACT INPUT "${_WASI_SDK_ARCHIVE}" DESTINATION "${_WASI_SDK_DOWNLOAD_DIR}") |
| file(REMOVE "${_WASI_SDK_ARCHIVE}") |
| file(WRITE "${_WASI_SDK_STAMP}" "${_WASI_SDK_STAMP_CONTENT}") |
| else() |
| message(STATUS "Using cached wasi-sdk at ${_WASI_SDK_ROOT}") |
| endif() |
| endif() |
| |
| set(CMAKE_C_COMPILER "${_WASI_SDK_ROOT}/bin/clang") |
| set(CMAKE_CXX_COMPILER "${_WASI_SDK_ROOT}/bin/clang++") |
| set(CMAKE_AR "${_WASI_SDK_ROOT}/bin/llvm-ar") |
| set(CMAKE_RANLIB "${_WASI_SDK_ROOT}/bin/llvm-ranlib") |
| set(CMAKE_STRIP "${_WASI_SDK_ROOT}/bin/llvm-strip") |
| set(CMAKE_LINKER "${_WASI_SDK_ROOT}/bin/wasm-ld") |
| |
| # CMake's Generic platform module doesn't try test compiles by default, |
| # but we need to tell it our compiler works for the wasm32 target. |
| set(CMAKE_C_COMPILER_WORKS ON) |
| set(CMAKE_CXX_COMPILER_WORKS ON) |
| |
| #------------------------------------------------------------------------------- |
| # Compiler and linker flags |
| #------------------------------------------------------------------------------- |
| |
| # Freestanding libc headers. By default this points at IREE's minimal libc |
| # shim, but embedders can set IREE_WASM_LIBC_INCLUDE_DIR to their own libc |
| # headers or to an empty string to provide include paths entirely out-of-band. |
| set(IREE_WASM_LIBC_INCLUDE_DIR |
| "${CMAKE_CURRENT_LIST_DIR}/../wasm/libc/include" |
| CACHE PATH |
| "Header directory for the freestanding WebAssembly C library. Empty disables the built-in include path.") |
| set(_IREE_WASM_LIBC_INCLUDE_FLAG "") |
| if(IREE_WASM_LIBC_INCLUDE_DIR) |
| get_filename_component( |
| IREE_WASM_LIBC_INCLUDE_DIR |
| "${IREE_WASM_LIBC_INCLUDE_DIR}" |
| ABSOLUTE |
| ) |
| set(_IREE_WASM_LIBC_INCLUDE_FLAG |
| "-isystem ${IREE_WASM_LIBC_INCLUDE_DIR}") |
| endif() |
| |
| set(IREE_WASM32_COMPILE_FLAGS "\ |
| --target=wasm32-unknown-unknown \ |
| -nostdinc \ |
| -nostdlib \ |
| -ffreestanding \ |
| ${_IREE_WASM_LIBC_INCLUDE_FLAG} \ |
| -fno-exceptions \ |
| -fno-rtti \ |
| -fvisibility=hidden \ |
| -fno-short-wchar \ |
| -mbulk-memory \ |
| -msign-ext \ |
| -mnontrapping-fptoint \ |
| -DIREE_SYNCHRONIZATION_DISABLE_UNSAFE=1 \ |
| -DIREE_THREADING_ENABLE=0 \ |
| -DIREE_PLATFORM_WEB=1") |
| |
| # Linker flags use -Wl, prefix because CMake invokes clang (not wasm-ld |
| # directly) as the linker driver. Clang strips the prefix and forwards |
| # to wasm-ld. |
| set(IREE_WASM32_LINK_FLAGS "\ |
| --target=wasm32-unknown-unknown \ |
| -nostdlib \ |
| -fuse-ld=lld \ |
| -Wl,--import-memory \ |
| -Wl,--no-entry \ |
| -Wl,--export-dynamic \ |
| -Wl,--allow-undefined \ |
| -Wl,--max-memory=4294967296") |
| |
| set(CMAKE_C_FLAGS "${IREE_WASM32_COMPILE_FLAGS} ${CMAKE_C_FLAGS}") |
| set(CMAKE_CXX_FLAGS "${IREE_WASM32_COMPILE_FLAGS} ${CMAKE_CXX_FLAGS}") |
| set(CMAKE_ASM_FLAGS "${IREE_WASM32_COMPILE_FLAGS} ${CMAKE_ASM_FLAGS}") |
| set(CMAKE_EXE_LINKER_FLAGS "${IREE_WASM32_LINK_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}") |
| set(CMAKE_SHARED_LINKER_FLAGS "${IREE_WASM32_LINK_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}") |
| set(CMAKE_MODULE_LINKER_FLAGS "${IREE_WASM32_LINK_FLAGS} ${CMAKE_MODULE_LINKER_FLAGS}") |
| |
| #------------------------------------------------------------------------------- |
| # IREE build configuration |
| #------------------------------------------------------------------------------- |
| |
| set(CMAKE_CROSSCOMPILING ON CACHE BOOL "") |
| set(IREE_ENABLE_THREADING OFF CACHE BOOL "" FORCE) |
| set(IREE_SYNCHRONIZATION_DISABLE_UNSAFE ON CACHE BOOL "" FORCE) |
| |
| # The compiler is not cross-compiled — it runs on the host. Either use |
| # pre-built host binaries (IREE_HOST_BIN_DIR) or an installed compiler. |
| set(IREE_BUILD_COMPILER OFF CACHE BOOL "" FORCE) |
| |
| # Wasm has no filesystem, no dynamic library loading. |
| set(IREE_BUILD_TESTS OFF CACHE BOOL "" FORCE) |
| set(IREE_BUILD_SAMPLES OFF CACHE BOOL "" FORCE) |
| set(IREE_BUILD_BINDINGS_TFLITE OFF CACHE BOOL "" FORCE) |
| set(IREE_BUILD_BINDINGS_TFLITE_JAVA OFF CACHE BOOL "" FORCE) |
| |
| # HAL configuration: local-sync only. local-task requires a thread-safe wasm |
| # libc/heap/TLS contract that this freestanding toolchain does not provide yet. |
| set(IREE_HAL_DRIVER_DEFAULTS OFF CACHE BOOL "" FORCE) |
| set(IREE_HAL_DRIVER_LOCAL_SYNC ON CACHE BOOL "" FORCE) |
| set(IREE_HAL_DRIVER_LOCAL_TASK OFF CACHE BOOL "" FORCE) |
| |
| # Executable loaders are opt-in for freestanding wasm. HAL transfer-only |
| # programs, such as samples/hal/hello, do not need executable loaders, and the |
| # VMVX loader pulls host schema-generation tooling into the cross build. |
| # Embedded ELF doesn't apply to wasm — wasm modules are the executable format. |
| set(IREE_HAL_EXECUTABLE_LOADER_DEFAULTS OFF CACHE BOOL "" FORCE) |
| set(IREE_HAL_EXECUTABLE_LOADER_VMVX_MODULE OFF CACHE BOOL "" FORCE) |
| set(IREE_HAL_EXECUTABLE_PLUGIN_DEFAULTS OFF CACHE BOOL "" FORCE) |