Просмотр исходного кода

Add custom section sample and enable CI test (#4891)

TianlongLiang 1 месяц назад
Родитель
Сommit
d0f8ac0e7d

+ 8 - 0
.github/workflows/compilation_on_android_ubuntu.yml

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

+ 8 - 0
.github/workflows/compilation_on_macos.yml

@@ -441,6 +441,14 @@ jobs:
           cmake --build . --config Release --parallel 4
           ./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)
         if: matrix.os == 'macos-15'
         run: |

+ 8 - 0
.github/workflows/nightly_run.yml

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

+ 1 - 0
samples/README.md

@@ -1,6 +1,7 @@
 # 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.
+- [**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.
 - **[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.

+ 1 - 0
samples/custom-section/.gitignore

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

+ 80 - 0
samples/custom-section/CMakeLists.txt

@@ -0,0 +1,80 @@
+# 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_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()

+ 69 - 0
samples/custom-section/README.md

@@ -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 - 0
samples/custom-section/build.sh

@@ -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 4
+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 - 0
samples/custom-section/run.sh

@@ -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}

+ 141 - 0
samples/custom-section/src/main.c

@@ -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;
+}

+ 95 - 0
samples/custom-section/src/native_impl.c

@@ -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');
+    }
+}

+ 19 - 0
samples/custom-section/wasm-apps/custom_section.c

@@ -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;
+}

+ 5 - 0
samples/custom-section/wasm-apps/custom_section_payload.bin

@@ -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 arbitrary data, and the host can resolve it with `wasm_runtime_get_custom_section` and print/use it later through a handle-based native API.

+ 2 - 0
samples/custom-section/wasm-apps/custom_section_payload.s

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