Add custom section sample and enable CI test

This commit is contained in:
TianlongLiang 2026-03-17 14:52:02 +08:00
parent c3dda4a7d6
commit c5ee306f2c
14 changed files with 532 additions and 0 deletions

View File

@ -628,6 +628,14 @@ jobs:
cmake --build . --config Release --parallel 4 cmake --build . --config Release --parallel 4
./import-func-callback ./import-func-callback
- name: Build Sample [custom_section]
run: |
cd samples/custom-section
./build.sh
./run.sh
./build.sh --aot
./run.sh --aot
test: test:
needs: [build_iwasm, build_llvm_libraries_on_ubuntu_2204, build_wamrc] needs: [build_iwasm, build_llvm_libraries_on_ubuntu_2204, build_wamrc]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}

View File

@ -441,6 +441,14 @@ jobs:
cmake --build . --config Release --parallel 4 cmake --build . --config Release --parallel 4
./import-func-callback ./import-func-callback
- name: Build Sample [custom_section]
run: |
cd samples/custom-section
./build.sh
./run.sh
./build.sh --aot
./run.sh --aot
- name: Test x18 register reservation (macOS ARM64 only) - name: Test x18 register reservation (macOS ARM64 only)
if: matrix.os == 'macos-15' if: matrix.os == 'macos-15'
run: | run: |

View File

@ -598,6 +598,14 @@ jobs:
cmake --build . --config Release --parallel 4 cmake --build . --config Release --parallel 4
./import-func-callback ./import-func-callback
- name: Build Sample [custom_section]
run: |
cd samples/custom-section
./build.sh
./run.sh
./build.sh --aot
./run.sh --aot
test: test:
needs: [build_iwasm, build_llvm_libraries_on_ubuntu, build_wamrc] needs: [build_iwasm, build_llvm_libraries_on_ubuntu, build_wamrc]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}

View File

@ -1,6 +1,7 @@
# Samples # Samples
- [**basic**](./basic): Demonstrating how to use runtime exposed API's to call WASM functions, how to register native functions and call them, and how to call WASM function from native function. - [**basic**](./basic): Demonstrating how to use runtime exposed API's to call WASM functions, how to register native functions and call them, and how to call WASM function from native function.
- [**custom_section**](./custom_section): Demonstrating how to embed a binary payload into a Wasm custom section, resolve it from native code with `wasm_runtime_get_custom_section`, and print it later through a handle-based native API.
- **[file](./file/README.md)**: Demonstrating the supported file interaction API of WASI. This sample can also demonstrate the SGX IPFS (Intel Protected File System), enabling an enclave to seal and unseal data at rest. - **[file](./file/README.md)**: Demonstrating the supported file interaction API of WASI. This sample can also demonstrate the SGX IPFS (Intel Protected File System), enabling an enclave to seal and unseal data at rest.
- **[multi-thread](./multi-thread/)**: Demonstrating how to run wasm application which creates multiple threads to execute wasm functions concurrently, and uses mutex/cond by calling pthread related API's. - **[multi-thread](./multi-thread/)**: Demonstrating how to run wasm application which creates multiple threads to execute wasm functions concurrently, and uses mutex/cond by calling pthread related API's.
- **[spawn-thread](./spawn-thread)**: Demonstrating how to execute wasm functions of the same wasm application concurrently, in threads created by host embedder or runtime, but not the wasm application itself. - **[spawn-thread](./spawn-thread)**: Demonstrating how to execute wasm functions of the same wasm application concurrently, in threads created by host embedder or runtime, but not the wasm application itself.

1
samples/custom-section/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/out/

View File

@ -0,0 +1,81 @@
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
cmake_minimum_required(VERSION 3.14)
include(CheckPIESupported)
if(NOT WAMR_BUILD_PLATFORM STREQUAL "windows")
project(custom_section)
else()
project(custom_section C ASM)
endif()
# ############### runtime settings ################
string(TOLOWER ${CMAKE_HOST_SYSTEM_NAME} WAMR_BUILD_PLATFORM)
if(APPLE)
add_definitions(-DBH_PLATFORM_DARWIN)
endif()
set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "")
if(NOT DEFINED WAMR_BUILD_TARGET)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm64|aarch64)")
set(WAMR_BUILD_TARGET "AARCH64")
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "riscv64")
set(WAMR_BUILD_TARGET "RISCV64")
elseif(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(WAMR_BUILD_TARGET "X86_64")
elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
set(WAMR_BUILD_TARGET "X86_32")
else()
message(SEND_ERROR "Unsupported build target platform!")
endif()
endif()
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif()
set(WAMR_BUILD_MEMORY_PROFILING 1)
set(WAMR_BUILD_INTERP 1)
set(WAMR_BUILD_AOT 1)
set(WAMR_BUILD_JIT 0)
set(WAMR_BUILD_LIBC_BUILTIN 1)
set(WAMR_BUILD_LOAD_CUSTOM_SECTION 1)
if(NOT MSVC)
set(WAMR_BUILD_LIBC_WASI 1)
endif()
if(NOT MSVC)
if(NOT(CMAKE_C_COMPILER MATCHES ".*clang.*" OR CMAKE_C_COMPILER_ID MATCHES ".*Clang"))
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections")
endif()
endif()
set(WAMR_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
include(${WAMR_ROOT_DIR}/build-scripts/runtime_lib.cmake)
add_library(vmlib ${WAMR_RUNTIME_LIB_SOURCE})
# ############### application related ################
include_directories(${CMAKE_CURRENT_LIST_DIR}/src)
include(${SHARED_DIR}/utils/uncommon/shared_uncommon.cmake)
add_executable(custom_section
src/main.c
src/native_impl.c
${UNCOMMON_SHARED_SOURCE}
)
check_pie_supported()
set_target_properties(custom_section PROPERTIES POSITION_INDEPENDENT_CODE ON)
if(APPLE)
target_link_libraries(custom_section vmlib -lm -ldl -lpthread)
else()
target_link_libraries(custom_section vmlib -lm -ldl -lpthread -lrt)
endif()

View File

@ -0,0 +1,69 @@
---
description: "The related code/working directory of this example resides in directory {WAMR_DIR}/samples/custom_section"
---
# The "custom_section" sample project
This sample demonstrates how to:
- embed a separate binary payload into a Wasm custom section through a `.s` file
- load that Wasm module with `WAMR_BUILD_LOAD_CUSTOM_SECTION=1`
- export native APIs that resolve a custom section name to a host-side handle
- print the custom section bytes later through a second native API
- optionally compile the Wasm module to AoT while preserving the `demo` custom section
The Wasm application is built from:
- `wasm-apps/custom_section.c`
- `wasm-apps/custom_section_payload.s`
- `wasm-apps/custom_section_payload.bin`
The assembler file emits a section named `.custom_section.demo`, which becomes a Wasm custom section named `demo` in the final `.wasm` file.
## Why use a custom section for this payload
The payload in this sample is treated as read-only metadata. Putting it in a custom section lets the embedder access the bytes directly from the loaded module through `wasm_runtime_get_custom_section`, instead of copying the data into Wasm linear memory per instance.
That matters when the data is large or rarely changed:
- the bytes stay in the module image as immutable data
- the host can look them up by section name and use them in place
- the Wasm app only needs to pass a small section name and receive a small handle
- no extra application-level serialization or buffer duplication is needed for the read-only payload
This pattern is useful for embedded assets, lookup tables, model metadata, certificates, and other static blobs that the host wants to consume without treating them as mutable Wasm heap data.
## Build this sample
Execute the `build.sh` script. The host executable and the Wasm app are generated in `out`.
```sh
./build.sh
```
Build the AoT variant only when needed by passing `--aot`. This preserves the `demo` custom section in the generated AoT file by calling `wamrc --emit-custom-sections=demo`.
```sh
./build.sh --aot
```
## Run the sample
Enter the output directory and run the Wasm sample directly:
```sh
cd ./out/
./custom_section -f wasm-apps/custom_section.wasm
```
Or run the helper script from `samples/custom_section`:
```sh
./run.sh
```
To run the AoT artifact instead, pass `--aot` to the helper script:
```sh
./run.sh --aot
```

63
samples/custom-section/build.sh Executable file
View File

@ -0,0 +1,63 @@
#!/bin/bash
set -e
CURR_DIR=$PWD
OUT_DIR=${PWD}/out
WASM_APPS=${PWD}/wasm-apps
WAMR_ROOT_DIR=${PWD}/../..
WAMRC_CMD=${WAMR_ROOT_DIR}/wamr-compiler/build/wamrc
BUILD_AOT=0
if [ $# -gt 1 ]; then
echo "Usage: $0 [--aot]"
exit 1
fi
if [ $# -eq 1 ]; then
if [ "$1" = "--aot" ]; then
BUILD_AOT=1
else
echo "Usage: $0 [--aot]"
exit 1
fi
fi
rm -rf ${OUT_DIR}
mkdir -p ${OUT_DIR}/wasm-apps
printf '##################### build custom_section project\n'
mkdir -p build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug
make -j ${nproc}
cp -a custom_section ${OUT_DIR}
printf '\n##################### build wasm app\n'
cd ${WASM_APPS}
/opt/wasi-sdk/bin/clang \
--target=wasm32 \
-O0 \
-nostdlib \
-Wl,--strip-all,--no-entry \
-Wl,--allow-undefined \
-Wl,--export=run_demo \
-o ${OUT_DIR}/wasm-apps/custom_section.wasm \
custom_section.c \
custom_section_payload.s
printf '\nbuild custom_section.wasm success\n'
if [ ${BUILD_AOT} -eq 1 ]; then
if [ ! -x ${WAMRC_CMD} ]; then
echo "Error: wamrc not found at ${WAMRC_CMD}"
echo "Please build wamrc first under ${WAMR_ROOT_DIR}/wamr-compiler"
exit 1
fi
printf '\n##################### build aot app\n'
${WAMRC_CMD} --emit-custom-sections=demo -o ${OUT_DIR}/wasm-apps/custom_section.aot ${OUT_DIR}/wasm-apps/custom_section.wasm
printf '\nbuild custom_section.aot success\n'
fi
cd ${CURR_DIR}

31
samples/custom-section/run.sh Executable file
View File

@ -0,0 +1,31 @@
#!/bin/bash
set -e
if [ $# -gt 1 ]; then
echo "Usage: $0 [--aot]"
exit 1
fi
APP=out/wasm-apps/custom_section.wasm
if [ $# -eq 1 ]; then
if [ "$1" = "--aot" ]; then
APP=out/wasm-apps/custom_section.aot
else
echo "Usage: $0 [--aot]"
exit 1
fi
fi
if [ ! -f ${APP} ]; then
echo "Error: ${APP} not found"
if [ "$APP" = "out/wasm-apps/custom_section.aot" ]; then
echo "Run ./build.sh --aot first"
else
echo "Run ./build.sh first"
fi
exit 1
fi
out/custom_section -f ${APP}

View File

@ -0,0 +1,141 @@
/*
* Copyright (C) 2019 Intel Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*/
#include "bh_getopt.h"
#include "bh_read_file.h"
#include "wasm_export.h"
int32_t
get_custom_section_handle(wasm_exec_env_t exec_env, const char *section_name);
void
print_custom_section(wasm_exec_env_t exec_env, int32_t handle);
void
reset_custom_section_handles(void);
static void
print_usage(void)
{
fprintf(stdout, "Options:\r\n");
fprintf(stdout, " -f [path of wasm file]\n");
}
int
main(int argc, char *argv_main[])
{
static char global_heap_buf[512 * 1024];
RuntimeInitArgs init_args;
wasm_module_t module = NULL;
wasm_module_inst_t module_inst = NULL;
wasm_exec_env_t exec_env = NULL;
wasm_function_inst_t func = NULL;
wasm_val_t results[1] = { { .kind = WASM_I32, .of.i32 = -1 } };
char *buffer = NULL;
char *wasm_path = NULL;
char error_buf[128];
int exit_code = 1;
int opt;
uint32_t buf_size;
uint32_t stack_size = 8092;
uint32_t heap_size = 8092;
memset(&init_args, 0, sizeof(RuntimeInitArgs));
while ((opt = getopt(argc, argv_main, "hf:")) != -1) {
switch (opt) {
case 'f':
wasm_path = optarg;
break;
case 'h':
print_usage();
return 0;
case '?':
print_usage();
return 0;
}
}
if (optind == 1) {
print_usage();
return 0;
}
static NativeSymbol native_symbols[] = {
{ "get_custom_section_handle", get_custom_section_handle, "($)i",
NULL },
{ "print_custom_section", print_custom_section, "(i)", NULL },
};
init_args.mem_alloc_type = Alloc_With_Pool;
init_args.mem_alloc_option.pool.heap_buf = global_heap_buf;
init_args.mem_alloc_option.pool.heap_size = sizeof(global_heap_buf);
init_args.n_native_symbols = sizeof(native_symbols) / sizeof(NativeSymbol);
init_args.native_module_name = "env";
init_args.native_symbols = native_symbols;
if (!wasm_runtime_full_init(&init_args)) {
printf("Init runtime environment failed.\n");
return -1;
}
reset_custom_section_handles();
buffer = bh_read_file_to_buffer(wasm_path, &buf_size);
if (!buffer) {
printf("Open wasm app file [%s] failed.\n", wasm_path);
goto fail;
}
module = wasm_runtime_load((uint8 *)buffer, buf_size, error_buf,
sizeof(error_buf));
if (!module) {
printf("Load wasm module failed. error: %s\n", error_buf);
goto fail;
}
module_inst = wasm_runtime_instantiate(module, stack_size, heap_size,
error_buf, sizeof(error_buf));
if (!module_inst) {
printf("Instantiate wasm module failed. error: %s\n", error_buf);
goto fail;
}
exec_env = wasm_runtime_create_exec_env(module_inst, stack_size);
if (!exec_env) {
printf("Create wasm execution environment failed.\n");
goto fail;
}
func = wasm_runtime_lookup_function(module_inst, "run_demo");
if (!func) {
printf("The wasm function run_demo is not found.\n");
goto fail;
}
if (!wasm_runtime_call_wasm_a(exec_env, func, 1, results, 0, NULL)) {
printf("call wasm function run_demo failed. error: %s\n",
wasm_runtime_get_exception(module_inst));
goto fail;
}
printf("Wasm returned custom section handle: %d\n", results[0].of.i32);
exit_code = results[0].of.i32 >= 0 ? 0 : 1;
fail:
if (exec_env) {
wasm_runtime_destroy_exec_env(exec_env);
}
if (module_inst) {
wasm_runtime_deinstantiate(module_inst);
}
if (module) {
wasm_runtime_unload(module);
}
if (buffer) {
BH_FREE(buffer);
}
reset_custom_section_handles();
wasm_runtime_destroy();
return exit_code;
}

View File

@ -0,0 +1,95 @@
/*
* Copyright (C) 2019 Intel Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*/
#include <stdio.h>
#include <string.h>
#include "wasm_export.h"
#define MAX_CUSTOM_SECTION_HANDLES 8
typedef struct CustomSectionHandle {
wasm_module_t module;
const uint8_t *content;
uint32_t length;
} CustomSectionHandle;
static CustomSectionHandle custom_section_handles[MAX_CUSTOM_SECTION_HANDLES];
void
reset_custom_section_handles(void)
{
memset(custom_section_handles, 0, sizeof(custom_section_handles));
}
int32_t
get_custom_section_handle(wasm_exec_env_t exec_env, const char *section_name)
{
wasm_module_inst_t module_inst = wasm_runtime_get_module_inst(exec_env);
wasm_module_t module = wasm_runtime_get_module(module_inst);
const uint8_t *content = NULL;
uint32_t length = 0;
uint32_t i;
if (!section_name || section_name[0] == '\0') {
printf("custom section name is empty\n");
return -1;
}
content = wasm_runtime_get_custom_section(module, section_name, &length);
if (!content) {
printf("custom section [%s] not found\n", section_name);
return -1;
}
for (i = 0; i < MAX_CUSTOM_SECTION_HANDLES; i++) {
if (custom_section_handles[i].content == content
&& custom_section_handles[i].module == module
&& custom_section_handles[i].length == length) {
return (int32_t)i;
}
}
for (i = 0; i < MAX_CUSTOM_SECTION_HANDLES; i++) {
if (!custom_section_handles[i].content) {
custom_section_handles[i].module = module;
custom_section_handles[i].content = content;
custom_section_handles[i].length = length;
printf("resolved custom section [%s] to handle %u (%u bytes)\n",
section_name, i, length);
return (int32_t)i;
}
}
printf("no free custom section handle slots remain\n");
return -1;
}
void
print_custom_section(wasm_exec_env_t exec_env, int32_t handle)
{
wasm_module_inst_t module_inst = wasm_runtime_get_module_inst(exec_env);
wasm_module_t module = wasm_runtime_get_module(module_inst);
CustomSectionHandle *section_handle = NULL;
if (handle < 0 || handle >= MAX_CUSTOM_SECTION_HANDLES) {
printf("invalid custom section handle %d\n", handle);
return;
}
section_handle = &custom_section_handles[handle];
if (!section_handle->content || section_handle->module != module) {
printf("custom section handle %d is not valid for this module\n",
handle);
return;
}
printf("custom section payload (%u bytes):\n", section_handle->length);
fwrite(section_handle->content, 1, section_handle->length, stdout);
if (section_handle->length == 0
|| section_handle->content[section_handle->length - 1] != '\n') {
putchar('\n');
}
}

View File

@ -0,0 +1,19 @@
__attribute__((import_module("env"),
import_name("get_custom_section_handle"))) int
get_custom_section_handle(const char *section_name);
__attribute__((import_module("env"), import_name("print_custom_section"))) void
print_custom_section(int handle);
__attribute__((export_name("run_demo"))) int
run_demo(void)
{
static const char section_name[] = "demo";
int handle = get_custom_section_handle(section_name);
if (handle >= 0) {
print_custom_section(handle);
}
return handle;
}

View File

@ -0,0 +1,5 @@
This payload lives in a Wasm custom section.
It is linked by custom_section_payload.s with .incbin.
The payload is read by custom_section.c, which is compiled to a Wasm module and executed by the host embedder.
It can be arbitrarily data, and the host can resolve it with `wasm_runtime_get_custom_section` and print/use it later through a handle-based native API.

View File

@ -0,0 +1,2 @@
.section .custom_section.demo,"",@
.incbin "custom_section_payload.bin"