Raw File
CMakeLists.txt
include(FetchContent)
include(CMakeDependentOption)

cmake_dependent_option(WITH_WABT "Include WABT Interpreter for WASM testing" ON "TARGET_WEBASSEMBLY" OFF)
cmake_dependent_option(WITH_WASM_SHELL "Download a wasm shell (e.g. d8) for testing AOT wasm code." ON "TARGET_WEBASSEMBLY" OFF)

if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
    if (WITH_WABT)
        message(STATUS "WITH_WABT is not yet supported on Windows")
        set(WITH_WABT OFF CACHE BOOL "WITH_WABT is not yet supported on Windows" FORCE)
    endif ()

    if (WITH_WASM_SHELL)
        message(STATUS "WITH_WASM_SHELL is not yet supported on Windows")
        set(WITH_WASM_SHELL OFF CACHE BOOL "WITH_WASM_SHELL is not yet supported on Windows" FORCE)
    endif ()
endif ()

if (WITH_WABT)
    set(WABT_VER 1.0.22)

    message(STATUS "Fetching WABT ${WABT_VER}...")
    FetchContent_Declare(wabt
                         GIT_REPOSITORY https://github.com/WebAssembly/wabt.git
                         GIT_TAG ${WABT_VER}
                         GIT_SHALLOW TRUE
                         GIT_SUBMODULES "")

    # configuration for wabt
    set(WITH_EXCEPTIONS ${Halide_ENABLE_EXCEPTIONS})
    set(BUILD_TESTS OFF)
    set(BUILD_TOOLS OFF)
    set(BUILD_LIBWASM OFF)
    FetchContent_MakeAvailable(wabt)

    set_target_properties(wabt PROPERTIES POSITION_INDEPENDENT_CODE ON)

    # Disable this very-noisy warning in GCC
    target_compile_options(wabt
                           PRIVATE
                           $<$<CXX_COMPILER_ID:GNU>:-Wno-alloca-larger-than>)

    # TODO: we want to require unique prefixes to include these files, to avoid ambiguity;
    # this means we have to prefix with "wabt-src/...", which is less bad than other alternatives,
    # but perhaps we could do better (esp. if wabt was smarter about what it exposed?)
    add_library(Halide_wabt INTERFACE)
    target_sources(Halide_wabt INTERFACE $<BUILD_INTERFACE:$<TARGET_OBJECTS:wabt>>)
    target_include_directories(Halide_wabt
                               SYSTEM # Use -isystem instead of -I; this is a trick so that clang-tidy won't analyze these includes
                               INTERFACE
                               $<BUILD_INTERFACE:${wabt_SOURCE_DIR}>
                               $<BUILD_INTERFACE:${wabt_BINARY_DIR}>
                               $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/_deps>)
    set_target_properties(Halide_wabt PROPERTIES EXPORT_NAME wabt)
endif ()

if (WITH_WASM_SHELL)
    # Even if we have the latest Emscripten SDK installed, we can't rely on it having
    # an up-to-date shell for running wasm; it includes a version of Node.js that usually
    # lags on wasm updates, so we need something more recent to reliably test wasm.
    # We'll go with a predictable version of d8 (the command-line shell for v8); this
    # is directly mimicking the approach useed by jsvu.
    # These are hosted at
    if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
        set(WASM_SHELL_PLATFORM "win64")
    elseif ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Darwin")
        set(WASM_SHELL_PLATFORM "mac64")
    else ()
        set(WASM_SHELL_PLATFORM "linux64")
    endif ()

    # We want to deliberately choose a stable version (rather than top-of-tree);
    # this might be a canary version (if needed to get the updates to v8 that we need)
    # but should be carefully tested before landing.
    #
    # Note that V8 9.1+ is apparently first version of V8/D8 that really does properly
    # implement the final WASM SIMD spec; versions 8.9 and 9.0 weren't quite complete
    # (there were some opcode renumberings) so they should be specifically avoided.
    # As of this writing, v9.1 is still in beta; the shell version below should be updated
    # to a proper release version (vs canary) once it is final.
    #
    # see https://github.com/WebAssembly/simd/blob/master/proposals/simd/ImplementationStatus.md
    set(WASM_SHELL_VERSION 9.1.269)
    set(WASM_SHELL_URL "https://storage.googleapis.com/chromium-v8/official/canary/v8-${WASM_SHELL_PLATFORM}-rel-${WASM_SHELL_VERSION}.zip")
    message(STATUS "Fetching WASM_SHELL ${WASM_SHELL_URL}...")
    FetchContent_Declare(wasm_shell URL "${WASM_SHELL_URL}")
    FetchContent_MakeAvailable(wasm_shell)

    find_program(D8_PATH d8
                 HINTS "${CMAKE_BINARY_DIR}/_deps/wasm_shell-src")

    if (NOT D8_PATH)
        message(FATAL_ERROR "Could not find or download D8 WASM shell")
    endif ()

    add_executable(d8 IMPORTED GLOBAL)
    set_target_properties(d8 PROPERTIES IMPORTED_LOCATION "${D8_PATH}")
endif ()

function(add_wasm_executable TARGET)
    set(options)
    set(oneValueArgs)
    set(multiValueArgs SRCS DEPS INCLUDES ENABLE_IF)
    cmake_parse_arguments(args "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

    if (args_ENABLE_IF AND NOT (${args_ENABLE_IF}))
        return()
    endif ()

    # Conceptually, we want something like this:
    # add_executable(${TARGET} ${args_SRCS})
    # if (args_INCLUDES)
    #     target_include_directories("${TARGET}" PRIVATE ${args_INCLUDES})
    # endif()
    # if (args_DEPS)
    #     target_link_libraries(${TARGET} PRIVATE ${args_DEPS})
    # endif ()

    find_program(EMCC emcc HINTS "$ENV{EMSDK}/upstream/emscripten")

    if (NOT EMCC)
        message(FATAL_ERROR "Building tests or apps for WASM requires that EMSDK point to a valid Emscripten install.")
    endif ()

    set(EMCC_FLAGS
        -O3
        -g
        -std=c++17
        -Wall
        -Wcast-qual
        -Werror
        -Wignored-qualifiers
        -Wno-comment
        -Wno-psabi
        -Wno-unknown-warning-option
        -Wno-unused-function
        -Wsign-compare
        -Wsuggest-override
        -s ASSERTIONS=1
        -s ALLOW_MEMORY_GROWTH=1
        -s WASM_BIGINT=1
        -s STANDALONE_WASM=1
        -s ENVIRONMENT=shell)

    set(SRCS)
    foreach (S IN LISTS args_SRCS)
        list(APPEND SRCS "${CMAKE_CURRENT_SOURCE_DIR}/${S}")
    endforeach ()

    set(INCLUDES)
    foreach (I IN LISTS args_INCLUDES)
        list(APPEND INCLUDES "-I${I}")
    endforeach ()

    set(DEPS)
    foreach (D IN LISTS args_DEPS)
        list(APPEND DEPS $<TARGET_FILE:${D}>)
    endforeach ()

    add_custom_command(OUTPUT "${TARGET}.wasm" "${TARGET}.js"
                       COMMAND ${EMCC} ${EMCC_FLAGS} ${INCLUDES} ${SRCS} ${DEPS} -o "${TARGET}.js"
                       DEPENDS ${SRCS} ${DEPS}
                       VERBATIM)

    add_custom_target("${TARGET}" ALL
                      DEPENDS "${TARGET}.wasm" "${TARGET}.js")

endfunction()

function(add_wasm_halide_test TARGET)
    set(options)
    set(oneValueArgs)
    set(multiValueArgs GROUPS ENABLE_IF)
    cmake_parse_arguments(args "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

    if (args_ENABLE_IF AND NOT (${args_ENABLE_IF}))
        return()
    endif ()

    if (NOT WITH_WASM_SHELL)
        message(FATAL_ERROR "WITH_WASM_SHELL must be enabled if testing AOT WASM code.")
    endif ()

    set(WASM_SHELL_FLAGS)
    if (Halide_TARGET MATCHES "wasm_simd128")
        list(APPEND WASM_SHELL_FLAGS "--experimental-wasm-simd")
    endif ()
    if (Halide_TARGET MATCHES "wasm_threads")
        # wasm_threads requires compilation with Emscripten, against a *browser*
        # environment rather than a shell environment. (This is because the 'pthreads'
        # support is an elaborate wrapper than Emscripten provides around WebWorkers.)
        # The version of d8/v8 that we pull does provide enough of a browser-like
        # environment to support this, but many shell tools (e.g. wabt-interp) don't,
        # so if you use a different WASM_SHELL, you may have to disable this.
        list(APPEND WASM_SHELL_FLAGS "--experimental-wasm-threads")
    endif ()

    add_halide_test("${TARGET}"
                    GROUPS ${args_GROUPS}
                    COMMAND d8 ${WASM_SHELL_FLAGS} "${TARGET}.js")
endfunction()
back to top