blob: d49d525fa0133769c4e677c20274221d3679a231 [file]
# 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
# iree_wasm_cc_library()
#
# CMake function for declaring JS companion files that accompany a C library
# compiled for wasm. These are the CMake equivalent of the Bazel
# iree_wasm_cc_library rule.
#
# Creates an INTERFACE library target with custom properties:
# IREE_WASM_JS_MODULE: the wasm import module name (e.g., "iree_syscall").
# IREE_WASM_JS_SRCS: semicolon-separated list of absolute paths to JS files.
# IREE_WASM_JS_ENTRIES: semicolon-separated list of "module:path" entries,
# propagated transitively via genex chains through iree_cc_library deps.
#
# Parameters:
# NAME: target name.
# MODULE: wasm import module name.
# SRCS: list of JS source files.
# DEPS: list of other iree_wasm_cc_library targets.
# PUBLIC: whether to export under iree::.
# TESTONLY: whether this is test-only.
#
# Example:
# iree_wasm_cc_library(
# NAME
# syscall_imports
# SRCS
# "src/syscall_imports.js"
# MODULE
# "iree_syscall"
# PUBLIC
# )
function(iree_wasm_cc_library)
cmake_parse_arguments(
_RULE
"PUBLIC;TESTONLY"
"PACKAGE;NAME;MODULE"
"SRCS;DEPS"
${ARGN}
)
if(_RULE_TESTONLY AND NOT IREE_BUILD_TESTS)
return()
endif()
# Prefix the library with the package name, so we get: iree_package_name.
if(_RULE_PACKAGE)
set(_PACKAGE_NS "${_RULE_PACKAGE}")
string(REPLACE "::" "_" _PACKAGE_NAME ${_RULE_PACKAGE})
else()
iree_package_ns(_PACKAGE_NS)
iree_package_name(_PACKAGE_NAME)
endif()
set(_NAME "${_PACKAGE_NAME}_${_RULE_NAME}")
# Replace dependencies passed by ::name with iree::package::name.
list(TRANSFORM _RULE_DEPS REPLACE "^::" "${_PACKAGE_NS}::")
# Create an INTERFACE library. This target carries no C compilation info —
# it exists only to participate in the dependency graph and carry JS file
# metadata via custom properties.
add_library(${_NAME} INTERFACE)
# Convert source file paths to absolute.
set(_ABS_SRCS)
foreach(_SRC ${_RULE_SRCS})
get_filename_component(_ABS_SRC "${_SRC}" ABSOLUTE)
list(APPEND _ABS_SRCS "${_ABS_SRC}")
endforeach()
# Build the module:path entries list for transitive propagation.
set(_JS_ENTRIES "")
foreach(_SRC ${_ABS_SRCS})
list(APPEND _JS_ENTRIES "${_RULE_MODULE}:${_SRC}")
endforeach()
# Set custom properties for wasm JS companion metadata.
set_target_properties(${_NAME} PROPERTIES
IREE_WASM_JS_MODULE "${_RULE_MODULE}"
IREE_WASM_JS_SRCS "${_ABS_SRCS}"
IREE_WASM_JS_ENTRIES "${_JS_ENTRIES}"
)
# Link deps so they're in the transitive dependency graph.
if(_RULE_DEPS)
target_link_libraries(${_NAME} INTERFACE ${_RULE_DEPS})
endif()
# IDE folder organization.
if(_RULE_PUBLIC)
set_property(TARGET ${_NAME} PROPERTY FOLDER ${IREE_IDE_FOLDER})
elseif(_RULE_TESTONLY)
set_property(TARGET ${_NAME} PROPERTY FOLDER ${IREE_IDE_FOLDER}/test)
else()
set_property(TARGET ${_NAME} PROPERTY FOLDER ${IREE_IDE_FOLDER}/internal)
endif()
# Install target into the export set so that PUBLIC cc_libraries that depend
# on this target can be exported without CMake complaining about missing
# transitive dependencies.
if(NOT _RULE_TESTONLY)
iree_install_targets(
TARGETS ${_NAME}
)
endif()
# Alias the target to iree::package::name.
iree_add_alias_library(${_PACKAGE_NS}::${_RULE_NAME} ${_NAME})
if(NOT "${_PACKAGE_NS}" STREQUAL "")
iree_package_dir(_PACKAGE_DIR)
if("${_RULE_NAME}" STREQUAL "${_PACKAGE_DIR}")
iree_add_alias_library(${_PACKAGE_NS} ${_NAME})
endif()
endif()
endfunction()
# iree_wasm_entry()
#
# CMake function for declaring the JS entry point for a wasm binary. These
# are the CMake equivalent of the Bazel iree_wasm_entry rule.
#
# Creates an INTERFACE library target with custom properties:
# IREE_WASM_ENTRY_MAIN: absolute path to the entry point .mjs file.
# IREE_WASM_ENTRY_SRCS: semicolon-separated list of absolute paths to
# local imports of the entry point (bundled at build time).
#
# Entry point discovery is done at generation time via direct dep checking
# in iree_cc_test / iree_wasm_cc_binary — no transitive propagation needed
# because the entry target is always a direct dep ("just add :main to deps").
#
# Parameters:
# NAME: target name.
# MAIN: entry point JS file.
# SRCS: local imports of the entry point.
# PUBLIC: whether to export under iree::.
#
# Example:
# iree_wasm_entry(
# NAME
# main
# MAIN
# "proactor_worker_main.mjs"
# SRCS
# "proactor_event_host.mjs"
# "proactor_ring.mjs"
# PUBLIC
# )
function(iree_wasm_entry)
cmake_parse_arguments(
_RULE
"PUBLIC;TESTONLY"
"PACKAGE;NAME;MAIN"
"SRCS"
${ARGN}
)
if(_RULE_TESTONLY AND NOT IREE_BUILD_TESTS)
return()
endif()
# Prefix the library with the package name.
if(_RULE_PACKAGE)
set(_PACKAGE_NS "${_RULE_PACKAGE}")
string(REPLACE "::" "_" _PACKAGE_NAME ${_RULE_PACKAGE})
else()
iree_package_ns(_PACKAGE_NS)
iree_package_name(_PACKAGE_NAME)
endif()
set(_NAME "${_PACKAGE_NAME}_${_RULE_NAME}")
# Create an INTERFACE library for dependency graph participation.
add_library(${_NAME} INTERFACE)
# Resolve entry point and source paths to absolute.
get_filename_component(_MAIN_ABS "${_RULE_MAIN}" ABSOLUTE)
set(_ABS_SRCS)
foreach(_SRC ${_RULE_SRCS})
get_filename_component(_ABS_SRC "${_SRC}" ABSOLUTE)
list(APPEND _ABS_SRCS "${_ABS_SRC}")
endforeach()
# Set custom properties for entry point metadata.
set_target_properties(${_NAME} PROPERTIES
IREE_WASM_ENTRY_MAIN "${_MAIN_ABS}"
IREE_WASM_ENTRY_SRCS "${_ABS_SRCS}"
)
# IDE folder organization.
if(_RULE_PUBLIC)
set_property(TARGET ${_NAME} PROPERTY FOLDER ${IREE_IDE_FOLDER})
elseif(_RULE_TESTONLY)
set_property(TARGET ${_NAME} PROPERTY FOLDER ${IREE_IDE_FOLDER}/test)
else()
set_property(TARGET ${_NAME} PROPERTY FOLDER ${IREE_IDE_FOLDER}/internal)
endif()
# Install target into the export set (same reason as iree_wasm_cc_library).
if(NOT _RULE_TESTONLY)
iree_install_targets(
TARGETS ${_NAME}
)
endif()
# Alias the target to iree::package::name.
iree_add_alias_library(${_PACKAGE_NS}::${_RULE_NAME} ${_NAME})
if(NOT "${_PACKAGE_NS}" STREQUAL "")
iree_package_dir(_PACKAGE_DIR)
if("${_RULE_NAME}" STREQUAL "${_PACKAGE_DIR}")
iree_add_alias_library(${_PACKAGE_NS} ${_NAME})
endif()
endif()
endfunction()
# _iree_wasm_setup_bundler()
#
# Sets up the wasm binary bundler for a target. Used by both iree_wasm_cc_binary
# and iree_cc_test (wasm path).
#
# This function:
# 1. Propagates IREE_WASM_JS_ENTRIES and IREE_WASM_JS_SRCS from deps using
# the same $<GENEX_EVAL:$<TARGET_PROPERTY:...>> pattern as
# _iree_cc_library_add_object_deps. This makes the collection fully
# order-independent — targets don't need to exist at configure time.
# 2. Discovers the entry point from direct deps via generation-time genexes.
# 3. Generates a manifest file via file(GENERATE) (evaluated at generation
# time, after all CMakeLists are processed).
# 4. Creates an add_custom_command to run the bundler at build time.
#
# Parameters:
# WASM_TARGET: name of the wasm executable target.
# OUTPUT: path for the output .mjs file.
# MAIN: explicit entry point (optional; discovered from deps if not set).
# DEPS: list of dependencies to collect wasm metadata from.
function(_iree_wasm_setup_bundler)
cmake_parse_arguments(
_RULE
""
"WASM_TARGET;OUTPUT;MAIN"
"DEPS"
${ARGN}
)
set(_BUNDLER "${CMAKE_SOURCE_DIR}/build_tools/wasm/wasm_binary_bundler.py")
set(_MANIFEST_FILE "${_RULE_OUTPUT}.modules")
# Propagate wasm JS metadata from deps via genex chains.
# At configure time, these are just recipe strings containing genex
# expressions. At generation time, CMake resolves the full chain — the dep
# targets don't need to exist when this code runs.
foreach(_DEP ${_RULE_DEPS})
set_property(TARGET ${_RULE_WASM_TARGET} APPEND PROPERTY
IREE_WASM_JS_ENTRIES
"$<GENEX_EVAL:$<TARGET_PROPERTY:${_DEP},IREE_WASM_JS_ENTRIES>>"
)
set_property(TARGET ${_RULE_WASM_TARGET} APPEND PROPERTY
IREE_WASM_JS_SRCS
"$<GENEX_EVAL:$<TARGET_PROPERTY:${_DEP},IREE_WASM_JS_SRCS>>"
)
endforeach()
# Resolve JS companion entries and source files at generation time.
set(_JS_ENTRIES_RAW "$<GENEX_EVAL:$<TARGET_PROPERTY:${_RULE_WASM_TARGET},IREE_WASM_JS_ENTRIES>>")
set(_JS_ENTRIES "$<FILTER:$<REMOVE_DUPLICATES:${_JS_ENTRIES_RAW}>,INCLUDE,.+>")
set(_JS_SRCS_RAW "$<GENEX_EVAL:$<TARGET_PROPERTY:${_RULE_WASM_TARGET},IREE_WASM_JS_SRCS>>")
set(_JS_SRCS "$<FILTER:$<REMOVE_DUPLICATES:${_JS_SRCS_RAW}>,INCLUDE,.+>")
# Generate the modules manifest at generation time (order-independent).
# Format: one "module:path" entry per line, read by the bundler.
string(ASCII 10 _NL)
file(GENERATE
OUTPUT "${_MANIFEST_FILE}"
CONTENT "$<JOIN:${_JS_ENTRIES},${_NL}>"
)
# Determine the entry point.
if(_RULE_MAIN)
# Explicit entry point (iree_wasm_cc_binary with MAIN parameter).
get_filename_component(_EFFECTIVE_MAIN "${_RULE_MAIN}" ABSOLUTE)
set(_ENTRY_SRCS "")
else()
# Discover entry point from direct deps via generation-time genexes.
# Each dep is checked for IREE_WASM_ENTRY_MAIN — the first non-empty
# value wins, falling back to the generic test harness.
set(_FALLBACK_MAIN "${CMAKE_SOURCE_DIR}/build_tools/wasm/wasm_test_main.mjs")
set(_EFFECTIVE_MAIN "${_FALLBACK_MAIN}")
set(_ENTRY_SRCS "")
foreach(_DEP ${_RULE_DEPS})
set(_DEP_ENTRY "$<TARGET_PROPERTY:${_DEP},IREE_WASM_ENTRY_MAIN>")
set(_EFFECTIVE_MAIN "$<IF:$<BOOL:${_DEP_ENTRY}>,${_DEP_ENTRY},${_EFFECTIVE_MAIN}>")
set(_DEP_SRCS "$<TARGET_PROPERTY:${_DEP},IREE_WASM_ENTRY_SRCS>")
set(_ENTRY_SRCS "$<IF:$<BOOL:${_DEP_SRCS}>,${_DEP_SRCS},${_ENTRY_SRCS}>")
endforeach()
endif()
# Run the bundler to produce the .mjs bundle.
add_custom_command(
OUTPUT "${_RULE_OUTPUT}"
COMMAND ${Python3_EXECUTABLE} "${_BUNDLER}"
--wasm "$<TARGET_FILE:${_RULE_WASM_TARGET}>"
--wasm-filename "$<TARGET_FILE_NAME:${_RULE_WASM_TARGET}>"
--main "${_EFFECTIVE_MAIN}"
--modules "${_MANIFEST_FILE}"
--output "${_RULE_OUTPUT}"
DEPENDS
${_RULE_WASM_TARGET}
"${_EFFECTIVE_MAIN}"
"${_BUNDLER}"
"${_MANIFEST_FILE}"
${_ENTRY_SRCS}
"${_JS_SRCS}"
COMMENT "Bundling JS companions for ${_RULE_WASM_TARGET}"
VERBATIM
)
endfunction()
# iree_wasm_cc_binary()
#
# Creates a wasm binary with bundled JS companions. CMake equivalent of the
# Bazel iree_wasm_cc_binary macro.
#
# This function creates two targets:
# {name}_wasm: the raw executable producing the wasm binary.
# {name}: custom target that runs the bundler to produce {name}.mjs.
#
# The bundler walks the transitive deps, collects all iree_wasm_cc_library
# targets, parses the wasm import section for dead code elimination, and
# concatenates the JS companions in dependency order with the entry point.
#
# Parameters:
# NAME: target name. The output is {name}.mjs.
# MAIN: entry point JS file. This is the JS equivalent of main.cc — it
# orchestrates wasm instantiation using the generated
# createWasmImports() function.
# SRCS: C/C++ source files for the wasm binary.
# DEPS: dependencies (both C libraries and iree_wasm_cc_library targets).
# COPTS: additional compiler options.
# DEFINES: preprocessor definitions.
# TESTONLY: whether this is test-only.
function(iree_wasm_cc_binary)
cmake_parse_arguments(
_RULE
"TESTONLY"
"PACKAGE;NAME;MAIN"
"SRCS;DEPS;COPTS;DEFINES"
${ARGN}
)
if(_RULE_TESTONLY AND NOT IREE_BUILD_TESTS)
return()
endif()
# Package naming.
if(_RULE_PACKAGE)
set(_PACKAGE_NS "${_RULE_PACKAGE}")
string(REPLACE "::" "_" _PACKAGE_NAME ${_RULE_PACKAGE})
else()
iree_package_ns(_PACKAGE_NS)
iree_package_name(_PACKAGE_NAME)
endif()
set(_NAME "${_PACKAGE_NAME}_${_RULE_NAME}")
set(_WASM_NAME "${_NAME}_wasm")
# Resolve dependencies passed by ::name with iree::package::name.
list(TRANSFORM _RULE_DEPS REPLACE "^::" "${_PACKAGE_NS}::")
# Create the wasm executable.
add_executable(${_WASM_NAME} ${_RULE_SRCS})
if(_RULE_COPTS)
target_compile_options(${_WASM_NAME} PRIVATE ${_RULE_COPTS})
endif()
if(_RULE_DEFINES)
target_compile_definitions(${_WASM_NAME} PRIVATE ${_RULE_DEFINES})
endif()
if(_RULE_DEPS)
target_link_libraries(${_WASM_NAME} PRIVATE ${_RULE_DEPS})
endif()
set(_OUTPUT_MJS "${CMAKE_CURRENT_BINARY_DIR}/${_RULE_NAME}.mjs")
_iree_wasm_setup_bundler(
WASM_TARGET ${_WASM_NAME}
OUTPUT "${_OUTPUT_MJS}"
MAIN "${_RULE_MAIN}"
DEPS ${_RULE_DEPS}
)
add_custom_target(${_NAME} ALL DEPENDS "${_OUTPUT_MJS}")
endfunction()
# iree_wasm_cc_test()
#
# Same as iree_wasm_cc_binary, plus a ctest test that runs the bundle via
# Node.js. The test passes if the .mjs entry point exits with code 0.
#
# Parameters: same as iree_wasm_cc_binary.
function(iree_wasm_cc_test)
cmake_parse_arguments(
_RULE
"TESTONLY"
"PACKAGE;NAME;MAIN"
"SRCS;DEPS;COPTS;DEFINES"
${ARGN}
)
# Build the bundle using iree_wasm_cc_binary.
iree_wasm_cc_binary(${ARGN})
# Derive the output .mjs path (must match iree_wasm_cc_binary's output).
set(_OUTPUT_MJS "${CMAKE_CURRENT_BINARY_DIR}/${_RULE_NAME}.mjs")
# Register a ctest that runs node on the bundle.
if(_RULE_PACKAGE)
set(_PACKAGE_NS "${_RULE_PACKAGE}")
string(REPLACE "::" "_" _PACKAGE_NAME ${_RULE_PACKAGE})
else()
iree_package_ns(_PACKAGE_NS)
iree_package_name(_PACKAGE_NAME)
endif()
set(_TEST_NAME "${_PACKAGE_NAME}_${_RULE_NAME}")
find_program(_NODE_EXECUTABLE node REQUIRED)
add_test(
NAME "${_TEST_NAME}"
COMMAND "${_NODE_EXECUTABLE}" "${_OUTPUT_MJS}"
)
endfunction()