blob: 24a84be741a0834744676bf6d82ded9416e05102 [file] [edit]
# 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)