Skip to content

Commit 22b0f21

Browse files
committed
Add riscv-wamr-qemu platform
1 parent 4972113 commit 22b0f21

24 files changed

Lines changed: 1965 additions & 0 deletions

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,17 @@ qemu-system-riscv64 -machine virt -bios none \
9595
-kernel build/bin/println.riscv.elf -nographic
9696
```
9797

98+
#### QEMU RISC-V WAMR
99+
```bash
100+
./platform/riscv-wamr-qemu/scripts/c2wamr-qemu.sh \
101+
examples/build-wasm/tinygo/fibonacci.wasm \
102+
build/bin/fibonacci.wamr.elf
103+
104+
# Run in QEMU
105+
qemu-system-riscv64 -machine virt -bios none \
106+
-kernel build/bin/fibonacci.wamr.elf -nographic
107+
```
108+
98109
## Examples
99110

100111
All examples below use Go, but the same principles apply to any language that compiles to WASM with WASI support.

docker/Dockerfile

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ RUN apt-get update && apt-get install -y \
2929
cmake \
3030
libglib2.0-dev \
3131
libslirp-dev \
32+
libpthread-stubs0-dev \
33+
wget \
3234
&& rm -rf /var/lib/apt/lists/*
3335

3436
# Build RISC-V GNU Toolchain
@@ -59,6 +61,38 @@ RUN git clone https://git.qemu.org/git/qemu.git && \
5961
cd .. && \
6062
rm -r qemu
6163

64+
# Build WAMR with ZKVM platform
65+
RUN git clone https://github.com/bytecodealliance/wasm-micro-runtime.git /opt/wamr
66+
COPY platform/riscv-wamr-qemu/wamr-platform/ /opt/wamr/core/shared/platform/zkvm/
67+
RUN cd /opt/wamr && \
68+
sed -i "s/find_package(Threads REQUIRED)/find_package(Threads)/g" CMakeLists.txt && \
69+
cmake . \
70+
-DWAMR_BUILD_PLATFORM=zkvm \
71+
-DWAMR_BUILD_TARGET=AOT \
72+
-DWAMR_BUILD_LIBC_BUILTIN=1 \
73+
-DWAMR_BUILD_LIB_PTHREAD=0 \
74+
-DWAMR_BUILD_THREAD_MGR=0 \
75+
-DWAMR_BUILD_WASI_THREADS=0 \
76+
-DTHREADS_PREFER_PTHREAD_FLAG=OFF \
77+
-DCMAKE_THREAD_LIBS_INIT="" \
78+
-DWAMR_BUILD_FAST_INTERP=1 \
79+
-DWAMR_BUILD_JIT=0 \
80+
-DWAMR_BUILD_AOT=1 \
81+
-DCMAKE_SYSTEM_PROCESSOR=riscv64 \
82+
-DWAMR_BUILD_TARGET=RISCV64_LP64 \
83+
-DCMAKE_C_COMPILER=/opt/riscv-newlib/bin/riscv64-unknown-elf-gcc \
84+
-DCMAKE_CXX_COMPILER=/opt/riscv-newlib/bin/riscv64-unknown-elf-g++ \
85+
-DCMAKE_ASM_COMPILER=/opt/riscv-newlib/bin/riscv64-unknown-elf-gcc \
86+
-DWASM_ENABLE_SIMDE=OFF \
87+
-DWAMR_BUILD_SIMD=0 && \
88+
make
89+
90+
# Use pre-compiled wamrc
91+
RUN wget https://github.com/bytecodealliance/wasm-micro-runtime/releases/download/WAMR-2.4.4/wamrc-2.4.4-x86_64-ubuntu-22.04.tar.gz && \
92+
tar xf wamrc-2.4.4-x86_64-ubuntu-22.04.tar.gz && \
93+
mv wamrc /usr/local/bin/ && \
94+
rm wamrc-2.4.4-x86_64-ubuntu-22.04.tar.gz
95+
6296
# Add toolchains to PATH
6397
ENV PATH="/opt/riscv-newlib/bin:/opt/w2c2/w2c2:${PATH}"
6498

16 KB
Binary file not shown.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/* originally from https://github.com/IngwiePhoenix/file2c */
2+
3+
#include <stdio.h>
4+
#include <stdlib.h>
5+
#include <assert.h>
6+
#include <sys/stat.h>
7+
8+
9+
void help(char* me) {
10+
printf("file2c originally by IngwiePhoenix | Convert a file into a C source file.\n");
11+
printf("Usage: %s inputFile varName > output", me);
12+
}
13+
14+
int main(int argc, char* argv[]) {
15+
if(argc != 3) { help(argv[0]); exit(1); }
16+
else {
17+
char* fn = argv[1];
18+
char* varName = argv[2];
19+
FILE* f = fopen(fn, "rb");
20+
if(!f) { fprintf(stderr, "Error while opening file."); exit(1); }
21+
22+
// Get filesize...
23+
int filesize;
24+
struct stat stat_buf;
25+
int rc = stat(fn, &stat_buf);
26+
filesize = stat_buf.st_size;
27+
28+
printf("const char %s[] = {\n ",varName);
29+
unsigned long n = 0;
30+
while(!feof(f)) {
31+
unsigned char c;
32+
if(fread(&c, 1, 1, f) == 0) break;
33+
++n;
34+
if((int)n == filesize) {
35+
printf("0x%.2X", (int)c);
36+
} else {
37+
printf("0x%.2X, ", (int)c);
38+
}
39+
if(n % 20 == 0) printf("\n ");
40+
}
41+
fclose(f);
42+
printf("\n};\n");
43+
printf("int %s_length=%i;\n",varName,filesize);
44+
}
45+
return 0;
46+
}

platform/riscv-wamr-qemu/main.c

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#include "bh_platform.h"
2+
#include "bh_read_file.h"
3+
#include "wasm_export.h"
4+
#include <stdio.h>
5+
6+
extern const char wasmModuleBuffer[];
7+
extern int wasmModuleBuffer_length;
8+
9+
int main(void) {
10+
int argc = 0;
11+
char *argv[0];
12+
13+
char error_buf[128];
14+
15+
wasm_module_t module;
16+
wasm_module_inst_t module_inst;
17+
wasm_function_inst_t func;
18+
wasm_exec_env_t exec_env;
19+
uint32 size, stack_size = 8092, heap_size = 8092;
20+
21+
wasm_runtime_set_log_level(WASM_LOG_LEVEL_VERBOSE);
22+
23+
/* initialize the wasm runtime by default configurations */
24+
if (!wasm_runtime_init()) {
25+
printf("runtime init failed\n");
26+
exit(1);
27+
}
28+
29+
/* parse the WASM file from buffer and create a WASM module */
30+
module = wasm_runtime_load(wasmModuleBuffer, wasmModuleBuffer_length, error_buf, sizeof(error_buf));
31+
if (module == 0) {
32+
printf("runtime load module failed: %s\n", error_buf);
33+
exit(1);
34+
}
35+
36+
/* create an instance of the WASM module (WASM linear memory is ready) */
37+
module_inst = wasm_runtime_instantiate(module, stack_size, heap_size, error_buf, sizeof(error_buf));
38+
if (module_inst == 0) {
39+
printf("wasm_runtime_instantiate failed as module_inst=%p: %s\n", module_inst, error_buf);
40+
exit(1);
41+
}
42+
43+
if (!wasm_application_execute_main(module_inst, argc, argv)) {
44+
printf("error executing main\n");
45+
/* TODO: check wasm_runtime_get_exception(..) */
46+
}
47+
48+
printf("wasm_runtime_unload...\n");
49+
wasm_runtime_unload(module);
50+
51+
return 0;
52+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
#!/bin/bash
2+
set -e
3+
4+
#set -x ## TODO : remove debug
5+
6+
# c2riscv-wamr-qemu - Compile WASM to WAMR RISC-V QEMU binary
7+
# Usage: ./platform/riscv-wamr-qemu/scripts/c2riscv-wamr-qemu.sh <guest-c-package-dir> <output-elf>
8+
9+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10+
WAMR_QEMU_DIR="$(dirname "$SCRIPT_DIR")"
11+
DOCKER_DIR="$WAMR_QEMU_DIR/../../docker"
12+
PROJECT_ROOT="$WAMR_QEMU_DIR/../.."
13+
WAMR_ROOT=/opt/wamr
14+
15+
16+
17+
# Check arguments
18+
if [ $# -lt 2 ]; then
19+
echo "Usage: $0 <guest-c-package-dir> <output-elf>"
20+
echo ""
21+
echo "Compiles a C package (from w2c2) to WAMR-based RISC-V binary for QEMU virt machine."
22+
echo ""
23+
echo "Arguments:"
24+
echo " guest-c-package-dir Directory containing guest.c, guest.h, w2c2_base.h"
25+
echo " output-elf Output RISC-V ELF binary path"
26+
echo ""
27+
echo "Example:"
28+
echo " $0 build/.c-packages/println build/bin/println.riscv.elf"
29+
echo ""
30+
echo "To run in QEMU:"
31+
echo " qemu-system-riscv64 -machine virt -bios none \\"
32+
echo " -kernel build/bin/println.riscv.elf -nographic \\"
33+
echo " -semihosting-config enable=on,target=native"
34+
echo ""
35+
exit 1
36+
fi
37+
38+
INPUT_WASM="$1"
39+
OUTPUT="$2"
40+
41+
# Change to project root
42+
cd "$PROJECT_ROOT"
43+
44+
# Convert to relative paths if needed
45+
if [[ "$INPUT_WASM" == /* ]]; then
46+
INPUT_WASM=$(realpath --relative-to="$PROJECT_ROOT" "$INPUT_WASM" 2>/dev/null || echo "$INPUT_WASM")
47+
fi
48+
if [[ "$OUTPUT" == /* ]]; then
49+
OUTPUT=$(realpath --relative-to="$PROJECT_ROOT" "$OUTPUT" 2>/dev/null || echo "$OUTPUT")
50+
fi
51+
52+
# Validate input exists
53+
if [ ! -f "$INPUT_WASM" ]; then
54+
echo "Error: Input file '$INPUT_WASM' not found"
55+
exit 1
56+
fi
57+
58+
## Verify guest package exists
59+
#if [ ! -f "$INPUT_WASM/guest.c" ] || [ ! -f "$GU#EST_DIR/guest.h" ]; then
60+
# echo "Error: Guest package not found at $GU#EST_DIR"
61+
# echo "Expected files: guest.c, guest.h, w2c#2_base.h"
62+
# exit 1
63+
#fi
64+
65+
# Create output directory
66+
mkdir -p "$(dirname "$OUTPUT")"
67+
68+
echo "======================================"
69+
echo "C to WAMR RISC-V QEMU Compilation"
70+
echo "======================================"
71+
echo "Guest package: $INPUT_WASM"
72+
echo "Output binary: $OUTPUT"
73+
echo ""
74+
75+
# Bundle wasm binary within output elf
76+
"$DOCKER_DIR/docker-shell.sh" \
77+
wamrc --target=riscv64 \
78+
--target-abi=lp64 \
79+
--cpu=generic-rv64 \
80+
--cpu-features='+i,+m,+a' \
81+
--opt-level=0 \
82+
--size-level=1 \
83+
-o $OUTPUT.riscv64.wamr $1
84+
85+
gcc platform/riscv-wamr-qemu/file2c/file2c.c \
86+
-o platform/riscv-wamr-qemu/file2c/file2c
87+
88+
# The byte buffer can be WASM binary data when
89+
# interpreter or JIT is enabled, or AOT binary
90+
# data when AOT is enabled.
91+
#
92+
# https://github.com/bytecodealliance/wasm-micro-runtime/blob/main/core/iwasm/include/wasm_export.h
93+
platform/riscv-wamr-qemu/file2c/file2c \
94+
$OUTPUT.riscv64.wamr \
95+
wasmModuleBuffer > $OUTPUT.riscv64.wamr.c
96+
97+
# Compile everything in one command via Docker
98+
echo "Compiling..."
99+
100+
# RISC-V toolchain prefix
101+
PREFIX=/opt/riscv-newlib/bin/riscv64-unknown-elf-
102+
103+
# Compiler flags (using -O0 for faster compilation of large generated files)
104+
CFLAGS=(
105+
-march=rv64ima
106+
-mabi=lp64
107+
-mcmodel=medany
108+
-specs=nosys.specs
109+
-D__bool_true_false_are_defined
110+
-ffunction-sections
111+
-fdata-sections
112+
-O0
113+
-g
114+
-Wall
115+
)
116+
117+
# Include directories
118+
INCLUDES=(
119+
-I"$WAMR_ROOT/core/iwasm/include"
120+
-I"$WAMR_ROOT/core/shared/utils"
121+
-I"$WAMR_ROOT/core/shared/utils/uncommon"
122+
-I"$WAMR_ROOT/core/shared/platform/zkvm"
123+
-Iwasi/embedded
124+
-Iplatform/riscv-qemu
125+
)
126+
127+
# Source files
128+
SOURCES=(
129+
platform/riscv-wamr-qemu/main.c
130+
platform/riscv-wamr-qemu/syscalls.c
131+
platform/riscv-wamr-qemu/uart.c
132+
"$OUTPUT.riscv64.wamr.c"
133+
)
134+
135+
# Assembly source
136+
ASM_SOURCE=platform/riscv-wamr-qemu/startup.S
137+
138+
# Linker script
139+
LINKER_SCRIPT=platform/riscv-wamr-qemu/virt.ld
140+
141+
# Linker flags (matching demo-qemu-virt-riscv/Makefile)
142+
LDFLAGS=(
143+
-T"$LINKER_SCRIPT"
144+
-nostartfiles
145+
-static
146+
-L"$WAMR_ROOT"
147+
-liwasm
148+
-Wl,--gc-sections
149+
-Wl,-Map="${OUTPUT%.elf}.map"
150+
)
151+
152+
# Link libraries
153+
LIBS=(-lc -lm -lgcc)
154+
155+
"$DOCKER_DIR/docker-shell.sh" ${PREFIX}gcc \
156+
"${CFLAGS[@]}" \
157+
"${INCLUDES[@]}" \
158+
"${SOURCES[@]}" \
159+
"$ASM_SOURCE" \
160+
"${LDFLAGS[@]}" \
161+
"${LIBS[@]}" \
162+
-o "$OUTPUT" 2>&1
163+
164+
# Check if compilation succeeded
165+
if [ $? -eq 0 ] && [ -f "$OUTPUT" ]; then
166+
SIZE=$(du -h "$OUTPUT" | cut -f1)
167+
echo "Compiled successfully"
168+
echo ""
169+
echo "======================================"
170+
echo "Binary created successfully!"
171+
echo "======================================"
172+
echo ""
173+
echo "Location: $OUTPUT"
174+
echo "Size: $SIZE"
175+
echo ""
176+
echo "To run in QEMU:"
177+
echo " qemu-system-riscv64 -machine virt -bios none \\"
178+
echo " -kernel $OUTPUT -nographic \\"
179+
echo " -semihosting-config enable=on,target=native"
180+
echo ""
181+
else
182+
echo "Error: Compilation failed"
183+
exit 1
184+
fi
185+

platform/riscv-wamr-qemu/startup.S

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
.section .text.init
2+
.global _start
3+
4+
_start:
5+
la sp, _stack_top
6+
7+
# Clear BSS section - using symbols defined in our linker script
8+
la t0, _bss_start
9+
la t1, _bss_end
10+
clear_bss:
11+
bgeu t0, t1, bss_done
12+
sb zero, 0(t0)
13+
addi t0, t0, 1
14+
j clear_bss
15+
bss_done:
16+
17+
# Jump to C code
18+
call main
19+
20+
# In case main returns
21+
1: j 1b

0 commit comments

Comments
 (0)