Skip to content

Commit

Permalink
Merge pull request #413 from ChinYikMing/serve-wasm
Browse files Browse the repository at this point in the history
Support hosting WebAssembly demo page locally
  • Loading branch information
jserv authored Apr 9, 2024
2 parents 1c776f9 + 935121d commit 9ea1e62
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 56 deletions.
61 changes: 5 additions & 56 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ CONFIG_FILE := $(OUT)/.config
CFLAGS = -std=gnu99 -O2 -Wall -Wextra
CFLAGS += -Wno-unused-label
CFLAGS += -include src/common.h
CFLAGS_emcc ?=

# Enable link-time optimization (LTO)
ENABLE_LTO ?= 1
Expand Down Expand Up @@ -141,20 +140,6 @@ endif
# For tail-call elimination, we need a specific set of build flags applied.
# FIXME: On macOS + Apple Silicon, -fno-stack-protector might have a negative impact.

# Enable tail-call for emcc
ifeq ("$(CC_IS_EMCC)", "1")
CFLAGS += -mtail-call
endif

# Build emscripten-port SDL
ifeq ("$(CC_IS_EMCC)", "1")
ifeq ($(call has, SDL), 1)
CFLAGS_emcc += -sUSE_SDL=2 -sSDL2_MIXER_FORMATS=wav,mid -sUSE_SDL_MIXER=2
OBJS_EXT += syscall_sdl.o
LDFLAGS += -pthread
endif
endif

ENABLE_UBSAN ?= 0
ifeq ("$(ENABLE_UBSAN)", "1")
CFLAGS += -fsanitize=undefined -fno-sanitize=alignment -fno-sanitize-recover=all
Expand All @@ -167,15 +152,12 @@ $(OUT)/emulate.o: CFLAGS += -foptimize-sibling-calls -fomit-frame-pointer -fno-s
# to the first target after .DEFAULT_GOAL is not set.
.DEFAULT_GOAL :=

WEB_FILES += $(BIN).js \
$(BIN).wasm \
$(BIN).worker.js
ifeq ("$(CC_IS_EMCC)", "1")
BIN := $(BIN).js
endif

all: config $(BIN)

include mk/external.mk

include mk/wasm.mk

OBJS := \
map.o \
utils.o \
Expand All @@ -193,30 +175,6 @@ OBJS := \
OBJS := $(addprefix $(OUT)/, $(OBJS))
deps := $(OBJS:%.o=%.o.d)

include mk/external.mk

deps_emcc :=
ASSETS := assets
WEB_JS_RESOURCES := $(ASSETS)/js
EXPORTED_FUNCS := _main,_indirect_rv_halt
ifeq ("$(CC_IS_EMCC)", "1")
CFLAGS_emcc += -sINITIAL_MEMORY=2GB \
-sALLOW_MEMORY_GROWTH \
-s"EXPORTED_FUNCTIONS=$(EXPORTED_FUNCS)" \
-sSTACK_SIZE=4MB \
-sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency \
--embed-file build@/ \
--embed-file build/timidity@/etc/timidity \
-DMEM_SIZE=0x40000000 \
-DCYCLE_PER_STEP=2000000 \
--pre-js $(WEB_JS_RESOURCES)/pre.js \
-O3 \
-w

# used to download all dependencies of elf executable and bundle into single wasm
deps_emcc += $(DOOM_DATA) $(QUAKE_DATA) $(TIMIDITY_DATA)
endif

$(OUT)/%.o: src/%.c $(deps_emcc)
$(VECHO) " CC\t$@\n"
$(Q)$(CC) -o $@ $(CFLAGS) $(CFLAGS_emcc) -c -MMD -MF $@.d $<
Expand Down Expand Up @@ -276,22 +234,12 @@ misalign: $(BIN)
# Non-trivial demonstration programs
ifeq ($(call has, SDL), 1)
doom_action := (cd $(OUT); ../$(BIN) doom.elf)
ifeq ("$(CC_IS_EMCC)", "1")
# TODO: check Chrome or Firefox is available and serve python httpd and open the web page
# TODO: serve and open a web page, show warning if environment not support pthread runtime
doom_action :=
endif
doom_deps += $(DOOM_DATA) $(BIN)
doom: $(doom_deps)
$(doom_action)

ifeq ($(call has, EXT_F), 1)
quake_action := (cd $(OUT); ../$(BIN) quake.elf)
ifeq ("$(CC_IS_EMCC)", "1")
# TODO: check Chrome or Firefox is available and serve python httpd and open the web page
# TODO: serve and open a web page, show warning if environment not support pthread runtime
quake_action :=
endif
quake_deps += $(QUAKE_DATA) $(BIN)
quake: $(quake_deps)
$(quake_action)
Expand All @@ -304,6 +252,7 @@ distclean: clean
-$(RM) $(DOOM_DATA) $(QUAKE_DATA)
$(RM) -r $(TIMIDITY_DATA)
$(RM) -r $(OUT)/id1
$(RM) -r $(DEMO_DIR)
$(RM) *.zip
$(RM) -r $(OUT)/mini-gdbstub
-$(RM) $(OUT)/.config
Expand Down
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,35 @@ To analyze the profiling data, use the `rv_profiler` tool with the desired optio
$ tools/rv_profiler [--start-address|--stop-address|--graph-ir] [test_program]
```

## WebAssembly Translation
### Build and run
`rv32emu` relies on [Emscripten](https://emscripten.org/docs/getting_started/downloads.html) to be compiled to WebAssembly. Thus, the target system should have
the Emscripten version 3.1.51 installed.

Moreover, `rv32emu` leverages the tail call optimization(TCO) strategy and we have tested the WebAssembly
execution in Chrome with at least MAJOR 112 and Firefox with at least MAJOR 121 since they supports
tail call feature. Thus, please check and update your browsers if necessary or install the suitable browsers
before going further.

Source your Emscripten SDK environment before make. For macOS and Linux user:
```shell
$ source ~/emsdk/emsdk_env.sh
```
Change the Emscripten SDK environment path if necessary.

At this point, you can build and start a web server service to serve WebAssembly by running:
```shell
$ make CC=emcc start-web
```
You would see the server's IP:PORT in your terminal. Copy and paste it to the browsers and
you just access the index page of `rv32emu`.

### Index page
You would see a dropdown menu which you can use to select the ELF executable. Select one and
click the Run button to run it.

Alternatively, you may want to view a hosted `rv32emu` [demo page](https://sysprog21.github.io/rv32emu-demo/) since building takes some time.

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines.
Expand Down
3 changes: 3 additions & 0 deletions mk/common.mk
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ endef
# Test suite
PASS_COLOR = \e[32;01m
NO_COLOR = \e[0m
GREEN = \033[32m
YELLOW = \033[33m
NC = \033[0m

notice = $(PRINTF) "$(PASS_COLOR)$(strip $1)$(NO_COLOR)\n"

Expand Down
104 changes: 104 additions & 0 deletions mk/wasm.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
CFLAGS_emcc ?=
deps_emcc :=
ASSETS := assets
WEB_HTML_RESOURCES := $(ASSETS)/html
WEB_JS_RESOURCES := $(ASSETS)/js
EXPORTED_FUNCS := _main,_indirect_rv_halt

ifeq ("$(CC_IS_EMCC)", "1")
WEB_FILES := $(BIN).js \
$(BIN).wasm \
$(BIN).worker.js
BIN := $(BIN).js

# TCO
CFLAGS += -mtail-call

# Build emscripten-port SDL
ifeq ($(call has, SDL), 1)
CFLAGS_emcc += -sUSE_SDL=2 -sSDL2_MIXER_FORMATS=wav,mid -sUSE_SDL_MIXER=2
OBJS_EXT += syscall_sdl.o
LDFLAGS += -pthread
endif

# More build flags
CFLAGS_emcc += -sINITIAL_MEMORY=2GB \
-sALLOW_MEMORY_GROWTH \
-s"EXPORTED_FUNCTIONS=$(EXPORTED_FUNCS)" \
-sSTACK_SIZE=4MB \
-sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency \
--embed-file build@/ \
--embed-file build/timidity@/etc/timidity \
-DMEM_SIZE=0x40000000 \
-DCYCLE_PER_STEP=2000000 \
--pre-js $(WEB_JS_RESOURCES)/pre.js \
-O3 \
-w

# used to download all dependencies of elf executable and bundle into single wasm
deps_emcc += $(DOOM_DATA) $(QUAKE_DATA) $(TIMIDITY_DATA)

# check browser MAJOR version if supports TCO
CHROME_MAJOR :=
CHROME_MAJOR_VERSION_CHECK_CMD :=
CHROME_SUPPORT_TCO_AT_MAJOR := 112
CHROME_SUPPORT_TCO_INFO := Chrome supports TCO, you can use Chrome to request the wasm
CHROME_NO_SUPPORT_TCO_WARNING := Chrome not found or Chrome must have at least version $(CHROME_SUPPORT_TCO_AT_MAJOR) in MAJOR to support TCO in wasm

FIREFOX_MAJOR :=
FIREFOX_MAJOR_VERSION_CHECK_CMD :=
FIREFOX_SUPPORT_TCO_AT_MAJOR := 121
FIREFOX_SUPPORT_TCO_INFO := Firefox supports TCO, you can use Firefox to request the wasm
FIREFOX_NO_SUPPORT_TCO_WARNING := Firefox not found or Firefox must have at least version $(FIREFOX_SUPPORT_TCO_AT_MAJOR) in MAJOR to support TCO in wasm

# FIXME: for Windows
ifeq ($(UNAME_S),Darwin)
CHROME_MAJOR_VERSION_CHECK_CMD := "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --version | awk '{print $$3}' | cut -f1 -d.
FIREFOX_MAJOR_VERSION_CHECK_CMD := /Applications/Firefox.app/Contents/MacOS/firefox --version | awk '{print $$3}' | cut -f1 -d.
else ifeq ($(UNAME_S),Linux)
CHROME_MAJOR_VERSION_CHECK_CMD := google-chrome --version | awk '{print $$3}' | cut -f1 -d.
FIREFOX_MAJOR_VERSION_CHECK_CMD := firefox -v | awk '{print $$3}' | cut -f1 -d.
endif
CHROME_MAJOR := $(shell $(CHROME_MAJOR_VERSION_CHECK_CMD))
FIREFOX_MAJOR := $(shell $(FIREFOX_MAJOR_VERSION_CHECK_CMD))

# Chrome
ifeq ($(shell echo $(CHROME_MAJOR)\>=$(CHROME_SUPPORT_TCO_AT_MAJOR) | bc), 1)
$(info $(shell echo "$(GREEN)$(CHROME_SUPPORT_TCO_INFO)$(NC)"))
else
$(warning $(shell echo "$(YELLOW)$(CHROME_NO_SUPPORT_TCO_WARNING)$(NC)"))
endif

# Firefox
ifeq ($(shell echo $(FIREFOX_MAJOR)\>=$(FIREFOX_SUPPORT_TCO_AT_MAJOR) | bc), 1)
$(info $(shell echo "$(GREEN)$(FIREFOX_SUPPORT_TCO_INFO)$(NC)"))
else
$(warning $(shell echo "$(YELLOW)$(FIREFOX_NO_SUPPORT_TCO_WARNING)$(NC)"))
endif

# used to serve wasm locally
DEMO_DIR := demo
DEMO_IP := 127.0.0.1
DEMO_PORT := 8000

# check if demo root directory exists and create it if not
check-demo-dir-exist:
$(Q)if [ ! -d "$(DEMO_DIR)" ]; then \
mkdir -p "$(DEMO_DIR)"; \
fi

# FIXME: without $(info) generates errors
define cp-web-file
$(Q)cp $(1) $(DEMO_DIR)
$(info)
endef

# WEB_FILES could be cleaned and recompiled, thus do not mix these two files into WEB_FILES
STATIC_WEB_FILES := $(WEB_HTML_RESOURCES)/index.html \
$(WEB_JS_RESOURCES)/coi-serviceworker.min.js

start-web: $(BIN) check-demo-dir-exist
$(foreach T, $(WEB_FILES), $(call cp-web-file, $(T)))
$(foreach T, $(STATIC_WEB_FILES), $(call cp-web-file, $(T)))
$(Q)python3 -m http.server --bind $(DEMO_IP) $(DEMO_PORT) --directory $(DEMO_DIR)
endif

0 comments on commit 9ea1e62

Please sign in to comment.