Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/testing-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,16 @@ jobs:
run: |
cmake -S . -B build -DHalide_TARGET=wasm-32-wasmrt-wasm_simd128-wasm_threads
cmake --build build --target build_generator

# HelloWasm is a super-build (not add_subdirectory()'d from apps/), so
# it needs its own configure/build/test rather than riding along with
# the "Configure/Build/Test apps" steps above.
- name: Configure HelloWasm
run: cmake -S . -B build -DCMAKE_PREFIX_PATH="${{ github.workspace }}/install"
working-directory: apps/HelloWasm

- name: Build and test HelloWasm
run: |
cmake --build build
ctest --test-dir build --output-on-failure
working-directory: apps/HelloWasm
1 change: 1 addition & 0 deletions apps/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ add_app(harris)
add_app(HelloBaremetal)
# add_app(HelloiOS) # don't build HelloiOS here because it isn't universal.
add_app(HelloPyTorch)
# add_app(HelloWasm) # driven by its own super-build; see apps/HelloWasm/README.md
add_app(hexagon_benchmarks)
add_app(hexagon_dma)
add_app(hist)
Expand Down
7 changes: 4 additions & 3 deletions apps/HelloBaremetal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,16 @@ located in `CMakeLists.txt` in the following sub directories.
3. cmake-external_project

If you want to understand the detail of build steps, please read "Cross
compiling" section of [README_cmake](../../README_cmake.md#cross-compiling), and
then `build.sh` and `CMakeLists.txt` in each sub directories.
compiling" section of
[HalideCMakePackage](../../doc/HalideCMakePackage.md#cross-compiling), and then
`build.sh` and `CMakeLists.txt` in each sub directories.

### Build procedure

#### Baremetal target

As a prerequisite, toolchain described above needs to be installed in your host
machine. The detail of the toolchanin configuration is set in
machine. The detail of the toolchain configuration is set in
[toolchain.arm-32-sample.cmake](cmake/toolchain.arm-32-sample.cmake), which you
might need to modify depending on the target baremetal system. Then, just run
the build script in one of the aforementioned sub directories.
Expand Down
3 changes: 0 additions & 3 deletions apps/HelloWasm/.gitignore

This file was deleted.

150 changes: 55 additions & 95 deletions apps/HelloWasm/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,102 +1,62 @@
cmake_minimum_required(VERSION 3.28)
project(HelloWasm)

enable_testing()

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS NO)

find_package(Halide REQUIRED)
set(halide_includes "$<TARGET_PROPERTY:Halide::Runtime,INTERFACE_INCLUDE_DIRECTORIES>")

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

configure_file(index.html index.html COPYONLY)

set(EMCC_FLAGS -s WASM=1 -s USE_SDL=2 -s TOTAL_MEMORY=512MB -O3)
set(EMCC_FLAGS_threads -pthread -matomics)

add_halide_generator(reaction_diffusion_generator SOURCES reaction_diffusion_generator.cpp)

set(features_simd -wasm_simd128)
set(features_threads -wasm_threads)
set(params_threads threads=true)
set(params threads=false)
foreach (simd IN ITEMS "" "_simd")
foreach (threads IN ITEMS "" "_threads")
set(config "wasm${simd}${threads}")
set(js_file "js/index${simd}${threads}.js")

set(target "wasm-32-wasmrt${features${simd}}${features${threads}}")

foreach (gen IN ITEMS init update render)
add_halide_library(
${config}_${gen}
FROM reaction_diffusion_generator
GENERATOR reaction_diffusion_${gen}
FUNCTION_NAME reaction_diffusion_${gen}
PARAMS ${params${threads}}
TARGETS ${target}
)
endforeach ()

set(pipeline_includes "$<TARGET_PROPERTY:${config}_init,INTERFACE_INCLUDE_DIRECTORIES>")
add_custom_command(
OUTPUT ${js_file}
COMMAND
"${EMCC}"
"$<SHELL_PATH:${CMAKE_CURRENT_SOURCE_DIR}/core.cpp>"
"$<TARGET_FILE:${config}_init>"
"$<TARGET_FILE:${config}_update>"
"$<TARGET_FILE:${config}_render>"
"$<TARGET_FILE:${config}_init.runtime>"
${EMCC_FLAGS}
${EMCC_FLAGS${threads}}
"-I$<JOIN:${halide_includes},;-I>"
"-I$<JOIN:${pipeline_includes},;-I>"
-o "${js_file}"
VERBATIM
DEPENDS ${config}_init ${config}_update ${config}_render
COMMAND_EXPAND_LISTS
)

add_custom_target(${config} ALL DEPENDS ${js_file})
endforeach ()
endforeach ()

foreach (gen IN ITEMS init update render)
add_halide_library(
reaction_diffusion_${gen}
FROM reaction_diffusion_generator
REGISTRATION ${gen}_cpp
PARAMS threads=true
)
endforeach ()

add_executable(run ${init_cpp} ${update_cpp} ${render_cpp})
target_link_libraries(
run
PRIVATE
Halide::RunGenMain reaction_diffusion_init reaction_diffusion_update reaction_diffusion_render
project(HelloWasm-Super)

include(ExternalProject)

# Halide generators always run on the host, regardless of the target they
# emit code for -- only the final app (core.cpp, linked with em++) actually
# needs the Emscripten toolchain. This follows the "Use a super-build"
# pattern in doc/HalideCMakePackage.md (see also
# apps/HelloBaremetal/cmake-super_build/ for the same pattern applied to a
# baremetal cross-compile).

# Prefer $EMSDK (set by sourcing emsdk_env.sh); PATH_SUFFIXES is what lets this
# find emcc without the PATH.
find_program(EMCC REQUIRED NAMES emcc HINTS ENV EMSDK PATH_SUFFIXES "upstream/emscripten")
get_filename_component(EMSCRIPTEN_ROOT "${EMCC}" DIRECTORY)
find_file(
EMSCRIPTEN_TOOLCHAIN_FILE REQUIRED
NAMES Emscripten.cmake
PATHS "${EMSCRIPTEN_ROOT}/cmake/Modules/Platform"
NO_DEFAULT_PATH
)

add_test(
NAME init_bench
COMMAND
run --benchmarks=all --benchmark_min_time=1 --name=reaction_diffusion_init
--output_extents=[1024,1024] --parsable_output
set(GEN_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/generator")

# Default to Release (both sub-projects pick up -O3 this way, standard CMake
# practice, rather than hardcoding optimization flags).
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif ()

# Step 1: build the generator (and the native benchmark) with the host compiler.
ExternalProject_Add(
generator_project
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/generator
BINARY_DIR ${GEN_BINARY_DIR}
INSTALL_COMMAND ""
CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}
CONFIGURE_HANDLED_BY_BUILD TRUE
)
add_test(
NAME update_bench
COMMAND
run --benchmarks=all --benchmark_min_time=1 --name=reaction_diffusion_update
--output_extents=[1024,1024] frame=0 mouse_x=0 mouse_y=0 state=random:0:[1024,1024,3]
--parsable_output

# Step 2: cross-compile the app with Emscripten, importing the generator
# built in step 1.
ExternalProject_Add(
app_project
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/app
BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/app
INSTALL_COMMAND ""
DEPENDS generator_project
CMAKE_ARGS
-DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN_TOOLCHAIN_FILE}
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}
-DHelloWasm-halide_generators_ROOT:FILEPATH=${GEN_BINARY_DIR}
CONFIGURE_HANDLED_BY_BUILD TRUE
)

enable_testing()
add_test(
NAME render_bench
COMMAND
run --benchmarks=all --benchmark_min_time=1 --name=reaction_diffusion_render
--output_extents=[1024,1024] state=random:0:[1024,1024,3] --parsable_output
NAME benchmarks
COMMAND ${CMAKE_CTEST_COMMAND} --test-dir ${GEN_BINARY_DIR} --output-on-failure
)
83 changes: 0 additions & 83 deletions apps/HelloWasm/Makefile

This file was deleted.

63 changes: 51 additions & 12 deletions apps/HelloWasm/README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,58 @@
This app is a demo of using the Halide webassembly backend
# HelloWasm

To try it out,
A demonstration of integrating a Halide pipeline into a WebAssembly application:
a reaction-diffusion simulation, AOT-compiled by Halide to four WASM variants
(scalar/SIMD x single/multi-threaded), linked into a browser demo with
Emscripten, and switched between at runtime based on what the browser actually
supports.

- install emscripten (see emscripten.org), and have emcc in your path.
## How the pieces fit together

- Build with `make all`
- `generator/reaction_diffusion_generator.cpp` defines the pipeline (three
Halide generators: `init`, `update`, `render`). Generators always run on the
*host*, regardless of what target they emit code for -- this is true even when
that target is `wasm-32-wasmrt`.
- `app/core.cpp` is the actual WASM application: an SDL2 program (compiled by
Emscripten) that calls into the AOT-compiled pipeline each frame and blits the
result to a `<canvas>`.
- Because the generator needs a host compiler and `core.cpp` needs the
Emscripten compiler, this is a CMake ["super-build"][super-build]: the
top-level `CMakeLists.txt` builds `generator/` with the host compiler first,
then configures `app/` under Emscripten's own CMake toolchain file, importing
the host-built generator to emit the pipeline for four `wasm-32-wasmrt`
variants (`FEATURES wasm_simd128`/`wasm_threads`, or neither).
- `index.html` picks one of the four compiled variants at load time based on
`?simd`/`?threads` URL parameters, falling back to the single-threaded scalar
build by default.

- Run a local webserver using, e.g.: python3 -m http.server 8080 &
## Building

- Load Google chrome (at least version 84), go to chrome://flags, and turn on
all the experimental webassembly stuff (e.g. threads, simd). If you don't do
this, only the single-threaded scalar variant will work (at the time of
writing).
Prerequisites: a Halide install (built via CMake -- see
`doc/BuildingHalideWithCMake.md`) and the [Emscripten SDK][emsdk]. The build
finds `emcc` via `$EMSDK` if you've sourced `emsdk_env.sh`, or otherwise via
`PATH`.

- Visit http://127.0.0.1:8080/index.html
```
cmake -S apps/HelloWasm -B build -DCMAKE_PREFIX_PATH=<path/to/halide install>
cmake --build build
ctest --test-dir build --output-on-failure
```

- Finally, run `make benchmark_native` and compare the performance you get with
native code
All build output (the four `index*.js`/`.wasm` pairs, plus copies of
`index.html`/`main.js`/`sw.js`) lands under `build/app/` -- nothing is ever
written back into the source tree.

## Running the demo

```
cd build/app
python3 -m http.server 8080
```

Then visit <http://127.0.0.1:8080/index.html>. The single-threaded scalar
variant should work in any modern browser; the SIMD and multi-threaded variants
may require enabling experimental WebAssembly features (e.g. in Chrome,
`chrome://flags`).

[emsdk]: https://emscripten.org/docs/getting_started/downloads.html
[super-build]: ../../doc/HalideCMakePackage.md#use-a-super-build
Loading
Loading