diff --git a/README.md b/README.md index c63c036..8208473 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ ziskemu -e build/bin/println.zkvm.elf ``` #### QEMU RISC-V + ```bash ./platform/riscv-qemu/scripts/c2riscv-qemu.sh \ build/c-packages/println \ @@ -95,6 +96,21 @@ qemu-system-riscv64 -machine virt -bios none \ -kernel build/bin/println.riscv.elf -nographic ``` +#### QEMU RISC-V WAMR + +Include OpenSBI BIOS (`-bios default` instead of `-bios none`) such that a shutdown function is present for improved benchmarking. + +```bash +./platform/riscv-wamr-qemu/scripts/wasm2wamr-qemu.sh \ + examples/build-wasm/go/fibonacci.wasm \ + build/bin/fibonacci.wamr.elf + +# Run in QEMU +./docker/docker-shell.sh qemu-system-riscv64 -machine virt -m 1024M \ + -kernel build/bin/fibonacci.wamr.elf -nographic -d plugin \ + -plugin /libinsn.so +``` + ## Examples All examples below use Go, but the same principles apply to any language that compiles to WASM with WASI support. @@ -183,9 +199,9 @@ Please note that: - `./platform/riscv-qemu-user/scripts/c2riscv-qemu-user.sh` uses target `-march=rv64imad -march=rv64imad` whereas Go direct compilation uses `rv64gc`. - `wasmtime` targets rv64gc -|program|through WASM, w2c2, -O0|through WASM, w2c2, optimized|though WASM, wasmtime|through WASM, wasmer (cranelift)|directly| +|program|through WASM, w2c2, -O0|through WASM, w2c2, optimized|through WAMR, -O0|though WASM, wasmtme|through WASM, wasmer (cranelift)|directly| |---|---|---|---|---|---| -|`stateless`|12,866,052,519|2,110,574,100 (-O3)|874,758,419|953,874,491|236,265,327| +|`stateless`|12,866,052,519|2,110,574,100 (-O3)|5,427,433,654|874,758,419|953,874,491|236,265,327| ## Analysis of the results @@ -206,6 +222,8 @@ Conclusions for `fibonacci` and `hello-world`: Surprisingly WASM though `wasmtime` is faster than `w2c2`. `wasmtime` approach is ~3-4 times slower than the direct approach. +Unoptimized WAMR AOT is currently in between `w2c2` and wasmtime. Running WAMR with non-zero optimization levels on RISC-V currently fails with a relocation error. https://github.com/bytecodealliance/wasm-micro-runtime/issues/4765 + ### Other observations `cranelift` (used by `wasmtime` and `wasmer`) proved to be the fastest runtime for Ethereum client programs written in Go and Rust. When running via `wasmtime`, execution required 2.7× more steps than native compilation for Rust and 4.0× for Go. This suggests the Go compiler may generate less efficient WASM bytecode than the Rust compiler, though the difference between the two ratios is significantly smaller than initially expected. diff --git a/docker/Dockerfile b/docker/Dockerfile index c607dd0..db9fa3a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -10,6 +10,7 @@ RUN apt-get update && apt-get install -y \ python3 \ python3-pip \ python3-tomli \ + python3.10-venv \ libmpc-dev \ libmpfr-dev \ libgmp-dev \ @@ -29,6 +30,11 @@ RUN apt-get update && apt-get install -y \ cmake \ libglib2.0-dev \ libslirp-dev \ + libpthread-stubs0-dev \ + g++-multilib \ + libgcc-9-dev \ + lib32gcc-9-dev \ + ccache \ && rm -rf /var/lib/apt/lists/* # Build RISC-V GNU Toolchain @@ -50,17 +56,68 @@ RUN git clone https://github.com/turbolent/w2c2.git /opt/w2c2 && \ RUN apt update && apt install -y git build-essential ninja-build pkg-config libglib2.0-dev libpixman-1-dev +# Include patch for insn.c because otherwise this error appears due to unknown +# instructions: +# +# ERROR:../tests/tcg/plugins/insn.c:97:vcpu_init: assertion failed: (count > 0) +# +# https://lists.gnu.org/archive/html/qemu-discuss/2025-08/msg00001.html RUN git clone https://git.qemu.org/git/qemu.git && \ cd qemu && \ - ./configure --target-list=riscv64-linux-user --enable-plugins && \ + sed -i 's/g_assert(count > 0)/g_assert(count >= 0)/g' tests/tcg/plugins/insn.c && \ + ./configure --target-list=riscv64-linux-user,riscv64-softmmu --enable-plugins && \ make -j4 && \ make install && \ cp build/tests/tcg/plugins/libinsn.so /libinsn.so && \ cd .. && \ rm -r qemu +# Build WAMR with ZKVM platform +# +# Some symbols such as __floatundisf, __floatdisf, __ltsf2 need to be +# explicitly registered for working soft float support on the target +# platform +# +# https://github.com/bytecodealliance/wasm-micro-runtime/issues/418 +# https://github.com/bytecodealliance/wasm-micro-runtime/issues/531 +# +# Additionally use LLVM 20. Profiling wamrc when compiling a large wasm +# file like stateless.wasm shows that AttemptToFoldSymbolOffsetDifference +# consumes most of the compile time. LLVM 19 contains a fix for this +# though. Also large code model is in principle supported for RISC-V +# since version 20. +# +# https://github.com/llvm/llvm-project/issues/81440 +RUN git clone https://github.com/psilva261/wasm-micro-runtime-zkvm.git /opt/wamr/ && \ + cd /opt/wamr && \ + git checkout zkvm && \ + cmake . \ + -DWAMR_BUILD_PLATFORM=zkvm \ + -DWAMR_BUILD_TARGET=AOT \ + -DWAMR_BUILD_LIBC_BUILTIN=1 \ + -DWAMR_BUILD_LIB_PTHREAD=0 \ + -DWAMR_BUILD_THREAD_MGR=0 \ + -DWAMR_BUILD_WASI_THREADS=0 \ + -DTHREADS_PREFER_PTHREAD_FLAG=OFF \ + -DCMAKE_THREAD_LIBS_INIT="" \ + -DWAMR_BUILD_FAST_INTERP=1 \ + -DWAMR_BUILD_JIT=0 \ + -DWAMR_BUILD_AOT=1 \ + -DCMAKE_SYSTEM_PROCESSOR=riscv64 \ + -DWAMR_BUILD_TARGET=RISCV64_LP64 \ + -DCMAKE_C_COMPILER=/opt/riscv-newlib/bin/riscv64-unknown-elf-gcc \ + -DCMAKE_CXX_COMPILER=/opt/riscv-newlib/bin/riscv64-unknown-elf-g++ \ + -DCMAKE_ASM_COMPILER=/opt/riscv-newlib/bin/riscv64-unknown-elf-gcc \ + -DWASM_ENABLE_SIMDE=OFF \ + -DWAMR_BUILD_SIMD=0 && \ + make && \ + cd /opt/wamr/wamr-compiler && \ + ./build_llvm.sh && \ + cmake . && \ + make + # Add toolchains to PATH -ENV PATH="/opt/riscv-newlib/bin:/opt/w2c2/w2c2:${PATH}" +ENV PATH="/opt/riscv-newlib/bin:/opt/w2c2/w2c2:/opt/wamr/wamr-compiler:${PATH}" RUN apt update && apt install -y gcc-riscv64-linux-gnu diff --git a/go_benchmark.sh b/go_benchmark.sh index 8c9a9ab..1a8bc37 100755 --- a/go_benchmark.sh +++ b/go_benchmark.sh @@ -19,6 +19,10 @@ echo "Transpiling WASM to WASMU with wasmer..." # TODO: Cranelift is used for now because LLVM support is buggy. Once LLVM is fixed in wasmer use `--llvm` flag instead of `cranelift`; https://github.com/wasmerio/wasmer/issues/5951#issuecomment-3632904384 ./docker/docker-shell.sh /root/.wasmer/bin/wasmer compile --cranelift --target riscv64gc-unknown-linux-gnu examples/build-wasm/go/stateless.wasm -o examples/build-wasm/go/stateless-by-wasmer/src/stateless.wasmu +echo "Transpiling WASM to WAMR AOT with wamrc..." + +./platform/riscv-wamr-qemu/scripts/wasm2wamr-qemu.sh examples/build-wasm/go/stateless.wasm build/bin/stateless.wamr.elf + echo "Compiling C to RISCV..." OPT_LEVEL="-O0" ./platform/riscv-qemu-user/scripts/c2riscv-qemu-user.sh build/c-packages/stateless/ build/bin/stateless.riscv.O0.elf @@ -36,6 +40,7 @@ echo "" > go_benchmark_results.txt ./docker/docker-shell.sh qemu-riscv64 -plugin /libinsn.so examples/go/stateless/stateless >> go_benchmark_results.txt 2>&1 ./docker/docker-shell.sh qemu-riscv64 -plugin /libinsn.so examples/build-wasm/go/stateless-by-wasmtime/target/riscv64gc-unknown-linux-gnu/release/standalone >> go_benchmark_results.txt 2>&1 ./docker/docker-shell.sh qemu-riscv64 -plugin /libinsn.so examples/build-wasm/go/stateless-by-wasmer/target/riscv64gc-unknown-linux-gnu/release/standalone >> go_benchmark_results.txt 2>&1 +./docker/docker-shell.sh qemu-system-riscv64 -d plugin -machine virt -m 1024M -plugin /libinsn.so -kernel build/bin/stateless.wamr.elf -nographic >> go_benchmark_results.txt 2>&1 echo "Done" diff --git a/platform/riscv-qemu/scripts/c2riscv-qemu.sh b/platform/riscv-qemu/scripts/c2riscv-qemu.sh index 2b93232..f87f949 100755 --- a/platform/riscv-qemu/scripts/c2riscv-qemu.sh +++ b/platform/riscv-qemu/scripts/c2riscv-qemu.sh @@ -69,7 +69,7 @@ PREFIX=/opt/riscv-newlib/bin/riscv64-unknown-elf- # Compiler flags (using -O0 for faster compilation of large generated files) CFLAGS=( - -march=rv64ima + -march=rv64ima_zicsr -mabi=lp64 -mcmodel=medany -specs=nosys.specs diff --git a/platform/riscv-wamr-qemu/file2c/file2c.c b/platform/riscv-wamr-qemu/file2c/file2c.c new file mode 100644 index 0000000..387b1a2 --- /dev/null +++ b/platform/riscv-wamr-qemu/file2c/file2c.c @@ -0,0 +1,46 @@ +/* originally from https://github.com/IngwiePhoenix/file2c */ + +#include +#include +#include +#include + + +void help(char* me) { + printf("file2c originally by IngwiePhoenix | Convert a file into a C source file.\n"); + printf("Usage: %s inputFile varName > output", me); +} + +int main(int argc, char* argv[]) { + if(argc != 3) { help(argv[0]); exit(1); } + else { + char* fn = argv[1]; + char* varName = argv[2]; + FILE* f = fopen(fn, "rb"); + if(!f) { fprintf(stderr, "Error while opening file."); exit(1); } + + // Get filesize... + int filesize; + struct stat stat_buf; + int rc = stat(fn, &stat_buf); + filesize = stat_buf.st_size; + + printf("const char %s[] = {\n ",varName); + unsigned long n = 0; + while(!feof(f)) { + unsigned char c; + if(fread(&c, 1, 1, f) == 0) break; + ++n; + if((int)n == filesize) { + printf("0x%.2X", (int)c); + } else { + printf("0x%.2X, ", (int)c); + } + if(n % 20 == 0) printf("\n "); + } + fclose(f); + printf("\n};\n"); + printf("int %s_length=%i;\n",varName,filesize); + } + return 0; +} diff --git a/platform/riscv-wamr-qemu/main.c b/platform/riscv-wamr-qemu/main.c new file mode 100644 index 0000000..e0fbabe --- /dev/null +++ b/platform/riscv-wamr-qemu/main.c @@ -0,0 +1,52 @@ +#include "bh_platform.h" +#include "bh_read_file.h" +#include "wasm_export.h" +#include + +extern const char wasmModuleBuffer[]; +extern int wasmModuleBuffer_length; + +int main(void) { + int argc = 0; + char *argv[0]; + + char error_buf[128]; + + wasm_module_t module; + wasm_module_inst_t module_inst; + wasm_function_inst_t func; + wasm_exec_env_t exec_env; + uint32 size, stack_size = 16*1024*1024; + + wasm_runtime_set_log_level(WASM_LOG_LEVEL_VERBOSE); + + /* initialize the wasm runtime by default configurations */ + if (!wasm_runtime_init()) { + printf("runtime init failed\n"); + exit(1); + } + + /* parse the WASM file from buffer and create a WASM module */ + module = wasm_runtime_load(wasmModuleBuffer, wasmModuleBuffer_length, error_buf, sizeof(error_buf)); + if (module == 0) { + printf("runtime load module failed: %s\n", error_buf); + exit(1); + } + + /* create an instance of the WASM module (WASM linear memory is ready) */ + module_inst = wasm_runtime_instantiate(module, stack_size, 0, error_buf, sizeof(error_buf)); + if (module_inst == 0) { + printf("wasm_runtime_instantiate failed as module_inst=%p: %s\n", module_inst, error_buf); + exit(1); + } + + if (!wasm_application_execute_main(module_inst, argc, argv)) { + printf("error executing main\n"); + printf("exception: %s\n", wasm_runtime_get_exception(module_inst)); + } + + printf("wasm_runtime_unload...\n"); + wasm_runtime_unload(module); + + return 0; +} diff --git a/platform/riscv-wamr-qemu/scripts/wasm2wamr-qemu.sh b/platform/riscv-wamr-qemu/scripts/wasm2wamr-qemu.sh new file mode 100755 index 0000000..68c5285 --- /dev/null +++ b/platform/riscv-wamr-qemu/scripts/wasm2wamr-qemu.sh @@ -0,0 +1,196 @@ +#!/bin/bash +set -e + +# wasm2riscv-wamr-qemu - Compile WASM to WAMR RISC-V QEMU binary +# Usage: ./platform/riscv-wamr-qemu/scripts/wasm2riscv-wamr-qemu.sh + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +WAMR_QEMU_DIR="$(dirname "$SCRIPT_DIR")" +DOCKER_DIR="$WAMR_QEMU_DIR/../../docker" +PROJECT_ROOT="$WAMR_QEMU_DIR/../.." +WAMR_ROOT=/opt/wamr + + + +# Check arguments +if [ $# -lt 2 ]; then + echo "Usage: $0 " + echo "" + echo "Compiles a C package (from w2c2) to WAMR-based RISC-V binary for QEMU virt machine." + echo "" + echo "Arguments:" + echo " guest-c-package-dir Directory containing guest.c, guest.h, w2c2_base.h" + echo " output-elf Output RISC-V ELF binary path" + echo "" + echo "Example:" + echo " $0 build/.c-packages/println build/bin/println.riscv.elf" + echo "" + echo "To run in QEMU:" + echo " ./docker/docker-shell.sh qemu-system-riscv64 -machine virt \\" + echo " -m 1024M -d plugin -plugin /libinsn.so \\" + echo " -kernel build/bin/println.riscv.elf -nographic \\" + echo " -semihosting-config enable=on,target=native" + echo "" + exit 1 +fi + +INPUT_WASM="$1" +OUTPUT="$2" + +# Change to project root +cd "$PROJECT_ROOT" + +# Convert to relative paths if needed +if [[ "$INPUT_WASM" == /* ]]; then + INPUT_WASM=$(realpath --relative-to="$PROJECT_ROOT" "$INPUT_WASM" 2>/dev/null || echo "$INPUT_WASM") +fi +if [[ "$OUTPUT" == /* ]]; then + OUTPUT=$(realpath --relative-to="$PROJECT_ROOT" "$OUTPUT" 2>/dev/null || echo "$OUTPUT") +fi + +# Validate input exists +if [ ! -f "$INPUT_WASM" ]; then + echo "Error: Input file '$INPUT_WASM' not found" + exit 1 +fi + +## Verify guest package exists +#if [ ! -f "$INPUT_WASM/guest.c" ] || [ ! -f "$GU#EST_DIR/guest.h" ]; then +# echo "Error: Guest package not found at $GU#EST_DIR" +# echo "Expected files: guest.c, guest.h, w2c#2_base.h" +# exit 1 +#fi + +# Create output directory +mkdir -p "$(dirname "$OUTPUT")" + +echo "======================================" +echo "WASM to WAMR RISC-V QEMU Compilation" +echo "======================================" +echo "Guest package: $INPUT_WASM" +echo "Output binary: $OUTPUT" +echo "" + +# Bundle wasm binary within output elf +# +# Without --bounds-checks=1 an error +# +# runtime: morestack on g0 +# +# is shown. Enabling it seems to trigger a calculation of +# the stack boundary +# +# https://github.com/bytecodealliance/wasm-micro-runtime/issues/3966 +# https://github.com/bytecodealliance/wasm-micro-runtime/pull/3967 +"$DOCKER_DIR/docker-shell.sh" \ + wamrc --target=riscv64 \ + --target-abi=lp64 \ + --cpu=generic-rv64 \ + --cpu-features='+i,+m,+a' \ + --opt-level=0 \ + --size-level=1 \ + --bounds-checks=1 \ + -o $OUTPUT.riscv64.wamr $1 + +gcc platform/riscv-wamr-qemu/file2c/file2c.c \ + -o platform/riscv-wamr-qemu/file2c/file2c + +# The byte buffer can be WASM binary data when +# interpreter or JIT is enabled, or AOT binary +# data when AOT is enabled. +# +# https://github.com/bytecodealliance/wasm-micro-runtime/blob/main/core/iwasm/include/wasm_export.h +platform/riscv-wamr-qemu/file2c/file2c \ + $OUTPUT.riscv64.wamr \ + wasmModuleBuffer > $OUTPUT.riscv64.wamr.c + +# Compile everything in one command via Docker +echo "Compiling..." + +# RISC-V toolchain prefix +PREFIX=/opt/riscv-newlib/bin/riscv64-unknown-elf- + +# Compiler flags (using -O0 for faster compilation of large generated files) +CFLAGS=( + -march=rv64ima + -mabi=lp64 + -mcmodel=medany + -specs=nosys.specs + -D__bool_true_false_are_defined + -ffunction-sections + -fdata-sections + -O0 + -g + -Wall +) + +# Include directories +INCLUDES=( + -I"$WAMR_ROOT/core/iwasm/include" + -I"$WAMR_ROOT/core/shared/utils" + -I"$WAMR_ROOT/core/shared/utils/uncommon" + -I"$WAMR_ROOT/core/shared/platform/zkvm" + -Iwasi/embedded + -Iplatform/riscv-qemu +) + +# Source files +SOURCES=( + platform/riscv-wamr-qemu/main.c + platform/riscv-wamr-qemu/syscalls.c + platform/riscv-wamr-qemu/uart.c + "$OUTPUT.riscv64.wamr.c" +) + +# Assembly source +ASM_SOURCE=platform/riscv-wamr-qemu/startup.S + +# Linker script +LINKER_SCRIPT=platform/riscv-wamr-qemu/virt.ld + +# Linker flags (matching demo-qemu-virt-riscv/Makefile) +LDFLAGS=( + -T"$LINKER_SCRIPT" + -nostartfiles + -static + -L"$WAMR_ROOT" + -liwasm + -Wl,--gc-sections + -Wl,-Map="${OUTPUT%.elf}.map" +) + +# Link libraries +LIBS=(-lc -lm -lgcc) + +"$DOCKER_DIR/docker-shell.sh" ${PREFIX}gcc \ + "${CFLAGS[@]}" \ + "${INCLUDES[@]}" \ + "${SOURCES[@]}" \ + "$ASM_SOURCE" \ + "${LDFLAGS[@]}" \ + "${LIBS[@]}" \ + -o "$OUTPUT" 2>&1 + +# Check if compilation succeeded +if [ $? -eq 0 ] && [ -f "$OUTPUT" ]; then + SIZE=$(du -h "$OUTPUT" | cut -f1) + echo "Compiled successfully" + echo "" + echo "======================================" + echo "Binary created successfully!" + echo "======================================" + echo "" + echo "Location: $OUTPUT" + echo "Size: $SIZE" + echo "" + echo "To run in QEMU:" + echo " ./docker/docker-shell.sh qemu-system-riscv64 -machine virt \\" + echo " -m 1024M -d plugin -plugin /libinsn.so \\" + echo " -kernel $OUTPUT -nographic \\" + echo " -semihosting-config enable=on,target=native" + echo "" +else + echo "Error: Compilation failed" + exit 1 +fi + diff --git a/platform/riscv-wamr-qemu/startup.S b/platform/riscv-wamr-qemu/startup.S new file mode 100644 index 0000000..0fb4e34 --- /dev/null +++ b/platform/riscv-wamr-qemu/startup.S @@ -0,0 +1,27 @@ +.section .text.init +.global _start + +_start: + la sp, _stack_top + + /* Clear BSS section - using symbols defined in our linker script */ + la t0, _bss_start + la t1, _bss_end +clear_bss: + bgeu t0, t1, bss_done + sb zero, 0(t0) + addi t0, t0, 1 + j clear_bss +bss_done: + + /* Call main */ + call main + + /* https://github.com/xypron/riscv_test_payload (MIT-licensed) */ + li a7, 0x53525354 # SBI extension: SRST (System Reset) + li a6, 0 # Function: system reset + li a0, 0 # Reset type: shutdown + li a1, 0 # Reset reason: no reason + ecall # Perform system shutdown +end: + j end diff --git a/platform/riscv-wamr-qemu/syscalls.c b/platform/riscv-wamr-qemu/syscalls.c new file mode 100644 index 0000000..593ab63 --- /dev/null +++ b/platform/riscv-wamr-qemu/syscalls.c @@ -0,0 +1,75 @@ +#include +#include +#include +#include + +#include "uart.h" + +int _write(int file, char *ptr, int len) { + // We're only handling stdout and stderr + if (file != STDOUT_FILENO && file != STDERR_FILENO) { + errno = EBADF; + return -1; + } + + int i; + for (i = 0; i < len; i++) { + uart_putc(ptr[i]); + } + return len; +} + +int _read(int file, char *ptr, int len) { + // We're only handling stdin + if (file != STDIN_FILENO) { + errno = EBADF; + return -1; + } + + int i; + for (i = 0; i < len; i++) { + ptr[i] = uart_getc(); + if (ptr[i] == '\r' || ptr[i] == '\n') { + i++; + break; + } + } + return i; +} + +// Other required syscalls with minimal implementations +int _open(const char *name, int flags, int mode) { errno = ENOSYS; return -1; } +int _close(int file) { return -1; } +int _lseek(int file, int ptr, int dir) { return 0; } +int _fstat(int file, struct stat *st) { + st->st_mode = S_IFCHR; + return 0; +} +int _isatty(int file) { + return 1; +} + +void* _sbrk(int incr) { + extern char _heap_start; // Defined by the linker - start of heap + extern char _stack_top; // Defined in our linker script - top of stack area + + static char *heap_end = &_heap_start; + char *prev_heap_end = heap_end; + + // Calculate safe stack limit - stack grows down from _stack_top towards _stack_bottom + char *stack_limit = &_stack_top; + + // Check if heap would grow too close to stack + if (heap_end + incr > stack_limit) { + errno = ENOMEM; + return (void*) -1; // Return error + } + + heap_end += incr; + return (void*) prev_heap_end; +} + +// Required stubs +void _exit(int code) {} +int _kill(int pid, int sig) { return -1; } +int _getpid(void) { return 1; } diff --git a/platform/riscv-wamr-qemu/uart.c b/platform/riscv-wamr-qemu/uart.c new file mode 100644 index 0000000..6f9db27 --- /dev/null +++ b/platform/riscv-wamr-qemu/uart.c @@ -0,0 +1,29 @@ +#include "uart.h" + +// QEMU UART registers - these addresses are for QEMU's 16550A UART +#define UART_BASE 0x10000000 +#define UART_THR (*(volatile char *)(UART_BASE + 0x00)) // Transmit Holding Register +#define UART_RBR (*(volatile char *)(UART_BASE + 0x00)) // Receive Buffer Register +#define UART_LSR (*(volatile char *)(UART_BASE + 0x05)) // Line Status Register + +#define UART_LSR_TX_IDLE (1 << 5) // Transmitter idle +#define UART_LSR_RX_READY (1 << 0) // Receiver ready + +void uart_putc(char c) { + // Wait until transmitter is idle + while ((UART_LSR & UART_LSR_TX_IDLE) == 0); + UART_THR = c; + + // Special handling for newline (send CR+LF) + if (c == '\n') { + while ((UART_LSR & UART_LSR_TX_IDLE) == 0); + UART_THR = '\r'; + } +} + +char uart_getc(void) { + // Wait for data + while ((UART_LSR & UART_LSR_RX_READY) == 0); + return UART_RBR; +} + diff --git a/platform/riscv-wamr-qemu/uart.h b/platform/riscv-wamr-qemu/uart.h new file mode 100644 index 0000000..580fc3e --- /dev/null +++ b/platform/riscv-wamr-qemu/uart.h @@ -0,0 +1,7 @@ +#ifndef UART_H +#define UART_H + +void uart_putc(char c); +char uart_getc(void); + +#endif diff --git a/platform/riscv-wamr-qemu/virt.ld b/platform/riscv-wamr-qemu/virt.ld new file mode 100644 index 0000000..eb6828f --- /dev/null +++ b/platform/riscv-wamr-qemu/virt.ld @@ -0,0 +1,73 @@ +/* Linker script for QEMU RISC-V virt machine */ + +OUTPUT_FORMAT("elf64-littleriscv") +OUTPUT_ARCH("riscv") +ENTRY(_start) + +/* QEMU virt machine memory map */ +MEMORY { + ram (wxa) : ORIGIN = 0x80200000, LENGTH = 1024M +} + +SECTIONS +{ + /* Start of RAM */ + . = 0x80200000; + + /* Code section */ + .text : { + KEEP(*(.text.init)) /* Keep startup code first */ + *(.text .text.*) + *(.gnu.linkonce.t.*) + } >ram + + /* Align to 8 bytes */ + . = ALIGN(8); + + /* Global pointer for efficient access to small data */ + PROVIDE(__global_pointer$ = . + 0x800); + + /* Read-only data */ + .rodata : { + *(.rodata .rodata.*) + *(.gnu.linkonce.r.*) + } >ram + + /* Align to 8 bytes */ + . = ALIGN(8); + + /* Initialized data */ + .data : { + *(.data .data.*) + *(.sdata .sdata.*) + *(.gnu.linkonce.d.*) + } >ram + + /* Align to 8 bytes */ + . = ALIGN(8); + + /* Uninitialized data (BSS) */ + .bss : { + PROVIDE(_bss_start = .); + *(.bss .bss.*) + *(.sbss .sbss.*) + *(.gnu.linkonce.b.*) + *(COMMON) + PROVIDE(_bss_end = .); + } >ram + + /* Align to page boundary */ + . = ALIGN(0x1000); + + /* Heap for malloc */ + PROVIDE(_heap_start = .); + . = . + 512M; /* 512MB heap */ + PROVIDE(_heap_end = .); + + /* Stack grows downward from top of RAM */ + . = . + 128M; /* 128MB stack */ + PROVIDE(_stack_top = .); + + /* End marker */ + PROVIDE(_end = .); +}